Time Lord technology: for_window config directive to run arbitrary cmds

An example to set all XTerms floating:
    for_window [class="XTerm"] mode floating

To make all urxvts use a 1-pixel border:
    for_window [class="urxvt"] border 1pixel

A less useful, but rather funny example:
    for_window [title="x200: ~/work"] mode floating

The commands are not completely arbitrary. The commands above were tested,
others may need some fixing. Internally, windows are compared against your
criteria (class, title, …) when they are initially managed and whenever one of
the relevant values change. Then, the specified command is run *once* (per
window). It gets prefixed with a criteria to make it match only the specific
window that triggered it. So, if you configure "mode floating", i3 runs
something like '[id="8393923"] mode floating'.
next
Michael Stapelberg 2011-05-15 20:10:25 +02:00
parent ca2e4199b5
commit 5ae4620a24
15 changed files with 291 additions and 34 deletions

View File

@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk
# Depend on the object files of all source-files in src/*.c and on all header files
AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c
FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c src/move.c src/output.c src/ewmh.c
FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c src/move.c src/output.c src/ewmh.c src/assignments.c
FILES:=$(FILES:.c=.o)
HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h))

View File

@ -62,5 +62,6 @@
#include "move.h"
#include "output.h"
#include "ewmh.h"
#include "assignments.h"
#endif

15
include/assignments.h Normal file
View File

@ -0,0 +1,15 @@
/*
* vim:ts=4:sw=4:expandtab
*
*/
#ifndef _ASSIGNMENTS_H
#define _ASSIGNMENTS_H
/**
* Checks the list of assignments for the given window and runs all matching
* ones (unless they have already been run for this specific window).
*
*/
void run_assignments(i3Window *window);
#endif

View File

@ -33,6 +33,7 @@ typedef struct Rect Rect;
typedef struct xoutput Output;
typedef struct Con Con;
typedef struct Match Match;
typedef struct Assignment Assignment;
typedef struct Window i3Window;
@ -274,11 +275,14 @@ struct Window {
/** Pixels the window reserves. left/right/top/bottom */
struct reservedpx reserved;
/** Pointers to the Assignments which were already ran for this Window
* (assignments run only once) */
uint32_t nr_assignments;
Assignment **ran_assignments;
};
struct Match {
enum { M_WINDOW, M_CON } what;
char *title;
int title_len;
char *application;
@ -296,10 +300,6 @@ struct Match {
Con *con_id;
enum { M_ANY = 0, M_TILING, M_FLOATING } floating;
enum { M_GLOBAL = 0, M_OUTPUT, M_WORKSPACE } levels;
enum { M_USER = 0, M_RESTART } source;
char *target_ws;
/* Where the window looking for a match should be inserted:
@ -317,6 +317,29 @@ struct Match {
TAILQ_ENTRY(Match) assignments;
};
struct Assignment {
/** type of this assignment:
*
* A_COMMAND = run the specified command for the matching window
* A_TO_WORKSPACE = assign the matching window to the specified workspace
* A_TO_OUTPUT = assign the matching window to the specified output
*
*/
enum { A_COMMAND = 0, A_TO_WORKSPACE = 1, A_TO_OUTPUT = 2 } type;
/** the criteria to check if a window matches */
Match match;
/** destination workspace/output/command, depending on the type */
union {
char *command;
char *workspace;
char *output;
} dest;
TAILQ_ENTRY(Assignment) real_assignments;
};
struct Con {
bool mapped;
enum {

View File

@ -28,6 +28,7 @@ extern TAILQ_HEAD(bindings_head, Binding) *bindings;
extern TAILQ_HEAD(autostarts_head, Autostart) autostarts;
extern TAILQ_HEAD(assignments_head, Match) assignments;
extern TAILQ_HEAD(ws_assignments_head, Workspace_Assignment) ws_assignments;
extern TAILQ_HEAD(real_assignments_head, Assignment) real_assignments;
extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins;
extern uint8_t root_depth;
extern bool xcursor_supported, xkb_supported;

View File

@ -6,14 +6,14 @@
* given window.
*
*/
void window_update_class(i3Window *win, xcb_get_property_reply_t *prop);
void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
/**
* Updates the name by using _NET_WM_NAME (encoded in UTF-8) for the given
* window. Further updates using window_update_name_legacy will be ignored.
*
*/
void window_update_name(i3Window *win, xcb_get_property_reply_t *prop);
void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
/**
* Updates the name by using WM_NAME (encoded in COMPOUND_TEXT). We do not
@ -22,7 +22,7 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop);
* window_update_name()).
*
*/
void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop);
void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
/**
* Updates the CLIENT_LEADER (logical parent window).

50
src/assignments.c Normal file
View File

@ -0,0 +1,50 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#include "all.h"
/*
* Checks the list of assignments for the given window and runs all matching
* ones (unless they have already been run for this specific window).
*
*/
void run_assignments(i3Window *window) {
DLOG("Checking assignments...\n");
/* Check if any assignments match */
Assignment *current;
TAILQ_FOREACH(current, &real_assignments, real_assignments) {
if (!match_matches_window(&(current->match), window))
continue;
bool skip = false;
for (int c = 0; c < window->nr_assignments; c++) {
if (window->ran_assignments[c] != current)
continue;
DLOG("This assignment already ran for the given window, not executing it again.\n");
skip = true;
break;
}
if (skip)
continue;
DLOG("matching assignment, would do:\n");
if (current->type == A_COMMAND) {
DLOG("execute command %s\n", current->dest.command);
char *full_command;
asprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command);
parse_cmd(full_command);
}
/* Store that we ran this assignment to not execute it again */
window->nr_assignments++;
window->ran_assignments = srealloc(window->ran_assignments, sizeof(Assignment*) * window->nr_assignments);
window->ran_assignments[window->nr_assignments-1] = current;
}
}

View File

@ -43,6 +43,9 @@ EOL (\r?\n)
%s COLOR_COND
%s OUTPUT_COND
%s OUTPUT_AWS_COND
%s WANT_QSTRING
%s FOR_WINDOW_COND
%s REQUIRE_WS
%x BUFFER_LINE
%%
@ -68,6 +71,16 @@ EOL (\r?\n)
}
<FOR_WINDOW_COND>"]" { yy_pop_state(); return ']'; }
<REQUIRE_WS>[ \t]* { yy_pop_state(); return WHITESPACE; }
<WANT_QSTRING>\"[^\"]+\" {
yy_pop_state();
/* strip quotes */
char *copy = sstrdup(yytext+1);
copy[strlen(copy)-1] = '\0';
yylval.string = copy;
return STR;
}
<BIND_A2WS_COND>[^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; }
<OUTPUT_AWS_COND>[a-zA-Z0-9_-]+ { yylval.string = strdup(yytext); return OUTPUT; }
^[ \t]*#[^\n]* { return TOKCOMMENT; }
@ -108,6 +121,18 @@ workspace_bar { return TOKWORKSPACEBAR; }
popup_during_fullscreen { return TOK_POPUP_DURING_FULLSCREEN; }
ignore { return TOK_IGNORE; }
leave_fullscreen { return TOK_LEAVE_FULLSCREEN; }
for_window {
/* Example: for_window [class="urxvt"] border none
*
* First, we wait for the ']' that finishes a match (FOR_WINDOW_COND)
* Then, we require a whitespace (REQUIRE_WS)
* And the rest of the line is parsed as a string
*/
yy_push_state(BIND_A2WS_COND);
yy_push_state(REQUIRE_WS);
yy_push_state(FOR_WINDOW_COND);
return TOK_FOR_WINDOW;
}
default { /* yylval.number = MODE_DEFAULT; */return TOK_DEFAULT; }
stacking { /* yylval.number = MODE_STACK; */return TOK_STACKING; }
stacked { return TOK_STACKING; }
@ -134,6 +159,13 @@ control { return TOKCONTROL; }
ctrl { return TOKCONTROL; }
shift { return TOKSHIFT; }
→ { return TOKARROW; }
class { yy_push_state(WANT_QSTRING); return TOK_CLASS; }
id { yy_push_state(WANT_QSTRING); return TOK_ID; }
con_id { yy_push_state(WANT_QSTRING); return TOK_CON_ID; }
con_mark { yy_push_state(WANT_QSTRING); return TOK_MARK; }
title { yy_push_state(WANT_QSTRING); return TOK_TITLE; }
{EOL} {
FREE(context->line_copy);
context->line_number++;

View File

@ -7,9 +7,12 @@
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include "all.h"
static Match current_match;
typedef struct yy_buffer_state *YY_BUFFER_STATE;
extern int yylex(struct context *context);
extern int yyparse(void);
@ -242,6 +245,13 @@ void parse_file(const char *f) {
%token TOK_POPUP_DURING_FULLSCREEN "popup_during_fullscreen"
%token TOK_IGNORE "ignore"
%token TOK_LEAVE_FULLSCREEN "leave_fullscreen"
%token TOK_FOR_WINDOW "for_window"
%token TOK_MARK "mark"
%token TOK_CLASS "class"
%token TOK_ID "id"
%token TOK_CON_ID "con_id"
%token TOK_TITLE "title"
%type <binding> binding
%type <binding> bindcode
@ -272,6 +282,7 @@ lines: /* empty */
line:
bindline
| for_window
| mode
| floating_modifier
| orientation
@ -292,6 +303,10 @@ line:
| popup_during_fullscreen
;
optwhitespace:
| WHITESPACE
;
comment:
TOKCOMMENT
;
@ -340,6 +355,90 @@ bindsym:
}
;
for_window:
TOK_FOR_WINDOW WHITESPACE match WHITESPACE command
{
printf("\t should execute command %s for the criteria mentioned above\n", $5);
Assignment *assignment = scalloc(sizeof(Assignment));
assignment->type = A_COMMAND;
assignment->match = current_match;
assignment->dest.command = $5;
TAILQ_INSERT_TAIL(&real_assignments, assignment, real_assignments);
}
;
match:
| matchstart optwhitespace criteria optwhitespace matchend
{
printf("match parsed\n");
}
;
matchstart:
'['
{
printf("start\n");
match_init(&current_match);
}
;
matchend:
']'
{
printf("match specification finished\n");
}
;
criteria:
TOK_CLASS '=' STR
{
printf("criteria: class = %s\n", $3);
current_match.class = $3;
}
| TOK_CON_ID '=' STR
{
printf("criteria: id = %s\n", $3);
char *end;
long parsed = strtol($3, &end, 10);
if (parsed == LONG_MIN ||
parsed == LONG_MAX ||
parsed < 0 ||
(end && *end != '\0')) {
ELOG("Could not parse con id \"%s\"\n", $3);
} else {
current_match.con_id = (Con*)parsed;
printf("id as int = %p\n", current_match.con_id);
}
}
| TOK_ID '=' STR
{
printf("criteria: window id = %s\n", $3);
char *end;
long parsed = strtol($3, &end, 10);
if (parsed == LONG_MIN ||
parsed == LONG_MAX ||
parsed < 0 ||
(end && *end != '\0')) {
ELOG("Could not parse window id \"%s\"\n", $3);
} else {
current_match.id = parsed;
printf("window id as int = %d\n", current_match.id);
}
}
| TOK_MARK '=' STR
{
printf("criteria: mark = %s\n", $3);
current_match.mark = $3;
}
| TOK_TITLE '=' STR
{
printf("criteria: title = %s\n", $3);
current_match.title = $3;
}
;
word_or_number:
WORD
| NUMBER

View File

@ -3,7 +3,7 @@
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
* cmdparse.y: the parser for commands you send to i3 (or bind on keys)
*
@ -80,6 +80,7 @@ int cmdyywrap() {
}
char *parse_cmd(const char *new) {
LOG("COMMAND: *%s*\n", new);
cmdyy_scan_string(new);
match_init(&current_match);
@ -512,15 +513,21 @@ direction:
mode:
TOK_MODE WHITESPACE window_mode
{
if ($3 == TOK_TOGGLE) {
printf("should toggle mode\n");
toggle_floating_mode(focused, false);
} else {
printf("should switch mode to %s\n", ($3 == TOK_FLOATING ? "floating" : "tiling"));
if ($3 == TOK_FLOATING) {
floating_enable(focused, false);
HANDLE_EMPTY_MATCH;
owindow *current;
TAILQ_FOREACH(current, &owindows, owindows) {
printf("matching: %p / %s\n", current->con, current->con->name);
if ($3 == TOK_TOGGLE) {
printf("should toggle mode\n");
toggle_floating_mode(current->con, false);
} else {
floating_disable(focused, false);
printf("should switch mode to %s\n", ($3 == TOK_FLOATING ? "floating" : "tiling"));
if ($3 == TOK_FLOATING) {
floating_enable(current->con, false);
} else {
floating_disable(current->con, false);
}
}
}
}
@ -608,11 +615,14 @@ layout:
printf("changing layout to %d\n", $3);
owindow *current;
HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) {
printf("matching: %p / %s\n", current->con, current->con->name);
con_set_layout(current->con, $3);
/* check if the match is empty, not if the result is empty */
if (match_is_empty(&current_match))
con_set_layout(focused->parent, $3);
else {
TAILQ_FOREACH(current, &owindows, owindows) {
printf("matching: %p / %s\n", current->con, current->con->name);
con_set_layout(current->con, $3);
}
}
}
;

View File

@ -541,7 +541,7 @@ static int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t
if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
return 1;
window_update_name(con->window, prop);
window_update_name(con->window, prop, false);
x_push_changes(croot);
@ -559,7 +559,7 @@ static int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, u
if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
return 1;
window_update_name_legacy(con->window, prop);
window_update_name_legacy(con->window, prop, false);
x_push_changes(croot);
@ -576,7 +576,7 @@ static int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t
if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
return 1;
window_update_class(con->window, prop);
window_update_class(con->window, prop, false);
return 0;
}

View File

@ -37,6 +37,8 @@ struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments);
* output) */
struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignments);
struct real_assignments_head real_assignments = TAILQ_HEAD_INITIALIZER(real_assignments);
/* We hope that those are supported and set them to true */
bool xcursor_supported = true;
bool xkb_supported = true;

View File

@ -158,9 +158,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
/* update as much information as possible so far (some replies may be NULL) */
window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL));
window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL));
window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL));
window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL), true);
window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL), true);
window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL), true);
window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL));
window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL));
window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));
@ -330,6 +330,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
* cleanup) */
xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window);
/* Check if any assignments match */
run_assignments(cwindow);
tree_render();
free(geom);

View File

@ -66,6 +66,12 @@ bool match_matches_window(Match *match, i3Window *window) {
return true;
}
/* TODO: pcre match */
if (match->title != NULL && window->name_json != NULL && strcasecmp(match->title, window->name_json) == 0) {
LOG("match made by title (%s)\n", window->name_json);
return true;
}
LOG("match->dock = %d, window->dock = %d\n", match->dock, window->dock);
if (match->dock != -1 &&
((window->dock == W_DOCK_TOP && match->dock == M_DOCK_TOP) ||

View File

@ -2,7 +2,7 @@
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#include "all.h"
@ -12,7 +12,7 @@
* given window.
*
*/
void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) {
void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
DLOG("empty property, not updating\n");
return;
@ -32,6 +32,11 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) {
else win->class_class = NULL;
LOG("WM_CLASS changed to %s (instance), %s (class)\n",
win->class_instance, win->class_class);
if (before_mgmt)
return;
run_assignments(win);
}
/*
@ -39,7 +44,7 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) {
* window. Further updates using window_update_name_legacy will be ignored.
*
*/
void window_update_name(i3Window *win, xcb_get_property_reply_t *prop) {
void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
DLOG("_NET_WM_NAME not specified, not changing\n");
return;
@ -70,6 +75,11 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop) {
LOG("_NET_WM_NAME changed to \"%s\"\n", win->name_json);
win->uses_net_wm_name = true;
if (before_mgmt)
return;
run_assignments(win);
}
/*
@ -79,7 +89,7 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop) {
* window_update_name()).
*
*/
void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop) {
void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
DLOG("prop == NULL\n");
return;
@ -106,6 +116,11 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop) {
win->name_json = sstrdup(new_name);
win->name_len = strlen(new_name);
win->name_x_changed = true;
if (before_mgmt)
return;
run_assignments(win);
}
/*