Implement putting clients onto specific workspaces ("assign" in the configfile)
This closes ticket #39
This commit is contained in:
parent
3ab4ecdb01
commit
e79cca8f72
|
@ -1,5 +1,5 @@
|
|||
|
||||
all: hacking-howto.html debugging.html
|
||||
all: hacking-howto.html debugging.html userguide.html
|
||||
|
||||
hacking-howto.html: hacking-howto
|
||||
asciidoc -a toc -n $<
|
||||
|
@ -7,6 +7,8 @@ hacking-howto.html: hacking-howto
|
|||
debugging.html: debugging
|
||||
asciidoc -n $<
|
||||
|
||||
userguide.html: userguide
|
||||
asciidoc -a toc -n $<
|
||||
|
||||
clean:
|
||||
rm -f */*.{aux,log,toc,bm,pdf,dvi}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
i3 User’s 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 I’ll 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), you’d 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
|
||||
----------------------
|
|
@ -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
|
|
@ -48,7 +48,6 @@ typedef struct Font i3Font;
|
|||
typedef struct Container Container;
|
||||
typedef struct Client Client;
|
||||
typedef struct Binding Binding;
|
||||
typedef struct Autostart Autostart;
|
||||
typedef struct Workspace Workspace;
|
||||
typedef struct Rect Rect;
|
||||
typedef struct Screen i3Screen;
|
||||
|
@ -208,6 +207,17 @@ struct Autostart {
|
|||
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:
|
||||
* - font id in X11 (load it once)
|
||||
|
|
|
@ -26,6 +26,7 @@ extern char **start_argv;
|
|||
extern Display *xkbdpy;
|
||||
extern TAILQ_HEAD(bindings_head, Binding) bindings;
|
||||
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 xcb_event_handlers_t evenths;
|
||||
extern int num_screens;
|
||||
|
|
|
@ -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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
|
@ -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 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 (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 (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;
|
||||
}
|
|
@ -23,6 +23,7 @@
|
|||
#include "layout.h"
|
||||
#include "i3.h"
|
||||
#include "xinerama.h"
|
||||
#include "client.h"
|
||||
|
||||
bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
|
||||
/* If this container is empty, we’re 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_client_from_container(conn, current_client, container);
|
||||
client_remove_from_container(conn, current_client, container);
|
||||
|
||||
if (new->currently_focused != NULL)
|
||||
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);
|
||||
|
||||
remove_client_from_container(conn, current_client, container);
|
||||
client_remove_from_container(conn, current_client, container);
|
||||
if (container->workspace->fullscreen_client == current_client)
|
||||
container->workspace->fullscreen_client = NULL;
|
||||
|
||||
|
@ -538,7 +539,7 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
|
|||
if (CUR_CELL->currently_focused != NULL) {
|
||||
set_focus(conn, CUR_CELL->currently_focused, true);
|
||||
if (need_warp) {
|
||||
warp_pointer_into(conn, CUR_CELL->currently_focused);
|
||||
client_warp_pointer_into(conn, CUR_CELL->currently_focused);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
}
|
||||
|
@ -574,7 +575,7 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
|
|||
if (CUR_CELL->currently_focused != NULL) {
|
||||
set_focus(conn, CUR_CELL->currently_focused, true);
|
||||
if (need_warp) {
|
||||
warp_pointer_into(conn, CUR_CELL->currently_focused);
|
||||
client_warp_pointer_into(conn, CUR_CELL->currently_focused);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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, we’re 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.
|
||||
* 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) {
|
||||
char *to_class, *to_title, *to_title_ucs = NULL;
|
||||
int to_title_ucs_len;
|
||||
char *classtitle;
|
||||
Client *client;
|
||||
|
||||
/* The first character is a quote, this was checked before */
|
||||
to_class = sstrdup(arguments+1);
|
||||
classtitle = sstrdup(arguments+1);
|
||||
/* The last character is a quote, we just set it to NULL */
|
||||
to_class[strlen(to_class)-1] = '\0';
|
||||
classtitle[strlen(classtitle)-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);
|
||||
if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) {
|
||||
free(classtitle);
|
||||
LOG("No matching client found.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
CIRCLEQ_FOREACH(client, &(con->clients), 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;
|
||||
|
||||
set_focus(conn, client, true);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
free(to_class);
|
||||
FREE(to_title_ucs);
|
||||
free(classtitle);
|
||||
set_focus(conn, client, true);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -763,7 +713,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
}
|
||||
|
||||
LOG("Killing current window\n");
|
||||
kill_window(conn, CUR_CELL->currently_focused);
|
||||
client_kill(conn, CUR_CELL->currently_focused);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
40
src/config.c
40
src/config.c
|
@ -87,7 +87,7 @@ void load_configuration(const char *override_configpath) {
|
|||
|
||||
/* exec-lines (autostart) */
|
||||
if (strcasecmp(key, "exec") == 0) {
|
||||
Autostart *new = smalloc(sizeof(Autostart));
|
||||
struct Autostart *new = smalloc(sizeof(struct Autostart));
|
||||
new->command = sstrdup(value);
|
||||
TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
|
||||
continue;
|
||||
|
@ -133,6 +133,44 @@ void load_configuration(const char *override_configpath) {
|
|||
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);
|
||||
exit(1);
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "config.h"
|
||||
#include "queue.h"
|
||||
#include "resize.h"
|
||||
#include "client.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
|
||||
|
@ -501,7 +502,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
|
|||
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);
|
||||
|
||||
/* Set focus to the last focused client in this container */
|
||||
con->currently_focused = get_last_focused_client(conn, con, NULL);
|
||||
|
|
142
src/mainx.c
142
src/mainx.c
|
@ -55,6 +55,9 @@ 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);
|
||||
|
||||
|
@ -102,17 +105,21 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_
|
|||
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_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, atoms[_NET_WM_NAME]);
|
||||
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, atoms[_NET_WM_NAME]);
|
||||
|
||||
free(geom);
|
||||
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,
|
||||
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 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);
|
||||
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);
|
||||
|
||||
|
@ -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… */
|
||||
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);
|
||||
|
||||
|
@ -227,15 +240,16 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
|||
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);
|
||||
}
|
||||
for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
|
||||
if (atom[i] != atoms[_NET_WM_WINDOW_TYPE_DOCK])
|
||||
continue;
|
||||
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) {
|
||||
|
@ -257,18 +271,65 @@ 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);
|
||||
new->desired_height = 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;
|
||||
|
||||
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;
|
||||
xcb_unmap_window(conn, new->frame);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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);
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
} 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 */
|
||||
|
@ -276,8 +337,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
|||
/* 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);
|
||||
CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
|
||||
else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
|
||||
|
||||
SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
|
||||
}
|
||||
|
@ -287,14 +348,15 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
|||
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;
|
||||
}
|
||||
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 */
|
||||
toggle_fullscreen(conn, new);
|
||||
return;
|
||||
}
|
||||
|
||||
render_layout(conn);
|
||||
}
|
||||
|
@ -531,7 +593,7 @@ int main(int argc, char *argv[], char *env[]) {
|
|||
}
|
||||
|
||||
/* Autostarting exec-lines */
|
||||
Autostart *exec;
|
||||
struct Autostart *exec;
|
||||
TAILQ_FOREACH(exec, &autostarts, autostarts) {
|
||||
LOG("auto-starting %s\n", exec->command);
|
||||
start_application(exec->command);
|
||||
|
|
112
src/util.c
112
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);
|
||||
|
@ -224,25 +225,6 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
|
|||
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.
|
||||
|
@ -435,17 +417,6 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode)
|
|||
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
|
||||
* 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) {
|
||||
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;
|
||||
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;
|
||||
FOR_TABLE(&(workspaces[workspace])) {
|
||||
Container *con = workspaces[workspace].table[cols][rows];
|
||||
Client *client;
|
||||
|
||||
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);
|
||||
CIRCLEQ_FOREACH(client, &(con->clients), 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;
|
||||
|
||||
matching = client;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
free(to_class);
|
||||
FREE(to_title_ucs);
|
||||
return matching;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue