]> git.sur5r.net Git - i3/i3/commitdiff
Implement scratchpad functionality (see userguide)
authorMichael Stapelberg <michael@stapelberg.de>
Wed, 21 Dec 2011 23:15:32 +0000 (23:15 +0000)
committerMichael Stapelberg <michael@stapelberg.de>
Wed, 21 Dec 2011 23:15:32 +0000 (23:15 +0000)
12 files changed:
docs/userguide
include/all.h
include/data.h
src/cmdparse.l
src/cmdparse.y
src/con.c
src/ipc.c
src/render.c
src/tree.c
src/workspace.c
testcases/lib/i3test.pm
testcases/t/116-nestedcons.t

index 7d57a064d8b228590bf2e0f2b90ce871f276f8b6..5785eca9f4ce0dbc3d6e8076fc1156dbe69c1eda 100644 (file)
@@ -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
index 8ae4e0a29ac26f8f24cb65a8d1fc1413e458ce43..72c8c8f42ba978be2545ae61230c4c796cef899f 100644 (file)
@@ -72,5 +72,6 @@
 #include "regex.h"
 #include "libi3.h"
 #include "startup.h"
+#include "scratchpad.h"
 
 #endif
index 40fffbfc4cebf8c71d2a4d18f38003968083357f..ad50e81fe96c5ac0fd9045d9f6ab8e1436cc6903 100644 (file)
@@ -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
index 5a727658aced1493281c617025fb05b29d23a253..1431ec357cc3616f4bc1c4388b906eafe25625b2 100644 (file)
@@ -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; }
index 7f86c8db1d7dd8965a0573cf5bb612f274994b84..7425c31ddd344577f556b31051aaa6b155c239d0 100644 (file)
@@ -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(&current_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
     {
index 7b5cc499468e5594f463a726b3a4864ee832e281..799148ef706b33e25d6402b18a4af50d38757645 100644 (file)
--- 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
index 60f456c3425274399557e9590ab4f438e2f25445..5143695bc29bfeb7da4a58da5e7f39aecdf4e734 100644 (file)
--- 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);
index 759c351fefe9364f5e50c719958d080531a6d4b9..b55c089ae4c5c53b42a2658d0725f0ce4160b441 100644 (file)
@@ -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));
index 53993b998164362ad4a64911d42c58cfbed415f0..bdfceac0c0bccd42eb928a7f3f62fa624cd67dab 100644 (file)
@@ -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();
 }
 
 /*
index 1ccae967d4a76152a71489a7e9eda87b779fb1e4..b8fb73a6d35107450218e4f689dd20927910b746 100644 (file)
@@ -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:
index 0f4b97c6b65601234ef95b24dbd5fb1fca5633d3..87758777af2e32eb73d9435c203f681522e75352 100644 (file)
@@ -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}};
index f6d4848dd8afd57bcf9464c68a68fbf9f694a163..d3ed9969b46c6e1cc9765e464716d98787b6e5bf 100644 (file)
@@ -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,