# 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))
#include "move.h"
#include "output.h"
#include "ewmh.h"
+#include "assignments.h"
#endif
--- /dev/null
+/*
+ * 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
typedef struct xoutput Output;
typedef struct Con Con;
typedef struct Match Match;
+typedef struct Assignment Assignment;
typedef struct Window i3Window;
/** 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;
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:
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 {
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;
* 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
* 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).
--- /dev/null
+/*
+ * 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;
+ }
+}
%s COLOR_COND
%s OUTPUT_COND
%s OUTPUT_AWS_COND
+%s WANT_QSTRING
+%s FOR_WINDOW_COND
+%s REQUIRE_WS
%x BUFFER_LINE
%%
}
+<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; }
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; }
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++;
#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);
%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
line:
bindline
+ | for_window
| mode
| floating_modifier
| orientation
| popup_during_fullscreen
;
+optwhitespace:
+ | WHITESPACE
+ ;
+
comment:
TOKCOMMENT
;
}
;
+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(¤t_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
* 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)
*
}
char *parse_cmd(const char *new) {
+ LOG("COMMAND: *%s*\n", new);
cmdyy_scan_string(new);
match_init(¤t_match);
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);
+ }
}
}
}
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(¤t_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);
+ }
}
}
;
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);
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);
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;
}
* 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;
/* 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));
* cleanup) */
xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window);
+ /* Check if any assignments match */
+ run_assignments(cwindow);
+
tree_render();
free(geom);
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) ||
* 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"
* 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;
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);
}
/*
* 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;
LOG("_NET_WM_NAME changed to \"%s\"\n", win->name_json);
win->uses_net_wm_name = true;
+
+ if (before_mgmt)
+ return;
+
+ run_assignments(win);
}
/*
* 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;
win->name_json = sstrdup(new_name);
win->name_len = strlen(new_name);
win->name_x_changed = true;
+
+ if (before_mgmt)
+ return;
+
+ run_assignments(win);
}
/*