Impliment i3-style marks

This commit adds three commands to sway: `show_marks`, `mark` and
`unmark`. Marks are displayed right-aligned in the window border as i3
does. Marks may be found using criteria.

Fixes #1007
This commit is contained in:
Calvin Lee 2017-04-02 14:38:33 -06:00
parent ab7570d311
commit 2445d27960
12 changed files with 189 additions and 0 deletions

View file

@ -126,6 +126,7 @@ sway_cmd cmd_ipc;
sway_cmd cmd_kill; sway_cmd cmd_kill;
sway_cmd cmd_layout; sway_cmd cmd_layout;
sway_cmd cmd_log_colors; sway_cmd cmd_log_colors;
sway_cmd cmd_mark;
sway_cmd cmd_mode; sway_cmd cmd_mode;
sway_cmd cmd_mouse_warping; sway_cmd cmd_mouse_warping;
sway_cmd cmd_move; sway_cmd cmd_move;
@ -140,12 +141,14 @@ sway_cmd cmd_resize;
sway_cmd cmd_scratchpad; sway_cmd cmd_scratchpad;
sway_cmd cmd_seamless_mouse; sway_cmd cmd_seamless_mouse;
sway_cmd cmd_set; sway_cmd cmd_set;
sway_cmd cmd_show_marks;
sway_cmd cmd_smart_gaps; sway_cmd cmd_smart_gaps;
sway_cmd cmd_split; sway_cmd cmd_split;
sway_cmd cmd_splith; sway_cmd cmd_splith;
sway_cmd cmd_splitt; sway_cmd cmd_splitt;
sway_cmd cmd_splitv; sway_cmd cmd_splitv;
sway_cmd cmd_sticky; sway_cmd cmd_sticky;
sway_cmd cmd_unmark;
sway_cmd cmd_workspace; sway_cmd cmd_workspace;
sway_cmd cmd_ws_auto_back_and_forth; sway_cmd cmd_ws_auto_back_and_forth;
sway_cmd cmd_workspace_layout; sway_cmd cmd_workspace_layout;

View file

@ -275,6 +275,7 @@ struct sway_config {
bool reading; bool reading;
bool auto_back_and_forth; bool auto_back_and_forth;
bool seamless_mouse; bool seamless_mouse;
bool show_marks;
bool edge_gaps; bool edge_gaps;
bool smart_gaps; bool smart_gaps;

View file

@ -165,6 +165,11 @@ struct sway_container {
* Number of slave groups (e.g. columns) in auto layouts. * Number of slave groups (e.g. columns) in auto layouts.
*/ */
size_t nb_slave_groups; size_t nb_slave_groups;
/**
* Marks applied to the container, list_t of char*.
*/
list_t *marks;
}; };
enum visibility_mask { enum visibility_mask {

View file

@ -1,8 +1,11 @@
#define _XOPEN_SOURCE 500
#include <wlc/wlc-render.h> #include <wlc/wlc-render.h>
#include <cairo/cairo.h> #include <cairo/cairo.h>
#include <pango/pangocairo.h> #include <pango/pangocairo.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <string.h>
#include <strings.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include "sway/border.h" #include "sway/border.h"
#include "sway/container.h" #include "sway/container.h"
@ -190,6 +193,26 @@ static void render_title_bar(swayc_t *view, cairo_t *cr, struct wlc_geometry *b,
cairo_set_source_u32(cr, colors->text); cairo_set_source_u32(cr, colors->text);
pango_printf(cr, config->font, 1, false, "%s", view->name); pango_printf(cr, config->font, 1, false, "%s", view->name);
} }
// Marks
if (config->show_marks && view->marks) {
int total_len = 0;
for(int i = view->marks->length - 1; i >= 0; --i) {
char *mark = (char *)view->marks->items[i];
if (*mark != '_') {
int width, height;
get_text_size(cr, config->font, &width, &height, 1, false, "[%s]", mark);
total_len += width;
if ((int)tb->size.w + x - (total_len + 2) < x + 2) {
break;
} else {
cairo_move_to(cr, (int)tb->size.w + x - (total_len + 2), y + 2);
cairo_set_source_u32(cr, colors->text);
pango_printf(cr, config->font, 1, false, "[%s]", mark);
}
}
}
}
// titlebars has a border all around for tabbed layouts // titlebars has a border all around for tabbed layouts
if (view->parent->layout == L_TABBED) { if (view->parent->layout == L_TABBED) {

View file

@ -190,6 +190,7 @@ static struct cmd_handler handlers[] = {
{ "kill", cmd_kill }, { "kill", cmd_kill },
{ "layout", cmd_layout }, { "layout", cmd_layout },
{ "log_colors", cmd_log_colors }, { "log_colors", cmd_log_colors },
{ "mark", cmd_mark },
{ "mode", cmd_mode }, { "mode", cmd_mode },
{ "mouse_warping", cmd_mouse_warping }, { "mouse_warping", cmd_mouse_warping },
{ "move", cmd_move }, { "move", cmd_move },
@ -203,12 +204,14 @@ static struct cmd_handler handlers[] = {
{ "scratchpad", cmd_scratchpad }, { "scratchpad", cmd_scratchpad },
{ "seamless_mouse", cmd_seamless_mouse }, { "seamless_mouse", cmd_seamless_mouse },
{ "set", cmd_set }, { "set", cmd_set },
{ "show_marks", cmd_show_marks },
{ "smart_gaps", cmd_smart_gaps }, { "smart_gaps", cmd_smart_gaps },
{ "split", cmd_split }, { "split", cmd_split },
{ "splith", cmd_splith }, { "splith", cmd_splith },
{ "splitt", cmd_splitt }, { "splitt", cmd_splitt },
{ "splitv", cmd_splitv }, { "splitv", cmd_splitv },
{ "sticky", cmd_sticky }, { "sticky", cmd_sticky },
{ "unmark", cmd_unmark },
{ "workspace", cmd_workspace }, { "workspace", cmd_workspace },
{ "workspace_auto_back_and_forth", cmd_ws_auto_back_and_forth }, { "workspace_auto_back_and_forth", cmd_ws_auto_back_and_forth },
{ "workspace_layout", cmd_workspace_layout }, { "workspace_layout", cmd_workspace_layout },

74
sway/commands/mark.c Normal file
View file

@ -0,0 +1,74 @@
#include <string.h>
#include <strings.h>
#include <stdbool.h>
#include "sway/commands.h"
#include "list.h"
#include "stringop.h"
struct cmd_results *cmd_mark(int argc, char **argv) {
struct cmd_results *error = NULL;
if (config->reading) return cmd_results_new(CMD_FAILURE, "mark", "Can't be used in config file.");
if ((error = checkarg(argc, "floating", EXPECTED_AT_LEAST, 1))) {
return error;
}
swayc_t *view = get_focused_container(&root_container);
bool add = false;
bool toggle = false;
if (strcmp(argv[0], "--add") == 0) {
--argc; ++argv;
add = true;
} else if (strcmp(argv[0], "--replace") == 0) {
--argc; ++argv;
}
if (argc && strcmp(argv[0], "--toggle") == 0) {
--argc; ++argv;
toggle = true;
}
if (argc) {
char *mark = join_args(argv, argc);
if (view->marks) {
if (add) {
int index;
if ((index = list_seq_find(view->marks, (int (*)(const void *, const void *))strcmp, mark)) != -1) {
if (toggle) {
free(view->marks->items[index]);
list_del(view->marks, index);
if (0 == view->marks->length) {
list_free(view->marks);
view->marks = NULL;
}
}
free(mark);
} else {
list_add(view->marks, mark);
}
} else {
if (toggle && list_seq_find(view->marks, (int (*)(const void *, const void *))strcmp, mark) != -1) {
// Delete the list
list_foreach(view->marks, free);
list_free(view->marks);
view->marks = NULL;
} else {
// Delete and replace with a new list
list_foreach(view->marks, free);
list_free(view->marks);
view->marks = create_list();
list_add(view->marks, mark);
}
}
} else {
view->marks = create_list();
list_add(view->marks, mark);
}
} else {
return cmd_results_new(CMD_FAILURE, "mark",
"Expected 'mark [--add|--replace] [--toggle] <mark>'");
}
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
}

View file

@ -0,0 +1,13 @@
#include <string.h>
#include <strings.h>
#include "sway/commands.h"
struct cmd_results *cmd_show_marks(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "show_marks", EXPECTED_EQUAL_TO, 1))) {
return error;
}
config->show_marks = !strcasecmp(argv[0], "on");
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
}

31
sway/commands/unmark.c Normal file
View file

@ -0,0 +1,31 @@
#include <string.h>
#include <strings.h>
#include "sway/commands.h"
#include "list.h"
#include "stringop.h"
struct cmd_results *cmd_unmark(int argc, char **argv) {
swayc_t *view = get_focused_container(&root_container);
if (view->marks) {
if (argc) {
char *mark = join_args(argv, argc);
int index;
if ((index = list_seq_find(view->marks, (int (*)(const void *, const void *))strcmp, mark)) != -1) {
free(view->marks->items[index]);
list_del(view->marks, index);
if (view->marks->length == 0) {
list_free(view->marks);
view->marks = NULL;
}
}
free(mark);
} else {
list_foreach(view->marks, free);
list_free(view->marks);
view->marks = NULL;
}
}
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
}

View file

@ -329,6 +329,7 @@ static void config_defaults(struct sway_config *config) {
config->auto_back_and_forth = false; config->auto_back_and_forth = false;
config->seamless_mouse = true; config->seamless_mouse = true;
config->reading = false; config->reading = false;
config->show_marks = true;
config->edge_gaps = true; config->edge_gaps = true;
config->smart_gaps = false; config->smart_gaps = false;

View file

@ -61,6 +61,10 @@ static void free_swayc(swayc_t *cont) {
} }
list_free(cont->floating); list_free(cont->floating);
} }
if (cont->marks) {
list_foreach(cont->marks, free);
list_free(cont->marks);
}
if (cont->parent) { if (cont->parent) {
remove_child(cont); remove_child(cont);
} }

View file

@ -12,6 +12,7 @@
enum criteria_type { // *must* keep in sync with criteria_strings[] enum criteria_type { // *must* keep in sync with criteria_strings[]
CRIT_CLASS, CRIT_CLASS,
CRIT_CON_MARK,
CRIT_ID, CRIT_ID,
CRIT_INSTANCE, CRIT_INSTANCE,
CRIT_TITLE, CRIT_TITLE,
@ -25,6 +26,7 @@ enum criteria_type { // *must* keep in sync with criteria_strings[]
// this *must* match the ordering in criteria_type enum // this *must* match the ordering in criteria_type enum
static const char * const criteria_strings[] = { static const char * const criteria_strings[] = {
"class", "class",
"con_mark",
"id", "id",
"instance", "instance",
"title", "title",
@ -243,6 +245,10 @@ ect_cleanup:
return error; return error;
} }
int regex_cmp(const char *item, const regex_t *regex) {
return regexec(regex, item, 0, NULL, 0);
}
// test a single view if it matches list of criteria tokens (all of them). // test a single view if it matches list of criteria tokens (all of them).
static bool criteria_test(swayc_t *cont, list_t *tokens) { static bool criteria_test(swayc_t *cont, list_t *tokens) {
if (cont->type != C_VIEW) { if (cont->type != C_VIEW) {
@ -264,6 +270,11 @@ static bool criteria_test(swayc_t *cont, list_t *tokens) {
matches++; matches++;
} }
break; break;
case CRIT_CON_MARK:
if (crit->regex && cont->marks && (list_seq_find(cont->marks, (int (*)(const void *, const void *))regex_cmp, crit->regex) != -1)) {
++matches;
}
break;
case CRIT_ID: case CRIT_ID:
if (!cont->app_id) { if (!cont->app_id) {
// ignore // ignore

View file

@ -316,6 +316,14 @@ The default colors are:
If smart_gaps are _on_ then gaps will only be enabled if a workspace has more If smart_gaps are _on_ then gaps will only be enabled if a workspace has more
than one child container. than one child container.
**mark** <--add|--replace> <--toggle> <identifier>::
Marks are arbitrary labels that can be used to identify certain windows and
then jump to them at a later time. By default, the **mark** command sets
_identifier_ as the only mark on a window. By specifying _--add_, mark will
add _identifier_ to the list of current marks. If _--toggle_ is specified mark
will remove _identifier_ if it is already a label. Marks may be found by using
a criteria. See the **Criteria** section below.
**mode** <mode_name>:: **mode** <mode_name>::
Switches to the given mode_name. The default mode is simply _default_. To Switches to the given mode_name. The default mode is simply _default_. To
create a new mode in config append _{_ to this command, the following lines create a new mode in config append _{_ to this command, the following lines
@ -368,6 +376,15 @@ The default colors are:
be configured with perfectly aligned adjacent positions for this option to be configured with perfectly aligned adjacent positions for this option to
have any effect. have any effect.
**show_marks** <on|off>::
If **show_marks** is on then marks will be showed in the window decoration.
However, any mark that starts with an underscore will not be drawn even if the
option is on. The default option is _on_.
**unmark** <identifier>::
**Unmark** will remove _identifier_ from the list of current marks on a window. If
no _identifier_ is specified then **unmark** will remove all marks.
**workspace** [number] <name>:: **workspace** [number] <name>::
Switches to the specified workspace. The string "number" is optional. The Switches to the specified workspace. The string "number" is optional. The
worspace _name_, if unquoted, may not contain the string "output", as sway worspace _name_, if unquoted, may not contain the string "output", as sway
@ -416,6 +433,9 @@ Currently supported attributes:
is _focused_ then the window class must be the same as that of the currently is _focused_ then the window class must be the same as that of the currently
focused window. focused window.
**con_mark**::
Compare against the window marks. Can be a regular expression.
**id**:: **id**::
Compare value against the app id. Can be a regular expression. Compare value against the app id. Can be a regular expression.