Implement scratchpad functionality (see userguide)

This commit is contained in:
Michael Stapelberg 2011-12-21 23:15:32 +00:00
parent 311b9e24df
commit 08986a1798
12 changed files with 210 additions and 12 deletions

View File

@ -1456,6 +1456,40 @@ bindsym mod+Shift+w reload
bindsym mod+Shift+e exit 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 dont 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]] [[multi_monitor]]
== Multiple monitors == Multiple monitors

View File

@ -72,5 +72,6 @@
#include "regex.h" #include "regex.h"
#include "libi3.h" #include "libi3.h"
#include "startup.h" #include "startup.h"
#include "scratchpad.h"
#endif #endif

View File

@ -480,6 +480,12 @@ struct Con {
/** callbacks */ /** callbacks */
void(*on_remove_child)(Con *); void(*on_remove_child)(Con *);
enum {
SCRATCHPAD_NONE = 0,
SCRATCHPAD_FRESH = 1,
SCRATCHPAD_CHANGED = 2
} scratchpad_state;
}; };
#endif #endif

View File

@ -133,6 +133,8 @@ output { WS_STRING; return TOK_OUTPUT; }
focus { return TOK_FOCUS; } focus { return TOK_FOCUS; }
move { return TOK_MOVE; } move { return TOK_MOVE; }
open { return TOK_OPEN; } open { return TOK_OPEN; }
scratchpad { return TOK_SCRATCHPAD; }
show { return TOK_SHOW; }
split { return TOK_SPLIT; } split { return TOK_SPLIT; }
horizontal { return TOK_HORIZONTAL; } horizontal { return TOK_HORIZONTAL; }
vertical { return TOK_VERTICAL; } vertical { return TOK_VERTICAL; }

View File

@ -155,6 +155,8 @@ bool definitelyGreaterThan(float a, float b, float epsilon) {
%token TOK_OPEN "open" %token TOK_OPEN "open"
%token TOK_NEXT "next" %token TOK_NEXT "next"
%token TOK_PREV "prev" %token TOK_PREV "prev"
%token TOK_SCRATCHPAD "scratchpad"
%token TOK_SHOW "show"
%token TOK_SPLIT "split" %token TOK_SPLIT "split"
%token TOK_HORIZONTAL "horizontal" %token TOK_HORIZONTAL "horizontal"
%token TOK_VERTICAL "vertical" %token TOK_VERTICAL "vertical"
@ -385,6 +387,7 @@ operation:
| mark | mark
| resize | resize
| nop | nop
| scratchpad
| mode | mode
; ;
@ -636,6 +639,11 @@ workspace:
} }
| TOK_WORKSPACE STR | 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); printf("should switch to workspace %s\n", $2);
Con *ws = con_get_workspace(focused); Con *ws = con_get_workspace(focused);
@ -798,6 +806,11 @@ move:
} }
| TOK_MOVE TOK_WORKSPACE STR | 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; owindow *current;
/* Error out early to not create a non-existing workspace (in /* Error out early to not create a non-existing workspace (in
@ -855,7 +868,7 @@ move:
{ {
owindow *current; owindow *current;
printf("should move window to output %s", $3); printf("should move window to output %s\n", $3);
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
@ -880,8 +893,10 @@ move:
output = get_output_by_name($3); output = get_output_by_name($3);
free($3); free($3);
if (!output) if (!output) {
printf("No such output found.\n");
break; break;
}
/* get visible workspace on output */ /* get visible workspace on output */
Con *ws = NULL; Con *ws = NULL;
@ -896,6 +911,20 @@ move:
tree_render(); 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: 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: resize:
TOK_RESIZE resize_way direction resize_px resize_tiling TOK_RESIZE resize_way direction resize_px resize_tiling
{ {

View File

@ -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 /* 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). */ * 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 /* 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 * con focused. Otherwise, we leave the focus on the current workspace as we

View File

@ -166,6 +166,19 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
break; 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"); ystr("percent");
if (con->percent == 0.0) if (con->percent == 0.0)
y(null); y(null);
@ -330,6 +343,8 @@ IPC_HANDLER(get_workspaces) {
Con *output; Con *output;
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
if (output->name[0] == '_' && output->name[1] == '_')
continue;
Con *ws; Con *ws;
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
assert(ws->type == CT_WORKSPACE); assert(ws->type == CT_WORKSPACE);

View File

@ -221,6 +221,9 @@ void render_con(Con *con, bool render_fullscreen) {
} }
if (con->layout == L_OUTPUT) { if (con->layout == L_OUTPUT) {
/* Skip i3-internal outputs */
if (con->name[0] == '_' && con->name[1] == '_')
return;
render_l_output(con); render_l_output(con);
} else if (con->type == CT_ROOT) { } else if (con->type == CT_ROOT) {
Con *output; Con *output;
@ -234,6 +237,8 @@ void render_con(Con *con, bool render_fullscreen) {
* windows/containers so that they overlap on another output. */ * windows/containers so that they overlap on another output. */
DLOG("Rendering floating windows:\n"); DLOG("Rendering floating windows:\n");
TAILQ_FOREACH(output, &(con->nodes_head), nodes) { TAILQ_FOREACH(output, &(con->nodes_head), nodes) {
if (output->name[0] == '_' && output->name[1] == '_')
continue;
/* Get the active workspace of that output */ /* Get the active workspace of that output */
Con *content = output_get_content(output); Con *content = output_get_content(output);
Con *workspace = TAILQ_FIRST(&(content->focus_head)); Con *workspace = TAILQ_FIRST(&(content->focus_head));

View File

@ -15,7 +15,48 @@ struct Con *focused;
struct all_cons_head all_cons = TAILQ_HEAD_INITIALIZER(all_cons); 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) { 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)); Con *ws = TAILQ_FIRST(&(out->nodes_head));
printf("ws = %p\n", ws); 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; return true;
} }
@ -66,6 +118,8 @@ void tree_init(xcb_get_geometry_reply_t *geometry) {
geometry->width, geometry->width,
geometry->height geometry->height
}; };
_create___i3();
} }
/* /*

View File

@ -185,6 +185,10 @@ static void workspace_reassign_sticky(Con *con) {
static void _workspace_show(Con *workspace, bool changed_num_workspaces) { static void _workspace_show(Con *workspace, bool changed_num_workspaces) {
Con *current, *old = NULL; 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 /* disable fullscreen for the other workspaces and get the workspace we are
* currently on. */ * currently on. */
TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) { TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) {
@ -278,7 +282,10 @@ Con* workspace_next() {
next = TAILQ_NEXT(current, nodes); next = TAILQ_NEXT(current, nodes);
} else { } else {
/* If currently a numbered workspace, find next numbered workspace. */ /* 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)) { NODES_FOREACH(output_get_content(output)) {
if (child->type != CT_WORKSPACE) if (child->type != CT_WORKSPACE)
continue; continue;
@ -290,12 +297,16 @@ Con* workspace_next() {
if (current->num < child->num && (!next || child->num < next->num)) if (current->num < child->num && (!next || child->num < next->num))
next = child; next = child;
} }
}
} }
/* Find next named workspace. */ /* Find next named workspace. */
if (!next) { if (!next) {
bool found_current = false; 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)) { NODES_FOREACH(output_get_content(output)) {
if (child->type != CT_WORKSPACE) if (child->type != CT_WORKSPACE)
continue; continue;
@ -306,17 +317,22 @@ Con* workspace_next() {
goto workspace_next_end; goto workspace_next_end;
} }
} }
}
} }
/* Find first workspace. */ /* Find first workspace. */
if (!next) { 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)) { NODES_FOREACH(output_get_content(output)) {
if (child->type != CT_WORKSPACE) if (child->type != CT_WORKSPACE)
continue; continue;
if (!next || (child->num != -1 && child->num < next->num)) if (!next || (child->num != -1 && child->num < next->num))
next = child; next = child;
} }
}
} }
workspace_next_end: workspace_next_end:
return next; return next;
@ -338,7 +354,10 @@ Con* workspace_prev() {
prev = NULL; prev = NULL;
} else { } else {
/* If numbered workspace, find previous numbered workspace. */ /* 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)) { NODES_FOREACH_REVERSE(output_get_content(output)) {
if (child->type != CT_WORKSPACE || child->num == -1) if (child->type != CT_WORKSPACE || child->num == -1)
continue; continue;
@ -348,12 +367,16 @@ Con* workspace_prev() {
if (current->num > child->num && (!prev || child->num > prev->num)) if (current->num > child->num && (!prev || child->num > prev->num))
prev = child; prev = child;
} }
}
} }
/* Find previous named workspace. */ /* Find previous named workspace. */
if (!prev) { if (!prev) {
bool found_current = false; 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)) { NODES_FOREACH_REVERSE(output_get_content(output)) {
if (child->type != CT_WORKSPACE) if (child->type != CT_WORKSPACE)
continue; continue;
@ -364,17 +387,22 @@ Con* workspace_prev() {
goto workspace_prev_end; goto workspace_prev_end;
} }
} }
}
} }
/* Find last workspace. */ /* Find last workspace. */
if (!prev) { 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)) { NODES_FOREACH_REVERSE(output_get_content(output)) {
if (child->type != CT_WORKSPACE) if (child->type != CT_WORKSPACE)
continue; continue;
if (!prev || child->num > prev->num) if (!prev || child->num > prev->num)
prev = child; prev = child;
} }
}
} }
workspace_prev_end: workspace_prev_end:

View File

@ -256,6 +256,7 @@ sub get_workspace_names {
my @outputs = @{$tree->{nodes}}; my @outputs = @{$tree->{nodes}};
my @cons; my @cons;
for my $output (@outputs) { for my $output (@outputs) {
next if $output->{name} eq '__i3';
# get the first CT_CON of each output # get the first CT_CON of each output
my $content = first { $_->{type} == 2 } @{$output->{nodes}}; my $content = first { $_->{type} == 2 } @{$output->{nodes}};
@cons = (@cons, @{$content->{nodes}}); @cons = (@cons, @{$content->{nodes}});
@ -336,11 +337,11 @@ sub get_dock_clients {
@{$output->{nodes}}); @{$output->{nodes}});
} elsif ($which eq 'top') { } elsif ($which eq 'top') {
my $first = first { $_->{type} == 5 } @{$output->{nodes}}; my $first = first { $_->{type} == 5 } @{$output->{nodes}};
@docked = (@docked, @{$first->{nodes}}); @docked = (@docked, @{$first->{nodes}}) if defined($first);
} elsif ($which eq 'bottom') { } elsif ($which eq 'bottom') {
my @matching = grep { $_->{type} == 5 } @{$output->{nodes}}; my @matching = grep { $_->{type} == 5 } @{$output->{nodes}};
my $last = $matching[-1]; my $last = $matching[-1];
@docked = (@docked, @{$last->{nodes}}); @docked = (@docked, @{$last->{nodes}}) if defined($last);
} }
} }
return @docked; return @docked;
@ -361,6 +362,7 @@ sub focused_ws {
my @outputs = @{$tree->{nodes}}; my @outputs = @{$tree->{nodes}};
my @cons; my @cons;
for my $output (@outputs) { for my $output (@outputs) {
next if $output->{name} eq '__i3';
# get the first CT_CON of each output # get the first CT_CON of each output
my $content = first { $_->{type} == 2 } @{$output->{nodes}}; my $content = first { $_->{type} == 2 } @{$output->{nodes}};
my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}}; my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}};

View File

@ -46,6 +46,7 @@ my $expected = {
swallows => $ignore, swallows => $ignore,
percent => undef, percent => undef,
layout => 'default', layout => 'default',
scratchpad_state => 'none',
focus => $ignore, focus => $ignore,
focused => JSON::XS::false, focused => JSON::XS::false,
urgent => JSON::XS::false, urgent => JSON::XS::false,