diff --git a/Makefile b/Makefile index c9359142..6e032f41 100644 --- a/Makefile +++ b/Makefile @@ -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)) diff --git a/include/all.h b/include/all.h index f9f12a70..ba582a80 100644 --- a/include/all.h +++ b/include/all.h @@ -62,5 +62,6 @@ #include "move.h" #include "output.h" #include "ewmh.h" +#include "assignments.h" #endif diff --git a/include/assignments.h b/include/assignments.h new file mode 100644 index 00000000..ecd8b808 --- /dev/null +++ b/include/assignments.h @@ -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 diff --git a/include/data.h b/include/data.h index c408d3f6..832200ee 100644 --- a/include/data.h +++ b/include/data.h @@ -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 { diff --git a/include/i3.h b/include/i3.h index a18cd6bc..6aeea847 100644 --- a/include/i3.h +++ b/include/i3.h @@ -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; diff --git a/include/window.h b/include/window.h index 1c48c012..fe282aa0 100644 --- a/include/window.h +++ b/include/window.h @@ -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). diff --git a/src/assignments.c b/src/assignments.c new file mode 100644 index 00000000..f41877f0 --- /dev/null +++ b/src/assignments.c @@ -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; + } +} diff --git a/src/cfgparse.l b/src/cfgparse.l index 1615288e..09cf9c44 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -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) } +"]" { yy_pop_state(); return ']'; } +[ \t]* { yy_pop_state(); return WHITESPACE; } +\"[^\"]+\" { + yy_pop_state(); + /* strip quotes */ + char *copy = sstrdup(yytext+1); + copy[strlen(copy)-1] = '\0'; + yylval.string = copy; + return STR; + } [^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; } [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++; diff --git a/src/cfgparse.y b/src/cfgparse.y index a5e0f0ba..00190dbc 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -7,9 +7,12 @@ #include #include #include +#include #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 %type 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(¤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 diff --git a/src/cmdparse.y b/src/cmdparse.y index eb41c820..bc4858f2 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -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(¤t_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(¤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); + } } } ; diff --git a/src/handlers.c b/src/handlers.c index f1e1c4b0..b87bb418 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -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; } diff --git a/src/main.c b/src/main.c index c079d8dc..39702223 100644 --- a/src/main.c +++ b/src/main.c @@ -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; diff --git a/src/manage.c b/src/manage.c index b511189c..7d240f42 100644 --- a/src/manage.c +++ b/src/manage.c @@ -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); diff --git a/src/match.c b/src/match.c index 9ed4d434..fd024d59 100644 --- a/src/match.c +++ b/src/match.c @@ -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) || diff --git a/src/window.c b/src/window.c index cd96890c..b6aaae9e 100644 --- a/src/window.c +++ b/src/window.c @@ -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); } /*