Merge pull request #2018 from Airblader/feature-2014

Allow multiple marks on a window
This commit is contained in:
Michael Stapelberg 2015-10-22 20:32:05 +02:00
commit 5333d15180
15 changed files with 344 additions and 98 deletions

View File

@ -1659,10 +1659,13 @@ workspace::
the special value +\_\_focused__+ to match all windows in the currently the special value +\_\_focused__+ to match all windows in the currently
focused workspace. focused workspace.
con_mark:: con_mark::
Compares the mark set for this container, see <<vim_like_marks>>. Compares the marks set for this container, see <<vim_like_marks>>. A
match is made if any of the container's marks matches the specified
mark.
con_id:: con_id::
Compares the i3-internal container ID, which you can get via the IPC Compares the i3-internal container ID, which you can get via the IPC
interface. Handy for scripting. interface. Handy for scripting. Use the special value +\_\_focused__+
to match only the currently focused window.
The criteria +class+, +instance+, +role+, +title+, +workspace+ and +mark+ are The criteria +class+, +instance+, +role+, +title+, +workspace+ and +mark+ are
actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for
@ -2113,24 +2116,36 @@ for this purpose: It lets you input a command and sends the command to i3. It
can also prefix this command and display a custom prompt for the input dialog. can also prefix this command and display a custom prompt for the input dialog.
The additional +--toggle+ option will remove the mark if the window already has The additional +--toggle+ option will remove the mark if the window already has
this mark, add it if the window has none or replace the current mark if it has this mark or add it otherwise. Note that you may need to use this in
another mark. combination with +--add+ (see below) as any other marks will otherwise be
removed.
By default, a window can only have one mark. You can use the +--add+ flag to
put more than one mark on a window.
Refer to <<show_marks>> if you don't want marks to be shown in the window decoration. Refer to <<show_marks>> if you don't want marks to be shown in the window decoration.
*Syntax*: *Syntax*:
------------------------------ ----------------------------------------------
mark [--toggle] <identifier> mark [--add|--replace] [--toggle] <identifier>
[con_mark="identifier"] focus [con_mark="identifier"] focus
unmark <identifier> unmark <identifier>
------------------------------ ----------------------------------------------
*Example (in a terminal)*: *Example (in a terminal)*:
------------------------------ ---------------------------------------------------------
$ i3-msg mark irssi # marks the focused container
$ i3-msg '[con_mark="irssi"] focus' mark irssi
$ i3-msg unmark irssi
------------------------------ # focus the container with the mark "irssi"
'[con_mark="irssi"] focus'
# remove the mark "irssi" from whichever container has it
unmark irssi
# remove all marks on all firefox windows
[class="(?i)firefox"] unmark
---------------------------------------------------------
/////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////
TODO: make i3-input replace %s TODO: make i3-input replace %s

View File

@ -115,10 +115,10 @@ void cmd_workspace_back_and_forth(I3_CMD);
void cmd_workspace_name(I3_CMD, const char *name); void cmd_workspace_name(I3_CMD, const char *name);
/** /**
* Implementation of 'mark [--toggle] <mark>' * Implementation of 'mark [--add|--replace] [--toggle] <mark>'
* *
*/ */
void cmd_mark(I3_CMD, const char *mark, const char *toggle); void cmd_mark(I3_CMD, const char *mark, const char *mode, const char *toggle);
/** /**
* Implementation of 'unmark [mark]' * Implementation of 'unmark [mark]'

View File

@ -146,26 +146,34 @@ Con *con_by_frame_id(xcb_window_t frame);
*/ */
Con *con_by_mark(const char *mark); Con *con_by_mark(const char *mark);
/**
* Returns true if and only if the given containers holds the mark.
*
*/
bool con_has_mark(Con *con, const char *mark);
/** /**
* Toggles the mark on a container. * Toggles the mark on a container.
* If the container already has this mark, the mark is removed. * If the container already has this mark, the mark is removed.
* Otherwise, the mark is assigned to the container. * Otherwise, the mark is assigned to the container.
* *
*/ */
void con_mark_toggle(Con *con, const char *mark); void con_mark_toggle(Con *con, const char *mark, mark_mode_t mode);
/** /**
* Assigns a mark to the container. * Assigns a mark to the container.
* *
*/ */
void con_mark(Con *con, const char *mark); void con_mark(Con *con, const char *mark, mark_mode_t mode);
/** /*
* If mark is NULL, this removes all existing marks. * Removes marks from containers.
* If con is NULL, all containers are considered.
* If name is NULL, this removes all existing marks.
* Otherwise, it will only remove the given mark (if it is present). * Otherwise, it will only remove the given mark (if it is present).
* *
*/ */
void con_unmark(const char *mark); void con_unmark(Con *con, const char *name);
/** /**
* Returns the first container below 'con' which wants to swallow this window * Returns the first container below 'con' which wants to swallow this window

View File

@ -46,6 +46,7 @@ typedef struct Con Con;
typedef struct Match Match; typedef struct Match Match;
typedef struct Assignment Assignment; typedef struct Assignment Assignment;
typedef struct Window i3Window; typedef struct Window i3Window;
typedef struct mark_t mark_t;
/****************************************************************************** /******************************************************************************
* Helper types * Helper types
@ -74,6 +75,9 @@ typedef enum { ADJ_NONE = 0,
ADJ_UPPER_SCREEN_EDGE = (1 << 2), ADJ_UPPER_SCREEN_EDGE = (1 << 2),
ADJ_LOWER_SCREEN_EDGE = (1 << 4) } adjacent_t; ADJ_LOWER_SCREEN_EDGE = (1 << 4) } adjacent_t;
typedef enum { MM_REPLACE,
MM_ADD } mark_mode_t;
/** /**
* Container layouts. See Con::layout. * Container layouts. See Con::layout.
*/ */
@ -523,6 +527,12 @@ typedef enum { CF_NONE = 0,
CF_OUTPUT = 1, CF_OUTPUT = 1,
CF_GLOBAL = 2 } fullscreen_mode_t; CF_GLOBAL = 2 } fullscreen_mode_t;
struct mark_t {
char *name;
TAILQ_ENTRY(mark_t) marks;
};
/** /**
* A 'Con' represents everything from the X11 root window down to a single X11 window. * A 'Con' represents everything from the X11 root window down to a single X11 window.
* *
@ -575,8 +585,8 @@ struct Con {
* displayed on whichever of the containers is currently visible */ * displayed on whichever of the containers is currently visible */
char *sticky_group; char *sticky_group;
/* user-definable mark to jump to this container later */ /* user-definable marks to jump to this container later */
char *mark; TAILQ_HEAD(marks_head, mark_t) marks_head;
/* cached to decide whether a redraw is needed */ /* cached to decide whether a redraw is needed */
bool mark_changed; bool mark_changed;

View File

@ -199,12 +199,14 @@ state FLOATING:
floating = 'enable', 'disable', 'toggle' floating = 'enable', 'disable', 'toggle'
-> call cmd_floating($floating) -> call cmd_floating($floating)
# mark [--toggle] <mark> # mark [--add|--replace] [--toggle] <mark>
state MARK: state MARK:
mode = '--add', '--replace'
->
toggle = '--toggle' toggle = '--toggle'
-> ->
mark = string mark = string
-> call cmd_mark($mark, $toggle) -> call cmd_mark($mark, $mode, $toggle)
# unmark [mark] # unmark [mark]
state UNMARK: state UNMARK:

View File

@ -290,10 +290,16 @@ void cmd_criteria_match_windows(I3_CMD) {
DLOG("doesnt match\n"); DLOG("doesnt match\n");
free(current); free(current);
} }
} else if (current_match->mark != NULL && current->con->mark != NULL && } else if (current_match->mark != NULL && !TAILQ_EMPTY(&(current->con->marks_head))) {
regex_matches(current_match->mark, current->con->mark)) { mark_t *mark;
TAILQ_FOREACH(mark, &(current->con->marks_head), marks) {
if (!regex_matches(current_match->mark, mark->name))
continue;
DLOG("match by mark\n"); DLOG("match by mark\n");
TAILQ_INSERT_TAIL(&owindows, current, owindows); TAILQ_INSERT_TAIL(&owindows, current, owindows);
break;
}
} else { } else {
if (current->con->window && match_matches_window(current_match, current->con->window)) { if (current->con->window && match_matches_window(current_match, current->con->window)) {
DLOG("matches window!\n"); DLOG("matches window!\n");
@ -997,10 +1003,10 @@ void cmd_workspace_name(I3_CMD, const char *name) {
} }
/* /*
* Implementation of 'mark [--toggle] <mark>' * Implementation of 'mark [--add|--replace] [--toggle] <mark>'
* *
*/ */
void cmd_mark(I3_CMD, const char *mark, const char *toggle) { void cmd_mark(I3_CMD, const char *mark, const char *mode, const char *toggle) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
owindow *current = TAILQ_FIRST(&owindows); owindow *current = TAILQ_FIRST(&owindows);
@ -1016,10 +1022,12 @@ void cmd_mark(I3_CMD, const char *mark, const char *toggle) {
} }
DLOG("matching: %p / %s\n", current->con, current->con->name); DLOG("matching: %p / %s\n", current->con, current->con->name);
mark_mode_t mark_mode = (mode == NULL || strcmp(mode, "--replace") == 0) ? MM_REPLACE : MM_ADD;
if (toggle != NULL) { if (toggle != NULL) {
con_mark_toggle(current->con, mark); con_mark_toggle(current->con, mark, mark_mode);
} else { } else {
con_mark(current->con, mark); con_mark(current->con, mark, mark_mode);
} }
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
@ -1032,7 +1040,14 @@ void cmd_mark(I3_CMD, const char *mark, const char *toggle) {
* *
*/ */
void cmd_unmark(I3_CMD, const char *mark) { void cmd_unmark(I3_CMD, const char *mark) {
con_unmark(mark); if (match_is_empty(current_match)) {
con_unmark(NULL, mark);
} else {
owindow *current;
TAILQ_FOREACH(current, &owindows, owindows) {
con_unmark(current->con, mark);
}
}
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply // XXX: default reply for now, make this a better reply

106
src/con.c
View File

@ -55,6 +55,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) {
TAILQ_INIT(&(new->nodes_head)); TAILQ_INIT(&(new->nodes_head));
TAILQ_INIT(&(new->focus_head)); TAILQ_INIT(&(new->focus_head));
TAILQ_INIT(&(new->swallow_head)); TAILQ_INIT(&(new->swallow_head));
TAILQ_INIT(&(new->marks_head));
if (parent != NULL) if (parent != NULL)
con_attach(new, parent, false); con_attach(new, parent, false);
@ -512,27 +513,41 @@ Con *con_by_frame_id(xcb_window_t frame) {
Con *con_by_mark(const char *mark) { Con *con_by_mark(const char *mark) {
Con *con; Con *con;
TAILQ_FOREACH(con, &all_cons, all_cons) { TAILQ_FOREACH(con, &all_cons, all_cons) {
if (con->mark != NULL && strcmp(con->mark, mark) == 0) if (con_has_mark(con, mark))
return con; return con;
} }
return NULL; return NULL;
} }
/*
* Returns true if and only if the given containers holds the mark.
*
*/
bool con_has_mark(Con *con, const char *mark) {
mark_t *current;
TAILQ_FOREACH(current, &(con->marks_head), marks) {
if (strcmp(current->name, mark) == 0)
return true;
}
return false;
}
/* /*
* Toggles the mark on a container. * Toggles the mark on a container.
* If the container already has this mark, the mark is removed. * If the container already has this mark, the mark is removed.
* Otherwise, the mark is assigned to the container. * Otherwise, the mark is assigned to the container.
* *
*/ */
void con_mark_toggle(Con *con, const char *mark) { void con_mark_toggle(Con *con, const char *mark, mark_mode_t mode) {
assert(con != NULL); assert(con != NULL);
DLOG("Toggling mark \"%s\" on con = %p.\n", mark, con); DLOG("Toggling mark \"%s\" on con = %p.\n", mark, con);
if (con->mark != NULL && strcmp(con->mark, mark) == 0) { if (con_has_mark(con, mark)) {
con_unmark(mark); con_unmark(con, mark);
} else { } else {
con_mark(con, mark); con_mark(con, mark, mode);
} }
} }
@ -540,55 +555,76 @@ void con_mark_toggle(Con *con, const char *mark) {
* Assigns a mark to the container. * Assigns a mark to the container.
* *
*/ */
void con_mark(Con *con, const char *mark) { void con_mark(Con *con, const char *mark, mark_mode_t mode) {
assert(con != NULL); assert(con != NULL);
DLOG("Setting mark \"%s\" on con = %p.\n", mark, con); DLOG("Setting mark \"%s\" on con = %p.\n", mark, con);
FREE(con->mark); con_unmark(NULL, mark);
con->mark = sstrdup(mark); if (mode == MM_REPLACE) {
DLOG("Removing all existing marks on con = %p.\n", con);
mark_t *current;
TAILQ_FOREACH(current, &(con->marks_head), marks) {
con_unmark(con, current->name);
}
}
mark_t *new = scalloc(1, sizeof(mark_t));
new->name = sstrdup(mark);
TAILQ_INSERT_TAIL(&(con->marks_head), new, marks);
con->mark_changed = true; con->mark_changed = true;
DLOG("Clearing the mark from all other windows.\n");
Con *other;
TAILQ_FOREACH(other, &all_cons, all_cons) {
/* Skip the window we actually handled since we took care of it already. */
if (con == other)
continue;
if (other->mark != NULL && strcmp(other->mark, mark) == 0) {
FREE(other->mark);
other->mark_changed = true;
}
}
} }
/* /*
* If mark is NULL, this removes all existing marks. * Removes marks from containers.
* If con is NULL, all containers are considered.
* If name is NULL, this removes all existing marks.
* Otherwise, it will only remove the given mark (if it is present). * Otherwise, it will only remove the given mark (if it is present).
* *
*/ */
void con_unmark(const char *mark) { void con_unmark(Con *con, const char *name) {
Con *con; Con *current;
if (mark == NULL) { if (name == NULL) {
DLOG("Unmarking all containers.\n"); DLOG("Unmarking all containers.\n");
TAILQ_FOREACH(con, &all_cons, all_cons) { TAILQ_FOREACH(current, &all_cons, all_cons) {
if (con->mark == NULL) if (con != NULL && current != con)
continue; continue;
FREE(con->mark); if (TAILQ_EMPTY(&(current->marks_head)))
con->mark_changed = true; continue;
mark_t *mark;
while (!TAILQ_EMPTY(&(current->marks_head))) {
mark = TAILQ_FIRST(&(current->marks_head));
FREE(mark->name);
TAILQ_REMOVE(&(current->marks_head), mark, marks);
FREE(mark);
}
current->mark_changed = true;
} }
} else { } else {
DLOG("Removing mark \"%s\".\n", mark); DLOG("Removing mark \"%s\".\n", name);
con = con_by_mark(mark); current = (con == NULL) ? con_by_mark(name) : con;
if (con == NULL) { if (current == NULL) {
DLOG("No container found with this mark, so there is nothing to do.\n"); DLOG("No container found with this mark, so there is nothing to do.\n");
return; return;
} }
DLOG("Found mark on con = %p. Removing it now.\n", con); DLOG("Found mark on con = %p. Removing it now.\n", current);
FREE(con->mark); current->mark_changed = true;
con->mark_changed = true;
mark_t *mark;
TAILQ_FOREACH(mark, &(current->marks_head), marks) {
if (strcmp(mark->name, name) != 0)
continue;
FREE(mark->name);
TAILQ_REMOVE(&(current->marks_head), mark, marks);
FREE(mark);
break;
}
} }
} }

View File

@ -275,9 +275,16 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
ystr("urgent"); ystr("urgent");
y(bool, con->urgent); y(bool, con->urgent);
if (con->mark != NULL) { if (!TAILQ_EMPTY(&(con->marks_head))) {
ystr("mark"); ystr("marks");
ystr(con->mark); y(array_open);
mark_t *mark;
TAILQ_FOREACH(mark, &(con->marks_head), marks) {
ystr(mark->name);
}
y(array_close);
} }
ystr("focused"); ystr("focused");
@ -819,9 +826,12 @@ IPC_HANDLER(get_marks) {
y(array_open); y(array_open);
Con *con; Con *con;
TAILQ_FOREACH(con, &all_cons, all_cons) TAILQ_FOREACH(con, &all_cons, all_cons) {
if (con->mark != NULL) mark_t *mark;
ystr(con->mark); TAILQ_FOREACH(mark, &(con->marks_head), marks) {
ystr(mark->name);
}
}
y(array_close); y(array_close);

View File

@ -28,6 +28,7 @@ static bool parsing_deco_rect;
static bool parsing_window_rect; static bool parsing_window_rect;
static bool parsing_geometry; static bool parsing_geometry;
static bool parsing_focus; static bool parsing_focus;
static bool parsing_marks;
struct Match *current_swallow; struct Match *current_swallow;
/* This list is used for reordering the focus stack after parsing the 'focus' /* This list is used for reordering the focus stack after parsing the 'focus'
@ -159,12 +160,16 @@ static int json_end_map(void *ctx) {
static int json_end_array(void *ctx) { static int json_end_array(void *ctx) {
LOG("end of array\n"); LOG("end of array\n");
if (!parsing_swallows && !parsing_focus) { if (!parsing_swallows && !parsing_focus && !parsing_marks) {
con_fix_percent(json_node); con_fix_percent(json_node);
} }
if (parsing_swallows) { if (parsing_swallows) {
parsing_swallows = false; parsing_swallows = false;
} }
if (parsing_marks) {
parsing_marks = false;
}
if (parsing_focus) { if (parsing_focus) {
/* Clear the list of focus mappings */ /* Clear the list of focus mappings */
struct focus_mapping *mapping; struct focus_mapping *mapping;
@ -214,6 +219,9 @@ static int json_key(void *ctx, const unsigned char *val, size_t len) {
if (strcasecmp(last_key, "focus") == 0) if (strcasecmp(last_key, "focus") == 0)
parsing_focus = true; parsing_focus = true;
if (strcasecmp(last_key, "marks") == 0)
parsing_marks = true;
return 1; return 1;
} }
@ -234,6 +242,11 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) {
ELOG("swallow key %s unknown\n", last_key); ELOG("swallow key %s unknown\n", last_key);
} }
free(sval); free(sval);
} else if (parsing_marks) {
char *mark;
sasprintf(&mark, "%.*s", (int)len, val);
con_mark(json_node, mark, MM_ADD);
} else { } else {
if (strcasecmp(last_key, "name") == 0) { if (strcasecmp(last_key, "name") == 0) {
json_node->name = scalloc(len + 1, 1); json_node->name = scalloc(len + 1, 1);
@ -336,13 +349,12 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) {
LOG("Unhandled \"last_splitlayout\": %s\n", buf); LOG("Unhandled \"last_splitlayout\": %s\n", buf);
free(buf); free(buf);
} else if (strcasecmp(last_key, "mark") == 0) { } else if (strcasecmp(last_key, "mark") == 0) {
DLOG("Found deprecated key \"mark\".\n");
char *buf = NULL; char *buf = NULL;
sasprintf(&buf, "%.*s", (int)len, val); sasprintf(&buf, "%.*s", (int)len, val);
/* We unmark any containers using this mark to avoid duplicates. */ con_mark(json_node, buf, MM_REPLACE);
con_unmark(buf);
json_node->mark = buf;
} else if (strcasecmp(last_key, "floating") == 0) { } else if (strcasecmp(last_key, "floating") == 0) {
char *buf = NULL; char *buf = NULL;
sasprintf(&buf, "%.*s", (int)len, val); sasprintf(&buf, "%.*s", (int)len, val);
@ -589,6 +601,7 @@ void tree_append_json(Con *con, const char *filename, char **errormsg) {
parsing_window_rect = false; parsing_window_rect = false;
parsing_geometry = false; parsing_geometry = false;
parsing_focus = false; parsing_focus = false;
parsing_marks = false;
setlocale(LC_NUMERIC, "C"); setlocale(LC_NUMERIC, "C");
stat = yajl_parse(hand, (const unsigned char *)buf, n); stat = yajl_parse(hand, (const unsigned char *)buf, n);
if (stat != yajl_status_ok) { if (stat != yajl_status_ok) {

View File

@ -279,6 +279,11 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) {
} }
if (strcmp(ctype, "con_id") == 0) { if (strcmp(ctype, "con_id") == 0) {
if (strcmp(cvalue, "__focused__") == 0) {
match->con_id = focused;
return;
}
char *end; char *end;
long parsed = strtol(cvalue, &end, 10); long parsed = strtol(cvalue, &end, 10);
if (parsed == LONG_MIN || if (parsed == LONG_MIN ||

24
src/x.c
View File

@ -545,11 +545,24 @@ void x_draw_decoration(Con *con) {
int indent_px = (indent_level * 5) * indent_mult; int indent_px = (indent_level * 5) * indent_mult;
int mark_width = 0; int mark_width = 0;
if (config.show_marks && con->mark != NULL && (con->mark)[0] != '_') { if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) {
char *formatted_mark; char *formatted_mark = sstrdup("");
sasprintf(&formatted_mark, "[%s]", con->mark); bool had_visible_mark = false;
mark_t *mark;
TAILQ_FOREACH(mark, &(con->marks_head), marks) {
if (mark->name[0] == '_')
continue;
had_visible_mark = true;
char *buf;
sasprintf(&buf, "%s[%s]", formatted_mark, mark->name);
free(formatted_mark);
formatted_mark = buf;
}
if (had_visible_mark) {
i3String *mark = i3string_from_utf8(formatted_mark); i3String *mark = i3string_from_utf8(formatted_mark);
FREE(formatted_mark);
mark_width = predict_text_width(mark); mark_width = predict_text_width(mark);
draw_text(mark, parent->pixmap, parent->pm_gc, NULL, draw_text(mark, parent->pixmap, parent->pm_gc, NULL,
@ -559,6 +572,9 @@ void x_draw_decoration(Con *con) {
I3STRING_FREE(mark); I3STRING_FREE(mark);
} }
FREE(formatted_mark);
}
i3String *title = win->title_format == NULL ? win->name : window_parse_title_format(win); i3String *title = win->title_format == NULL ? win->name : window_parse_title_format(win);
draw_text(title, draw_text(title,
parent->pixmap, parent->pm_gc, NULL, parent->pixmap, parent->pm_gc, NULL,

View File

@ -398,7 +398,7 @@ EOT
my @nodes = @{get_ws($tmp)->{floating_nodes}}; my @nodes = @{get_ws($tmp)->{floating_nodes}};
cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); cmp_ok(@nodes, '==', 1, 'one floating container on this workspace');
is($nodes[0]->{nodes}[0]->{mark}, 'branded', "mark set (window_type = $atom)"); is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'branded' ], "mark set (window_type = $atom)");
exit_gracefully($pid); exit_gracefully($pid);
@ -431,7 +431,7 @@ EOT
my @nodes = @{get_ws($tmp)->{floating_nodes}}; my @nodes = @{get_ws($tmp)->{floating_nodes}};
cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); cmp_ok(@nodes, '==', 1, 'one floating container on this workspace');
is($nodes[0]->{nodes}[0]->{mark}, 'branded', "mark set (window_type = $atom)"); is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'branded' ], "mark set (window_type = $atom)");
exit_gracefully($pid); exit_gracefully($pid);
@ -454,7 +454,7 @@ $window = open_window;
@nodes = @{get_ws('trigger')->{floating_nodes}}; @nodes = @{get_ws('trigger')->{floating_nodes}};
cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); cmp_ok(@nodes, '==', 1, 'one floating container on this workspace');
is($nodes[0]->{nodes}[0]->{mark}, 'triggered', "mark set for workspace criterion"); is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'triggered' ], "mark set for workspace criterion");
exit_gracefully($pid); exit_gracefully($pid);

View File

@ -28,7 +28,7 @@ sub get_mark_for_window_on_workspace {
my ($ws, $con) = @_; my ($ws, $con) = @_;
my $current = first { $_->{window} == $con->{id} } @{get_ws_content($ws)}; my $current = first { $_->{window} == $con->{id} } @{get_ws_content($ws)};
return $current->{mark}; return $current->{marks};
} }
############################################################## ##############################################################
@ -41,7 +41,6 @@ cmd 'split h';
is_deeply(get_marks(), [], 'no marks set yet'); is_deeply(get_marks(), [], 'no marks set yet');
############################################################## ##############################################################
# 2: mark a con, check that it's marked, unmark it, check that # 2: mark a con, check that it's marked, unmark it, check that
############################################################## ##############################################################
@ -98,7 +97,7 @@ cmd 'mark important';
cmd 'focus left'; cmd 'focus left';
cmd 'mark important'; cmd 'mark important';
is(get_mark_for_window_on_workspace($tmp, $first), 'important', 'first container now has the mark'); is_deeply(get_mark_for_window_on_workspace($tmp, $first), [ 'important' ], 'first container now has the mark');
ok(!get_mark_for_window_on_workspace($tmp, $second), 'second container lost the mark'); ok(!get_mark_for_window_on_workspace($tmp, $second), 'second container lost the mark');
############################################################## ##############################################################
@ -116,7 +115,7 @@ ok(!get_mark_for_window_on_workspace($tmp, $con), 'container no longer has the m
$con = open_window; $con = open_window;
cmd 'mark --toggle important'; cmd 'mark --toggle important';
is(get_mark_for_window_on_workspace($tmp, $con), 'important', 'container now has the mark'); is_deeply(get_mark_for_window_on_workspace($tmp, $con), [ 'important' ], 'container now has the mark');
############################################################## ##############################################################
# 7: mark a con, toggle a different mark, check it is marked # 7: mark a con, toggle a different mark, check it is marked
@ -125,8 +124,8 @@ is(get_mark_for_window_on_workspace($tmp, $con), 'important', 'container now has
$con = open_window; $con = open_window;
cmd 'mark boring'; cmd 'mark boring';
cmd 'mark --toggle important'; cmd 'mark --replace --toggle important';
is(get_mark_for_window_on_workspace($tmp, $con), 'important', 'container has the most recent mark'); is_deeply(get_mark_for_window_on_workspace($tmp, $con), [ 'important' ], 'container has the most recent mark');
############################################################## ##############################################################
# 8: mark a con, toggle the mark on another con, # 8: mark a con, toggle the mark on another con,
@ -140,7 +139,7 @@ cmd 'mark important';
cmd 'focus left'; cmd 'focus left';
cmd 'mark --toggle important'; cmd 'mark --toggle important';
is(get_mark_for_window_on_workspace($tmp, $first), 'important', 'left container has the mark now'); is_deeply(get_mark_for_window_on_workspace($tmp, $first), [ 'important' ], 'left container has the mark now');
ok(!get_mark_for_window_on_workspace($tmp, $second), 'second containr no longer has the mark'); ok(!get_mark_for_window_on_workspace($tmp, $second), 'second containr no longer has the mark');
############################################################## ##############################################################

View File

@ -63,7 +63,7 @@ is($con->{window_properties}->{instance}, 'special',
# The mark `special_class_mark` is added in a `for_window` assignment in the # The mark `special_class_mark` is added in a `for_window` assignment in the
# config for testing purposes # config for testing purposes
is($con->{mark}, 'special_class_mark', is_deeply($con->{marks}, [ 'special_class_mark' ],
'A `for_window` assignment should run for a match when the window changes class'); 'A `for_window` assignment should run for a match when the window changes class');
change_window_class($win, "abcdefghijklmnopqrstuv\0abcd", 24); change_window_class($win, "abcdefghijklmnopqrstuv\0abcd", 24);

View File

@ -0,0 +1,117 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • http://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • http://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • http://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Tests for mark/unmark with multiple marks on a single window.
# Ticket: #2014
use i3test;
use List::Util qw(first);
my ($ws, $con, $first, $second);
sub get_marks {
return i3(get_socket_path())->get_marks->recv;
}
sub get_mark_for_window_on_workspace {
my ($ws, $con) = @_;
my $current = first { $_->{window} == $con->{id} } @{get_ws_content($ws)};
return $current->{marks};
}
###############################################################################
# Verify that multiple marks can be set on a window.
###############################################################################
$ws = fresh_workspace;
$con = open_window;
cmd 'mark --add A';
cmd 'mark --add B';
is_deeply(sort(get_marks()), [ 'A', 'B' ], 'both marks exist');
is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'B' ], 'both marks are on the same window');
cmd 'unmark';
###############################################################################
# Verify that toggling a mark can affect only the specified mark.
###############################################################################
$ws = fresh_workspace;
$con = open_window;
cmd 'mark A';
cmd 'mark --add --toggle B';
is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'B' ], 'both marks are on the same window');
cmd 'mark --add --toggle B';
is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A' ], 'only mark B has been removed');
cmd 'unmark';
###############################################################################
# Verify that unmarking a mark leaves other marks on the same window intact.
###############################################################################
$ws = fresh_workspace;
$con = open_window;
cmd 'mark --add A';
cmd 'mark --add B';
cmd 'mark --add C';
cmd 'unmark B';
is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'C' ], 'only mark B has been removed');
cmd 'unmark';
###############################################################################
# Verify that matching via mark works on windows with multiple marks.
###############################################################################
$ws = fresh_workspace;
$con = open_window;
cmd 'mark --add A';
cmd 'mark --add B';
open_window;
cmd '[con_mark=B] mark --add C';
is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'B', 'C' ], 'matching on a mark works with multiple marks');
cmd 'unmark';
###############################################################################
# Verify that "unmark" can be matched on the focused window.
###############################################################################
$ws = fresh_workspace;
$con = open_window;
cmd 'mark --add A';
cmd 'mark --add B';
open_window;
cmd 'mark --add C';
cmd 'mark --add D';
is_deeply(sort(get_marks()), [ 'A', 'B', 'C', 'D' ], 'all marks exist');
cmd '[con_id=__focused__] unmark';
is_deeply(sort(get_marks()), [ 'A', 'B' ], 'marks on the unfocused window still exist');
is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'B' ], 'matching on con_id=__focused__ works for unmark');
cmd 'unmark';
###############################################################################
done_testing;