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
#include "regex.h"
#include "libi3.h"
#include "startup.h"
+#include "scratchpad.h"
#endif
/** callbacks */
void(*on_remove_child)(Con *);
+
+ enum {
+ SCRATCHPAD_NONE = 0,
+ SCRATCHPAD_FRESH = 1,
+ SCRATCHPAD_CHANGED = 2
+ } scratchpad_state;
};
#endif
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; }
%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"
| mark
| resize
| nop
+ | scratchpad
| mode
;
}
| 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);
}
| 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
{
owindow *current;
- printf("should move window to output %s", $3);
+ printf("should move window to output %s\n", $3);
HANDLE_EMPTY_MATCH;
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;
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:
}
;
+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
{
/* 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
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);
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);
}
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;
* 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));
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) {
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;
}
geometry->width,
geometry->height
};
+
+ _create___i3();
}
/*
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) {
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;
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;
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;
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;
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;
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:
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}});
@{$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;
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}};
swallows => $ignore,
percent => undef,
layout => 'default',
+ scratchpad_state => 'none',
focus => $ignore,
focused => JSON::XS::false,
urgent => JSON::XS::false,