From 08986a17983c45781e59515b528710413b953984 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 21 Dec 2011 23:15:32 +0000 Subject: [PATCH] Implement scratchpad functionality (see userguide) --- docs/userguide | 34 ++++++++++++++++++++++ include/all.h | 1 + include/data.h | 6 ++++ src/cmdparse.l | 2 ++ src/cmdparse.y | 53 ++++++++++++++++++++++++++++++++-- src/con.c | 3 +- src/ipc.c | 15 ++++++++++ src/render.c | 5 ++++ src/tree.c | 56 +++++++++++++++++++++++++++++++++++- src/workspace.c | 40 ++++++++++++++++++++++---- testcases/lib/i3test.pm | 6 ++-- testcases/t/116-nestedcons.t | 1 + 12 files changed, 210 insertions(+), 12 deletions(-) diff --git a/docs/userguide b/docs/userguide index 7d57a064..5785eca9 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1456,6 +1456,40 @@ bindsym mod+Shift+w reload bindsym mod+Shift+e exit ---------------------------- +=== Scratchpad + +There are two commands to use any existing window as scratchpad window. +move +scratchpad+ will move a window to the scratchpad workspace. This will make it +invisible until you show it again. There is no way to open that workspace. +Instead, when using +scratchpad show+, the window will be shown again, as a +floating window, centered on your current workspace (using +scratchpad show+ on +a visible scratchpad window will make it hidden again, so you can have a +keybinding to toggle). + +As the name indicates, this is useful for having a window with your favorite +editor always at hand. However, you can also use this for other permanently +running applications which you don’t want to see all the time: Your music +player, alsamixer, maybe even your mail client…? + +*Syntax*: +--------------- +move scratchpad + +scratchpad show +--------------- + +*Examples*: +------------------------------------------------ +# Make the currently focused window a scratchpad +bindsym mod+Shift+minus move scratchpad + +# Show the first scratchpad window +bindsym mod+minus scratchpad show + +# Show the sup-mail scratchpad window, if any. +bindsym mod4+s [title="^Sup ::"] scratchpad show +------------------------------------------------ + [[multi_monitor]] == Multiple monitors diff --git a/include/all.h b/include/all.h index 8ae4e0a2..72c8c8f4 100644 --- a/include/all.h +++ b/include/all.h @@ -72,5 +72,6 @@ #include "regex.h" #include "libi3.h" #include "startup.h" +#include "scratchpad.h" #endif diff --git a/include/data.h b/include/data.h index 40fffbfc..ad50e81f 100644 --- a/include/data.h +++ b/include/data.h @@ -480,6 +480,12 @@ struct Con { /** callbacks */ void(*on_remove_child)(Con *); + + enum { + SCRATCHPAD_NONE = 0, + SCRATCHPAD_FRESH = 1, + SCRATCHPAD_CHANGED = 2 + } scratchpad_state; }; #endif diff --git a/src/cmdparse.l b/src/cmdparse.l index 5a727658..1431ec35 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -133,6 +133,8 @@ output { WS_STRING; return TOK_OUTPUT; } focus { return TOK_FOCUS; } move { return TOK_MOVE; } open { return TOK_OPEN; } +scratchpad { return TOK_SCRATCHPAD; } +show { return TOK_SHOW; } split { return TOK_SPLIT; } horizontal { return TOK_HORIZONTAL; } vertical { return TOK_VERTICAL; } diff --git a/src/cmdparse.y b/src/cmdparse.y index 7f86c8db..7425c31d 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -155,6 +155,8 @@ bool definitelyGreaterThan(float a, float b, float epsilon) { %token TOK_OPEN "open" %token TOK_NEXT "next" %token TOK_PREV "prev" +%token TOK_SCRATCHPAD "scratchpad" +%token TOK_SHOW "show" %token TOK_SPLIT "split" %token TOK_HORIZONTAL "horizontal" %token TOK_VERTICAL "vertical" @@ -385,6 +387,7 @@ operation: | mark | resize | nop + | scratchpad | mode ; @@ -636,6 +639,11 @@ workspace: } | TOK_WORKSPACE STR { + if (strncasecmp($2, "__i3_", strlen("__i3_")) == 0) { + printf("You cannot switch to the i3 internal workspaces.\n"); + break; + } + printf("should switch to workspace %s\n", $2); Con *ws = con_get_workspace(focused); @@ -798,6 +806,11 @@ move: } | TOK_MOVE TOK_WORKSPACE STR { + if (strncasecmp($3, "__i3_", strlen("__i3_")) == 0) { + printf("You cannot switch to the i3 internal workspaces.\n"); + break; + } + owindow *current; /* Error out early to not create a non-existing workspace (in @@ -855,7 +868,7 @@ move: { owindow *current; - printf("should move window to output %s", $3); + printf("should move window to output %s\n", $3); HANDLE_EMPTY_MATCH; @@ -880,8 +893,10 @@ move: output = get_output_by_name($3); free($3); - if (!output) + if (!output) { + printf("No such output found.\n"); break; + } /* get visible workspace on output */ Con *ws = NULL; @@ -896,6 +911,20 @@ move: tree_render(); } + | TOK_MOVE TOK_SCRATCHPAD + { + printf("should move window to scratchpad\n"); + owindow *current; + + HANDLE_EMPTY_MATCH; + + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + scratchpad_move(current->con); + } + + tree_render(); + } ; append_layout: @@ -969,6 +998,26 @@ nop: } ; +scratchpad: + TOK_SCRATCHPAD TOK_SHOW + { + printf("should show scratchpad window\n"); + owindow *current; + + if (match_is_empty(¤t_match)) { + scratchpad_show(NULL); + } else { + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + scratchpad_show(current->con); + } + } + + tree_render(); + } + ; + + resize: TOK_RESIZE resize_way direction resize_px resize_tiling { diff --git a/src/con.c b/src/con.c index 7b5cc499..799148ef 100644 --- a/src/con.c +++ b/src/con.c @@ -657,7 +657,8 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool /* 7: focus the con on the target workspace (the X focus is only updated by * calling tree_render(), so for the "real" focus this is a no-op). */ - con_focus(con_descend_focused(con)); + if (workspace->name[0] != '_' || workspace->name[1] != '_') + con_focus(con_descend_focused(con)); /* 8: when moving to a visible workspace on a different output, we keep the * con focused. Otherwise, we leave the focus on the current workspace as we diff --git a/src/ipc.c b/src/ipc.c index 60f456c3..5143695b 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -166,6 +166,19 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { break; } + ystr("scratchpad_state"); + switch (con->scratchpad_state) { + case SCRATCHPAD_NONE: + ystr("none"); + break; + case SCRATCHPAD_FRESH: + ystr("fresh"); + break; + case SCRATCHPAD_CHANGED: + ystr("changed"); + break; + } + ystr("percent"); if (con->percent == 0.0) y(null); @@ -330,6 +343,8 @@ IPC_HANDLER(get_workspaces) { Con *output; TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + if (output->name[0] == '_' && output->name[1] == '_') + continue; Con *ws; TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { assert(ws->type == CT_WORKSPACE); diff --git a/src/render.c b/src/render.c index 759c351f..b55c089a 100644 --- a/src/render.c +++ b/src/render.c @@ -221,6 +221,9 @@ void render_con(Con *con, bool render_fullscreen) { } if (con->layout == L_OUTPUT) { + /* Skip i3-internal outputs */ + if (con->name[0] == '_' && con->name[1] == '_') + return; render_l_output(con); } else if (con->type == CT_ROOT) { Con *output; @@ -234,6 +237,8 @@ void render_con(Con *con, bool render_fullscreen) { * windows/containers so that they overlap on another output. */ DLOG("Rendering floating windows:\n"); TAILQ_FOREACH(output, &(con->nodes_head), nodes) { + if (output->name[0] == '_' && output->name[1] == '_') + continue; /* Get the active workspace of that output */ Con *content = output_get_content(output); Con *workspace = TAILQ_FIRST(&(content->focus_head)); diff --git a/src/tree.c b/src/tree.c index 53993b99..bdfceac0 100644 --- a/src/tree.c +++ b/src/tree.c @@ -15,7 +15,48 @@ struct Con *focused; struct all_cons_head all_cons = TAILQ_HEAD_INITIALIZER(all_cons); /* - * Loads tree from ~/.i3/_restart.json (used for in-place restarts). + * Create the pseudo-output __i3. Output-independent workspaces such as + * __i3_scratch will live there. + * + */ +static Con *_create___i3() { + Con *__i3 = con_new(croot, NULL); + FREE(__i3->name); + __i3->name = sstrdup("__i3"); + __i3->type = CT_OUTPUT; + __i3->layout = L_OUTPUT; + con_fix_percent(croot); + x_set_name(__i3, "[i3 con] pseudo-output __i3"); + /* For retaining the correct position/size of a scratchpad window, the + * dimensions of the real outputs should be multiples of the __i3 + * pseudo-output. */ + __i3->rect.width = 1280; + __i3->rect.height = 1024; + + /* Add a content container. */ + DLOG("adding main content container\n"); + Con *content = con_new(NULL, NULL); + content->type = CT_CON; + FREE(content->name); + content->name = sstrdup("content"); + + x_set_name(content, "[i3 con] content __i3"); + con_attach(content, __i3, false); + + /* Attach the __i3_scratch workspace. */ + Con *ws = con_new(NULL, NULL); + ws->type = CT_WORKSPACE; + ws->num = -1; + ws->name = sstrdup("__i3_scratch"); + con_attach(ws, content, false); + x_set_name(ws, "[i3 con] workspace __i3_scratch"); + ws->fullscreen_mode = CF_OUTPUT; + + return __i3; +} + +/* + * Loads tree from 'path' (used for in-place restarts). * */ bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) { @@ -47,6 +88,17 @@ bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) { Con *ws = TAILQ_FIRST(&(out->nodes_head)); printf("ws = %p\n", ws); + /* For in-place restarting into v4.2, we need to make sure the new + * pseudo-output __i3 is present. */ + if (strcmp(out->name, "__i3") != 0) { + DLOG("Adding pseudo-output __i3 during inplace restart\n"); + Con *__i3 = _create___i3(); + /* Ensure that it is the first output, other places in the code make + * that assumption. */ + TAILQ_REMOVE(&(croot->nodes_head), __i3, nodes); + TAILQ_INSERT_HEAD(&(croot->nodes_head), __i3, nodes); + } + return true; } @@ -66,6 +118,8 @@ void tree_init(xcb_get_geometry_reply_t *geometry) { geometry->width, geometry->height }; + + _create___i3(); } /* diff --git a/src/workspace.c b/src/workspace.c index 1ccae967..b8fb73a6 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -185,6 +185,10 @@ static void workspace_reassign_sticky(Con *con) { static void _workspace_show(Con *workspace, bool changed_num_workspaces) { Con *current, *old = NULL; + /* safe-guard against showing i3-internal workspaces like __i3_scratch */ + if (workspace->name[0] == '_' && workspace->name[1] == '_') + return; + /* disable fullscreen for the other workspaces and get the workspace we are * currently on. */ TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) { @@ -278,7 +282,10 @@ Con* workspace_next() { next = TAILQ_NEXT(current, nodes); } else { /* If currently a numbered workspace, find next numbered workspace. */ - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + /* Skip outputs starting with __, they are internal. */ + if (output->name[0] == '_' && output->name[1] == '_') + continue; NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; @@ -290,12 +297,16 @@ Con* workspace_next() { if (current->num < child->num && (!next || child->num < next->num)) next = child; } + } } /* Find next named workspace. */ if (!next) { bool found_current = false; - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + /* Skip outputs starting with __, they are internal. */ + if (output->name[0] == '_' && output->name[1] == '_') + continue; NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; @@ -306,17 +317,22 @@ Con* workspace_next() { goto workspace_next_end; } } + } } /* Find first workspace. */ if (!next) { - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + /* Skip outputs starting with __, they are internal. */ + if (output->name[0] == '_' && output->name[1] == '_') + continue; NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; if (!next || (child->num != -1 && child->num < next->num)) next = child; } + } } workspace_next_end: return next; @@ -338,7 +354,10 @@ Con* workspace_prev() { prev = NULL; } else { /* If numbered workspace, find previous numbered workspace. */ - TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) + TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { + /* Skip outputs starting with __, they are internal. */ + if (output->name[0] == '_' && output->name[1] == '_') + continue; NODES_FOREACH_REVERSE(output_get_content(output)) { if (child->type != CT_WORKSPACE || child->num == -1) continue; @@ -348,12 +367,16 @@ Con* workspace_prev() { if (current->num > child->num && (!prev || child->num > prev->num)) prev = child; } + } } /* Find previous named workspace. */ if (!prev) { bool found_current = false; - TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) + TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { + /* Skip outputs starting with __, they are internal. */ + if (output->name[0] == '_' && output->name[1] == '_') + continue; NODES_FOREACH_REVERSE(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; @@ -364,17 +387,22 @@ Con* workspace_prev() { goto workspace_prev_end; } } + } } /* Find last workspace. */ if (!prev) { - TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) + TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { + /* Skip outputs starting with __, they are internal. */ + if (output->name[0] == '_' && output->name[1] == '_') + continue; NODES_FOREACH_REVERSE(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; if (!prev || child->num > prev->num) prev = child; } + } } workspace_prev_end: diff --git a/testcases/lib/i3test.pm b/testcases/lib/i3test.pm index 0f4b97c6..87758777 100644 --- a/testcases/lib/i3test.pm +++ b/testcases/lib/i3test.pm @@ -256,6 +256,7 @@ sub get_workspace_names { my @outputs = @{$tree->{nodes}}; my @cons; for my $output (@outputs) { + next if $output->{name} eq '__i3'; # get the first CT_CON of each output my $content = first { $_->{type} == 2 } @{$output->{nodes}}; @cons = (@cons, @{$content->{nodes}}); @@ -336,11 +337,11 @@ sub get_dock_clients { @{$output->{nodes}}); } elsif ($which eq 'top') { my $first = first { $_->{type} == 5 } @{$output->{nodes}}; - @docked = (@docked, @{$first->{nodes}}); + @docked = (@docked, @{$first->{nodes}}) if defined($first); } elsif ($which eq 'bottom') { my @matching = grep { $_->{type} == 5 } @{$output->{nodes}}; my $last = $matching[-1]; - @docked = (@docked, @{$last->{nodes}}); + @docked = (@docked, @{$last->{nodes}}) if defined($last); } } return @docked; @@ -361,6 +362,7 @@ sub focused_ws { my @outputs = @{$tree->{nodes}}; my @cons; for my $output (@outputs) { + next if $output->{name} eq '__i3'; # get the first CT_CON of each output my $content = first { $_->{type} == 2 } @{$output->{nodes}}; my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}}; diff --git a/testcases/t/116-nestedcons.t b/testcases/t/116-nestedcons.t index f6d4848d..d3ed9969 100644 --- a/testcases/t/116-nestedcons.t +++ b/testcases/t/116-nestedcons.t @@ -46,6 +46,7 @@ my $expected = { swallows => $ignore, percent => undef, layout => 'default', + scratchpad_state => 'none', focus => $ignore, focused => JSON::XS::false, urgent => JSON::XS::false, -- 2.39.5