2012-08-12 12:18:43 +02:00
|
|
|
|
#undef I3__FILE__
|
|
|
|
|
#define I3__FILE__ "workspace.c"
|
2009-07-28 13:55:09 +02:00
|
|
|
|
/*
|
2010-04-13 16:43:08 +02:00
|
|
|
|
* vim:ts=4:sw=4:expandtab
|
2009-07-28 13:55:09 +02:00
|
|
|
|
*
|
|
|
|
|
* i3 - an improved dynamic tiling window manager
|
2015-04-04 02:17:56 +02:00
|
|
|
|
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
2009-07-28 13:55:09 +02:00
|
|
|
|
*
|
2011-10-23 00:40:02 +02:00
|
|
|
|
* workspace.c: Modifying workspaces, accessing them, moving containers to
|
|
|
|
|
* workspaces.
|
2009-07-28 13:55:09 +02:00
|
|
|
|
*
|
|
|
|
|
*/
|
2010-03-27 15:25:51 +01:00
|
|
|
|
#include "all.h"
|
2014-12-20 05:43:47 +01:00
|
|
|
|
#include "yajl_utils.h"
|
2010-03-27 15:25:51 +01:00
|
|
|
|
|
2011-10-18 00:17:56 +02:00
|
|
|
|
/* Stores a copy of the name of the last used workspace for the workspace
|
|
|
|
|
* back-and-forth switching. */
|
|
|
|
|
static char *previous_workspace_name = NULL;
|
|
|
|
|
|
2015-09-14 09:28:42 +02:00
|
|
|
|
/* NULL-terminated list of workspace names (in order) extracted from
|
|
|
|
|
* keybindings. */
|
|
|
|
|
static char **binding_workspace_names = NULL;
|
|
|
|
|
|
Introduce splith/splitv layouts, remove orientation
With this commit, the "default" layout is replaced by the splith and
splitv layouts. splith is equivalent to default with orientation
horizontal and splitv is equivalent to default with orientation
vertical.
The "split h" and "split v" commands continue to work as before, they
split the current container and you will end up in a split container
with layout splith (after "split h") or splitv (after "split v").
To change a splith container into a splitv container, use either "layout
splitv" or "layout toggle split". The latter command is used in the
default config as mod+l (previously "layout default"). In case you have
"layout default" in your config file, it is recommended to just replace
it by "layout toggle split", which will work as "layout default" did
before when pressing it once, but toggle between horizontal/vertical
when pressing it repeatedly.
The rationale behind this commit is that it’s cleaner to have all
parameters that influence how windows are rendered in the layout itself
rather than having a special parameter in combination with only one
layout. This enables us to change existing split containers in all cases
without breaking existing features (see ticket #464). Also, users should
feel more confident about whether they are actually splitting or just
changing an existing split container now.
As a nice side-effect, this commit brings back the "layout toggle"
feature we once had in i3 version 3 (see the userguide).
AFAIK, it is safe to use in-place restart to upgrade into versions
after this commit (switching to an older version will break your layout,
though).
Fixes #464
2012-08-04 03:04:00 +02:00
|
|
|
|
/*
|
|
|
|
|
* Sets ws->layout to splith/splitv if default_orientation was specified in the
|
|
|
|
|
* configfile. Otherwise, it uses splith/splitv depending on whether the output
|
|
|
|
|
* is higher than wide.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
static void _workspace_apply_default_orientation(Con *ws) {
|
|
|
|
|
/* If default_orientation is set to NO_ORIENTATION we determine
|
|
|
|
|
* orientation depending on output resolution. */
|
|
|
|
|
if (config.default_orientation == NO_ORIENTATION) {
|
|
|
|
|
Con *output = con_get_output(ws);
|
|
|
|
|
ws->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH;
|
2015-04-26 20:23:08 +02:00
|
|
|
|
ws->rect = output->rect;
|
Introduce splith/splitv layouts, remove orientation
With this commit, the "default" layout is replaced by the splith and
splitv layouts. splith is equivalent to default with orientation
horizontal and splitv is equivalent to default with orientation
vertical.
The "split h" and "split v" commands continue to work as before, they
split the current container and you will end up in a split container
with layout splith (after "split h") or splitv (after "split v").
To change a splith container into a splitv container, use either "layout
splitv" or "layout toggle split". The latter command is used in the
default config as mod+l (previously "layout default"). In case you have
"layout default" in your config file, it is recommended to just replace
it by "layout toggle split", which will work as "layout default" did
before when pressing it once, but toggle between horizontal/vertical
when pressing it repeatedly.
The rationale behind this commit is that it’s cleaner to have all
parameters that influence how windows are rendered in the layout itself
rather than having a special parameter in combination with only one
layout. This enables us to change existing split containers in all cases
without breaking existing features (see ticket #464). Also, users should
feel more confident about whether they are actually splitting or just
changing an existing split container now.
As a nice side-effect, this commit brings back the "layout toggle"
feature we once had in i3 version 3 (see the userguide).
AFAIK, it is safe to use in-place restart to upgrade into versions
after this commit (switching to an older version will break your layout,
though).
Fixes #464
2012-08-04 03:04:00 +02:00
|
|
|
|
DLOG("Auto orientation. Workspace size set to (%d,%d), setting layout to %d.\n",
|
|
|
|
|
output->rect.width, output->rect.height, ws->layout);
|
|
|
|
|
} else {
|
|
|
|
|
ws->layout = (config.default_orientation == HORIZ) ? L_SPLITH : L_SPLITV;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-09-27 15:20:47 +02:00
|
|
|
|
/*
|
|
|
|
|
* Returns a pointer to the workspace with the given number (starting at 0),
|
|
|
|
|
* creating the workspace if necessary (by allocating the necessary amount of
|
|
|
|
|
* memory and initializing the data structures correctly).
|
|
|
|
|
*
|
|
|
|
|
*/
|
2011-03-14 00:56:04 +01:00
|
|
|
|
Con *workspace_get(const char *num, bool *created) {
|
2011-05-14 22:13:29 +02:00
|
|
|
|
Con *output, *workspace = NULL;
|
2010-04-13 16:43:08 +02:00
|
|
|
|
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
|
|
|
|
|
GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num));
|
2011-05-14 22:13:29 +02:00
|
|
|
|
|
|
|
|
|
if (workspace == NULL) {
|
|
|
|
|
LOG("Creating new workspace \"%s\"\n", num);
|
|
|
|
|
/* unless an assignment is found, we will create this workspace on the current output */
|
|
|
|
|
output = con_get_output(focused);
|
|
|
|
|
/* look for assignments */
|
|
|
|
|
struct Workspace_Assignment *assignment;
|
2010-04-13 16:43:08 +02:00
|
|
|
|
|
2014-05-18 05:36:58 +02:00
|
|
|
|
/* We set workspace->num to the number if this workspace’s name begins
|
|
|
|
|
* with a positive number. Otherwise it’s a named ws and num will be
|
|
|
|
|
* -1. */
|
|
|
|
|
long parsed_num = ws_name_to_number(num);
|
|
|
|
|
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
2014-05-18 05:36:58 +02:00
|
|
|
|
if (strcmp(assignment->name, num) == 0) {
|
|
|
|
|
DLOG("Found workspace name assignment to output \"%s\"\n", assignment->output);
|
|
|
|
|
GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
|
|
|
|
|
break;
|
2014-06-15 19:07:02 +02:00
|
|
|
|
} else if (parsed_num != -1 && name_is_digits(assignment->name) && ws_name_to_number(assignment->name) == parsed_num) {
|
2014-05-18 05:36:58 +02:00
|
|
|
|
DLOG("Found workspace number assignment to output \"%s\"\n", assignment->output);
|
|
|
|
|
GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
|
|
|
|
|
}
|
2010-03-27 15:25:51 +01:00
|
|
|
|
}
|
2014-05-18 05:36:58 +02:00
|
|
|
|
|
2011-02-21 01:55:36 +01:00
|
|
|
|
Con *content = output_get_content(output);
|
|
|
|
|
LOG("got output %p with content %p\n", output, content);
|
2010-11-21 23:35:49 +01:00
|
|
|
|
/* We need to attach this container after setting its type. con_attach
|
|
|
|
|
* will handle CT_WORKSPACEs differently */
|
2011-06-02 17:21:38 +02:00
|
|
|
|
workspace = con_new(NULL, NULL);
|
2010-11-14 16:41:46 +01:00
|
|
|
|
char *name;
|
2011-10-23 14:16:56 +02:00
|
|
|
|
sasprintf(&name, "[i3 con] workspace %s", num);
|
2010-11-14 16:41:46 +01:00
|
|
|
|
x_set_name(workspace, name);
|
|
|
|
|
free(name);
|
2010-05-31 00:11:11 +02:00
|
|
|
|
workspace->type = CT_WORKSPACE;
|
2011-01-04 22:38:33 +01:00
|
|
|
|
FREE(workspace->name);
|
|
|
|
|
workspace->name = sstrdup(num);
|
2012-09-16 22:53:41 +02:00
|
|
|
|
workspace->workspace_layout = config.default_layout;
|
2014-05-18 05:36:58 +02:00
|
|
|
|
workspace->num = parsed_num;
|
2010-11-21 23:35:49 +01:00
|
|
|
|
LOG("num = %d\n", workspace->num);
|
2011-03-16 11:56:51 +01:00
|
|
|
|
|
Introduce splith/splitv layouts, remove orientation
With this commit, the "default" layout is replaced by the splith and
splitv layouts. splith is equivalent to default with orientation
horizontal and splitv is equivalent to default with orientation
vertical.
The "split h" and "split v" commands continue to work as before, they
split the current container and you will end up in a split container
with layout splith (after "split h") or splitv (after "split v").
To change a splith container into a splitv container, use either "layout
splitv" or "layout toggle split". The latter command is used in the
default config as mod+l (previously "layout default"). In case you have
"layout default" in your config file, it is recommended to just replace
it by "layout toggle split", which will work as "layout default" did
before when pressing it once, but toggle between horizontal/vertical
when pressing it repeatedly.
The rationale behind this commit is that it’s cleaner to have all
parameters that influence how windows are rendered in the layout itself
rather than having a special parameter in combination with only one
layout. This enables us to change existing split containers in all cases
without breaking existing features (see ticket #464). Also, users should
feel more confident about whether they are actually splitting or just
changing an existing split container now.
As a nice side-effect, this commit brings back the "layout toggle"
feature we once had in i3 version 3 (see the userguide).
AFAIK, it is safe to use in-place restart to upgrade into versions
after this commit (switching to an older version will break your layout,
though).
Fixes #464
2012-08-04 03:04:00 +02:00
|
|
|
|
workspace->parent = content;
|
|
|
|
|
_workspace_apply_default_orientation(workspace);
|
2011-03-16 11:56:51 +01:00
|
|
|
|
|
2011-02-20 23:43:03 +01:00
|
|
|
|
con_attach(workspace, content, false);
|
2010-03-12 21:05:05 +01:00
|
|
|
|
|
2014-12-20 05:43:47 +01:00
|
|
|
|
ipc_send_workspace_event("init", workspace, NULL);
|
2014-06-16 08:50:47 +02:00
|
|
|
|
ewmh_update_number_of_desktops();
|
2014-06-23 23:44:24 +02:00
|
|
|
|
ewmh_update_desktop_names();
|
2014-06-22 21:20:14 +02:00
|
|
|
|
ewmh_update_desktop_viewport();
|
Handle the EWMH atom _NET_WM_DESKTOP.
We already claim _NET_WM_DESKTOP support in _NET_SUPPORTED since around 2009,
but haven't actually done anything with it. However, especially pagers like
gnome-panel rely on this property to be updated and many tools, like GTK, want
to use the corresponding client messages to make a window sticky, move it
around etc.
This patch implements full support according to the EWMH spec. This means:
* We set the property on all windows when managing it.
* We keep the property updated on all windows at all times.
* We read and respect the property upon managing a window if it was set before
mapping the window.
* We react to client messages for it.
* We remove the property on withdrawn windows.
Note that the special value 0xFFFFFFFF, according to the spec, means that the
window shall be shown on all workspaces. We do this by making it sticky and
float it. This shows it on all workspaces at least on the output it is on.
Furthermore, the spec gives us the freedom to ignore _NET_WM_DESKTOP when
managing a window if we have good reason to. In our case, we give window
swallowing a higher priority since the user would likely expect that and we
want to keep placeholder windows only around for as long as we have to.
However, we do prioritize this property over, for example, startup
notifications.
fixes #2153
fixes #1507
fixes #938
2016-01-11 20:53:26 +01:00
|
|
|
|
ewmh_update_wm_desktop();
|
2011-03-14 00:56:04 +01:00
|
|
|
|
if (created != NULL)
|
|
|
|
|
*created = true;
|
2014-06-15 19:07:02 +02:00
|
|
|
|
} else if (created != NULL) {
|
2011-03-14 00:56:04 +01:00
|
|
|
|
*created = false;
|
2010-04-13 16:43:08 +02:00
|
|
|
|
}
|
2009-09-27 14:00:54 +02:00
|
|
|
|
|
2010-04-13 16:43:08 +02:00
|
|
|
|
return workspace;
|
2009-09-27 14:00:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
2012-01-14 23:09:09 +01:00
|
|
|
|
/*
|
2015-09-14 09:28:42 +02:00
|
|
|
|
* Extracts workspace names from keybindings (e.g. “web” from “bindsym $mod+1
|
|
|
|
|
* workspace web”), so that when an output needs a workspace, i3 can start with
|
|
|
|
|
* the first configured one. Needs to be called before reorder_bindings() so
|
|
|
|
|
* that the config-file order is used, not the i3-internal order.
|
2012-01-14 23:09:09 +01:00
|
|
|
|
*
|
|
|
|
|
*/
|
2015-09-14 09:28:42 +02:00
|
|
|
|
void extract_workspace_names_from_bindings(void) {
|
2012-01-14 23:09:09 +01:00
|
|
|
|
Binding *bind;
|
2015-09-14 09:28:42 +02:00
|
|
|
|
int n = 0;
|
|
|
|
|
if (binding_workspace_names != NULL) {
|
|
|
|
|
for (int i = 0; binding_workspace_names[i] != NULL; i++) {
|
|
|
|
|
free(binding_workspace_names[i]);
|
|
|
|
|
}
|
|
|
|
|
FREE(binding_workspace_names);
|
|
|
|
|
}
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(bind, bindings, bindings) {
|
2012-01-14 23:09:09 +01:00
|
|
|
|
DLOG("binding with command %s\n", bind->command);
|
|
|
|
|
if (strlen(bind->command) < strlen("workspace ") ||
|
|
|
|
|
strncasecmp(bind->command, "workspace", strlen("workspace")) != 0)
|
|
|
|
|
continue;
|
|
|
|
|
DLOG("relevant command = %s\n", bind->command);
|
2014-10-01 22:50:48 +02:00
|
|
|
|
const char *target = bind->command + strlen("workspace ");
|
2015-06-09 22:14:56 +02:00
|
|
|
|
while (*target == ' ' || *target == '\t')
|
2013-05-26 13:24:55 +02:00
|
|
|
|
target++;
|
2012-01-14 23:09:09 +01:00
|
|
|
|
/* We check if this is the workspace
|
2012-05-16 06:24:16 +02:00
|
|
|
|
* next/prev/next_on_output/prev_on_output/back_and_forth/number command.
|
2012-01-14 23:09:09 +01:00
|
|
|
|
* Beware: The workspace names "next", "prev", "next_on_output",
|
2012-06-03 19:44:42 +02:00
|
|
|
|
* "prev_on_output", "number", "back_and_forth" and "current" are OK,
|
|
|
|
|
* so we check before stripping the double quotes */
|
2012-01-14 23:09:09 +01:00
|
|
|
|
if (strncasecmp(target, "next", strlen("next")) == 0 ||
|
|
|
|
|
strncasecmp(target, "prev", strlen("prev")) == 0 ||
|
|
|
|
|
strncasecmp(target, "next_on_output", strlen("next_on_output")) == 0 ||
|
|
|
|
|
strncasecmp(target, "prev_on_output", strlen("prev_on_output")) == 0 ||
|
2012-05-16 06:24:16 +02:00
|
|
|
|
strncasecmp(target, "number", strlen("number")) == 0 ||
|
2012-06-03 19:44:42 +02:00
|
|
|
|
strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 ||
|
|
|
|
|
strncasecmp(target, "current", strlen("current")) == 0)
|
2012-01-14 23:09:09 +01:00
|
|
|
|
continue;
|
2014-10-01 22:50:48 +02:00
|
|
|
|
char *target_name = parse_string(&target, false);
|
|
|
|
|
if (target_name == NULL)
|
|
|
|
|
continue;
|
|
|
|
|
if (strncasecmp(target_name, "__", strlen("__")) == 0) {
|
2014-05-03 15:02:51 +02:00
|
|
|
|
LOG("Cannot create workspace \"%s\". Names starting with __ are i3-internal.\n", target);
|
2014-10-01 22:50:48 +02:00
|
|
|
|
free(target_name);
|
2014-04-30 11:12:22 +02:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2015-09-14 09:28:42 +02:00
|
|
|
|
DLOG("Saving workspace name \"%s\"\n", target_name);
|
|
|
|
|
|
2015-09-14 10:22:43 +02:00
|
|
|
|
binding_workspace_names = srealloc(binding_workspace_names, ++n * sizeof(char *));
|
|
|
|
|
binding_workspace_names[n - 1] = target_name;
|
2015-09-14 09:28:42 +02:00
|
|
|
|
}
|
2015-09-14 10:22:43 +02:00
|
|
|
|
binding_workspace_names = srealloc(binding_workspace_names, ++n * sizeof(char *));
|
|
|
|
|
binding_workspace_names[n - 1] = NULL;
|
2015-09-14 09:28:42 +02:00
|
|
|
|
}
|
2012-01-14 23:09:09 +01:00
|
|
|
|
|
2015-09-14 09:28:42 +02:00
|
|
|
|
/*
|
|
|
|
|
* Returns a pointer to a new workspace in the given output. The workspace
|
|
|
|
|
* is created attached to the tree hierarchy through the given content
|
|
|
|
|
* container.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
Con *create_workspace_on_output(Output *output, Con *content) {
|
|
|
|
|
/* add a workspace to this output */
|
|
|
|
|
Con *out, *current;
|
|
|
|
|
char *name;
|
|
|
|
|
bool exists = true;
|
|
|
|
|
Con *ws = con_new(NULL, NULL);
|
|
|
|
|
ws->type = CT_WORKSPACE;
|
|
|
|
|
|
|
|
|
|
/* try the configured workspace bindings first to find a free name */
|
|
|
|
|
for (int n = 0; binding_workspace_names[n] != NULL; n++) {
|
|
|
|
|
char *target_name = binding_workspace_names[n];
|
2012-01-14 23:09:09 +01:00
|
|
|
|
/* Ensure that this workspace is not assigned to a different output —
|
|
|
|
|
* otherwise we would create it, then move it over to its output, then
|
|
|
|
|
* find a new workspace, etc… */
|
|
|
|
|
bool assigned = false;
|
|
|
|
|
struct Workspace_Assignment *assignment;
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
2015-09-14 09:28:42 +02:00
|
|
|
|
if (strcmp(assignment->name, target_name) != 0 ||
|
2012-01-14 23:09:09 +01:00
|
|
|
|
strcmp(assignment->output, output->name) == 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
assigned = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (assigned)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
current = NULL;
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
|
2015-09-14 09:28:42 +02:00
|
|
|
|
GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, target_name));
|
2012-01-14 23:09:09 +01:00
|
|
|
|
exists = (current != NULL);
|
|
|
|
|
if (!exists) {
|
2015-09-14 09:28:42 +02:00
|
|
|
|
ws->name = sstrdup(target_name);
|
2012-01-14 23:09:09 +01:00
|
|
|
|
/* Set ->num to the number of the workspace, if the name actually
|
|
|
|
|
* is a number or starts with a number */
|
2014-06-22 17:12:51 +02:00
|
|
|
|
ws->num = ws_name_to_number(ws->name);
|
2012-01-14 23:09:09 +01:00
|
|
|
|
LOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (exists) {
|
|
|
|
|
/* get the next unused workspace number */
|
|
|
|
|
DLOG("Getting next unused workspace by number\n");
|
|
|
|
|
int c = 0;
|
|
|
|
|
while (exists) {
|
|
|
|
|
c++;
|
|
|
|
|
|
2013-08-07 17:20:22 +02:00
|
|
|
|
ws->num = c;
|
2012-01-14 23:09:09 +01:00
|
|
|
|
|
|
|
|
|
current = NULL;
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
|
|
|
|
|
GREP_FIRST(current, output_get_content(out), child->num == ws->num);
|
2012-01-14 23:09:09 +01:00
|
|
|
|
exists = (current != NULL);
|
|
|
|
|
|
2013-08-07 17:20:22 +02:00
|
|
|
|
DLOG("result for ws %d: exists = %d\n", c, exists);
|
2012-01-14 23:09:09 +01:00
|
|
|
|
}
|
2013-08-07 17:20:22 +02:00
|
|
|
|
sasprintf(&(ws->name), "%d", c);
|
2012-01-14 23:09:09 +01:00
|
|
|
|
}
|
|
|
|
|
con_attach(ws, content, false);
|
|
|
|
|
|
|
|
|
|
sasprintf(&name, "[i3 con] workspace %s", ws->name);
|
|
|
|
|
x_set_name(ws, name);
|
|
|
|
|
free(name);
|
|
|
|
|
|
|
|
|
|
ws->fullscreen_mode = CF_OUTPUT;
|
|
|
|
|
|
2012-10-03 03:52:57 +02:00
|
|
|
|
ws->workspace_layout = config.default_layout;
|
Introduce splith/splitv layouts, remove orientation
With this commit, the "default" layout is replaced by the splith and
splitv layouts. splith is equivalent to default with orientation
horizontal and splitv is equivalent to default with orientation
vertical.
The "split h" and "split v" commands continue to work as before, they
split the current container and you will end up in a split container
with layout splith (after "split h") or splitv (after "split v").
To change a splith container into a splitv container, use either "layout
splitv" or "layout toggle split". The latter command is used in the
default config as mod+l (previously "layout default"). In case you have
"layout default" in your config file, it is recommended to just replace
it by "layout toggle split", which will work as "layout default" did
before when pressing it once, but toggle between horizontal/vertical
when pressing it repeatedly.
The rationale behind this commit is that it’s cleaner to have all
parameters that influence how windows are rendered in the layout itself
rather than having a special parameter in combination with only one
layout. This enables us to change existing split containers in all cases
without breaking existing features (see ticket #464). Also, users should
feel more confident about whether they are actually splitting or just
changing an existing split container now.
As a nice side-effect, this commit brings back the "layout toggle"
feature we once had in i3 version 3 (see the userguide).
AFAIK, it is safe to use in-place restart to upgrade into versions
after this commit (switching to an older version will break your layout,
though).
Fixes #464
2012-08-04 03:04:00 +02:00
|
|
|
|
_workspace_apply_default_orientation(ws);
|
2012-01-14 23:09:09 +01:00
|
|
|
|
|
|
|
|
|
return ws;
|
|
|
|
|
}
|
|
|
|
|
|
2009-08-02 22:31:52 +02:00
|
|
|
|
/*
|
|
|
|
|
* Returns true if the workspace is currently visible. Especially important for
|
|
|
|
|
* multi-monitor environments, as they can have multiple currenlty active
|
|
|
|
|
* workspaces.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2010-11-12 17:34:13 +01:00
|
|
|
|
bool workspace_is_visible(Con *ws) {
|
|
|
|
|
Con *output = con_get_output(ws);
|
|
|
|
|
if (output == NULL)
|
|
|
|
|
return false;
|
2011-06-10 18:27:20 +02:00
|
|
|
|
Con *fs = con_get_fullscreen_con(output, CF_OUTPUT);
|
2010-11-12 17:34:13 +01:00
|
|
|
|
LOG("workspace visible? fs = %p, ws = %p\n", fs, ws);
|
|
|
|
|
return (fs == ws);
|
2009-08-02 22:31:52 +02:00
|
|
|
|
}
|
2010-03-27 15:25:51 +01:00
|
|
|
|
|
2010-09-01 18:11:01 +02:00
|
|
|
|
/*
|
|
|
|
|
* XXX: we need to clean up all this recursive walking code.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) {
|
|
|
|
|
Con *current;
|
|
|
|
|
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
|
2010-09-01 18:11:01 +02:00
|
|
|
|
if (current != exclude &&
|
|
|
|
|
current->sticky_group != NULL &&
|
|
|
|
|
current->window != NULL &&
|
|
|
|
|
strcmp(current->sticky_group, sticky_group) == 0)
|
|
|
|
|
return current;
|
|
|
|
|
|
|
|
|
|
Con *recurse = _get_sticky(current, sticky_group, exclude);
|
|
|
|
|
if (recurse != NULL)
|
|
|
|
|
return recurse;
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(current, &(con->floating_head), floating_windows) {
|
2010-09-01 18:11:01 +02:00
|
|
|
|
if (current != exclude &&
|
|
|
|
|
current->sticky_group != NULL &&
|
|
|
|
|
current->window != NULL &&
|
|
|
|
|
strcmp(current->sticky_group, sticky_group) == 0)
|
|
|
|
|
return current;
|
|
|
|
|
|
|
|
|
|
Con *recurse = _get_sticky(current, sticky_group, exclude);
|
|
|
|
|
if (recurse != NULL)
|
|
|
|
|
return recurse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Reassigns all child windows in sticky containers. Called when the user
|
|
|
|
|
* changes workspaces.
|
|
|
|
|
*
|
|
|
|
|
* XXX: what about sticky containers which contain containers?
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
static void workspace_reassign_sticky(Con *con) {
|
|
|
|
|
Con *current;
|
|
|
|
|
/* 1: go through all containers */
|
|
|
|
|
|
|
|
|
|
/* handle all children and floating windows of this node */
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
|
2010-09-01 18:11:01 +02:00
|
|
|
|
if (current->sticky_group == NULL) {
|
|
|
|
|
workspace_reassign_sticky(current);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG("Ah, this one is sticky: %s / %p\n", current->name, current);
|
|
|
|
|
/* 2: find a window which we can re-assign */
|
|
|
|
|
Con *output = con_get_output(current);
|
|
|
|
|
Con *src = _get_sticky(output, current->sticky_group, current);
|
|
|
|
|
|
|
|
|
|
if (src == NULL) {
|
|
|
|
|
LOG("No window found for this sticky group\n");
|
|
|
|
|
workspace_reassign_sticky(current);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
x_move_win(src, current);
|
|
|
|
|
current->window = src->window;
|
|
|
|
|
current->mapped = true;
|
|
|
|
|
src->window = NULL;
|
|
|
|
|
src->mapped = false;
|
|
|
|
|
|
|
|
|
|
x_reparent_child(current, src);
|
|
|
|
|
|
|
|
|
|
LOG("re-assigned window from src %p to dest %p\n", src, current);
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(current, &(con->floating_head), floating_windows)
|
|
|
|
|
workspace_reassign_sticky(current);
|
2010-09-01 18:11:01 +02:00
|
|
|
|
}
|
2009-08-08 19:51:51 +02:00
|
|
|
|
|
2012-09-22 13:48:22 +02:00
|
|
|
|
/*
|
|
|
|
|
* Callback to reset the urgent flag of the given con to false. May be started by
|
|
|
|
|
* _workspace_show to avoid urgency hints being lost by switching to a workspace
|
|
|
|
|
* focusing the con.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents) {
|
|
|
|
|
Con *con = w->data;
|
|
|
|
|
|
2015-08-02 20:50:14 +02:00
|
|
|
|
ev_timer_stop(main_loop, con->urgency_timer);
|
|
|
|
|
FREE(con->urgency_timer);
|
|
|
|
|
|
2014-06-20 14:33:02 +02:00
|
|
|
|
if (con->urgent) {
|
|
|
|
|
DLOG("Resetting urgency flag of con %p by timer\n", con);
|
2015-08-02 20:50:14 +02:00
|
|
|
|
con_set_urgency(con, false);
|
2014-06-20 14:33:02 +02:00
|
|
|
|
con_update_parents_urgency(con);
|
|
|
|
|
workspace_update_urgent_flag(con_get_workspace(con));
|
|
|
|
|
ipc_send_window_event("urgent", con);
|
|
|
|
|
tree_render();
|
|
|
|
|
}
|
2012-09-22 13:48:22 +02:00
|
|
|
|
}
|
2011-10-02 23:03:16 +02:00
|
|
|
|
|
2012-05-09 19:56:21 +02:00
|
|
|
|
static void _workspace_show(Con *workspace) {
|
2011-10-02 17:54:23 +02:00
|
|
|
|
Con *current, *old = NULL;
|
2015-09-17 22:52:56 +02:00
|
|
|
|
Con *old_focus = focused;
|
2011-01-08 00:38:10 +01:00
|
|
|
|
|
2011-12-22 00:15:32 +01:00
|
|
|
|
/* safe-guard against showing i3-internal workspaces like __i3_scratch */
|
2012-09-23 14:56:56 +02:00
|
|
|
|
if (con_is_internal(workspace))
|
2011-12-22 00:15:32 +01:00
|
|
|
|
return;
|
|
|
|
|
|
2011-01-08 00:38:10 +01:00
|
|
|
|
/* disable fullscreen for the other workspaces and get the workspace we are
|
|
|
|
|
* currently on. */
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) {
|
2011-01-08 00:38:10 +01:00
|
|
|
|
if (current->fullscreen_mode == CF_OUTPUT)
|
|
|
|
|
old = current;
|
|
|
|
|
current->fullscreen_mode = CF_NONE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* enable fullscreen for the target workspace. If it happens to be the
|
|
|
|
|
* same one we are currently on anyways, we can stop here. */
|
|
|
|
|
workspace->fullscreen_mode = CF_OUTPUT;
|
2011-10-18 00:17:56 +02:00
|
|
|
|
current = con_get_workspace(focused);
|
|
|
|
|
if (workspace == current) {
|
2011-03-20 18:27:14 +01:00
|
|
|
|
DLOG("Not switching, already there.\n");
|
2010-05-09 23:20:49 +02:00
|
|
|
|
return;
|
2011-03-20 18:27:14 +01:00
|
|
|
|
}
|
2009-08-08 19:51:51 +02:00
|
|
|
|
|
2011-10-18 00:17:56 +02:00
|
|
|
|
/* Remember currently focused workspace for switching back to it later with
|
|
|
|
|
* the 'workspace back_and_forth' command.
|
|
|
|
|
* NOTE: We have to duplicate the name as the original will be freed when
|
2012-11-10 09:01:04 +01:00
|
|
|
|
* the corresponding workspace is cleaned up.
|
|
|
|
|
* NOTE: Internal cons such as __i3_scratch (when a scratchpad window is
|
|
|
|
|
* focused) are skipped, see bug #868. */
|
2012-12-14 21:44:20 +01:00
|
|
|
|
if (current && !con_is_internal(current)) {
|
2012-11-10 09:01:04 +01:00
|
|
|
|
FREE(previous_workspace_name);
|
|
|
|
|
if (current) {
|
|
|
|
|
previous_workspace_name = sstrdup(current->name);
|
|
|
|
|
DLOG("Setting previous_workspace_name = %s\n", previous_workspace_name);
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-10-18 00:17:56 +02:00
|
|
|
|
|
2010-09-01 18:11:01 +02:00
|
|
|
|
workspace_reassign_sticky(workspace);
|
|
|
|
|
|
2012-09-28 23:04:37 +02:00
|
|
|
|
DLOG("switching to %p / %s\n", workspace, workspace->name);
|
2011-01-27 16:08:25 +01:00
|
|
|
|
Con *next = con_descend_focused(workspace);
|
2010-04-16 20:59:21 +02:00
|
|
|
|
|
2012-09-22 13:48:22 +02:00
|
|
|
|
/* Memorize current output */
|
|
|
|
|
Con *old_output = con_get_output(focused);
|
|
|
|
|
|
|
|
|
|
/* Display urgency hint for a while if the newly visible workspace would
|
|
|
|
|
* focus and thereby immediately destroy it */
|
|
|
|
|
if (next->urgent && (int)(config.workspace_urgency_timer * 1000) > 0) {
|
|
|
|
|
/* focus for now… */
|
2014-06-20 14:33:02 +02:00
|
|
|
|
next->urgent = false;
|
2012-09-22 13:48:22 +02:00
|
|
|
|
con_focus(next);
|
|
|
|
|
|
|
|
|
|
/* … but immediately reset urgency flags; they will be set to false by
|
|
|
|
|
* the timer callback in case the container is focused at the time of
|
|
|
|
|
* its expiration */
|
|
|
|
|
focused->urgent = true;
|
|
|
|
|
workspace->urgent = true;
|
|
|
|
|
|
|
|
|
|
if (focused->urgency_timer == NULL) {
|
|
|
|
|
DLOG("Deferring reset of urgency flag of con %p on newly shown workspace %p\n",
|
2014-06-15 19:07:02 +02:00
|
|
|
|
focused, workspace);
|
2015-08-03 11:50:13 +02:00
|
|
|
|
focused->urgency_timer = scalloc(1, sizeof(struct ev_timer));
|
2012-09-22 13:48:22 +02:00
|
|
|
|
/* use a repeating timer to allow for easy resets */
|
|
|
|
|
ev_timer_init(focused->urgency_timer, workspace_defer_update_urgent_hint_cb,
|
2014-06-15 19:07:02 +02:00
|
|
|
|
config.workspace_urgency_timer, config.workspace_urgency_timer);
|
2012-09-22 13:48:22 +02:00
|
|
|
|
focused->urgency_timer->data = focused;
|
|
|
|
|
ev_timer_start(main_loop, focused->urgency_timer);
|
|
|
|
|
} else {
|
|
|
|
|
DLOG("Resetting urgency timer of con %p on workspace %p\n",
|
2014-06-15 19:07:02 +02:00
|
|
|
|
focused, workspace);
|
2012-09-22 13:48:22 +02:00
|
|
|
|
ev_timer_again(main_loop, focused->urgency_timer);
|
|
|
|
|
}
|
|
|
|
|
} else
|
|
|
|
|
con_focus(next);
|
|
|
|
|
|
2014-12-20 05:43:47 +01:00
|
|
|
|
ipc_send_workspace_event("focus", workspace, current);
|
2012-11-03 12:17:31 +01:00
|
|
|
|
|
2012-09-28 23:04:37 +02:00
|
|
|
|
DLOG("old = %p / %s\n", old, (old ? old->name : "(null)"));
|
2012-09-22 13:48:22 +02:00
|
|
|
|
/* Close old workspace if necessary. This must be done *after* doing
|
2015-12-28 02:58:35 +01:00
|
|
|
|
* urgency handling, because tree_close_internal() will do a con_focus() on the next
|
2012-09-22 13:48:22 +02:00
|
|
|
|
* client, which will clear the urgency flag too early. Also, there is no
|
|
|
|
|
* way for con_focus() to know about when to clear urgency immediately and
|
|
|
|
|
* when to defer it. */
|
2011-02-20 23:43:03 +01:00
|
|
|
|
if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
|
2010-11-12 17:34:13 +01:00
|
|
|
|
/* check if this workspace is currently visible */
|
|
|
|
|
if (!workspace_is_visible(old)) {
|
|
|
|
|
LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
|
2014-12-20 05:43:47 +01:00
|
|
|
|
yajl_gen gen = ipc_marshal_workspace_event("empty", old, NULL);
|
2015-12-28 02:58:35 +01:00
|
|
|
|
tree_close_internal(old, DONT_KILL_WINDOW, false, false);
|
2014-12-20 05:43:47 +01:00
|
|
|
|
|
|
|
|
|
const unsigned char *payload;
|
|
|
|
|
ylength length;
|
|
|
|
|
y(get_buf, &payload, &length);
|
|
|
|
|
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, (const char *)payload);
|
|
|
|
|
|
|
|
|
|
y(free);
|
|
|
|
|
|
2014-06-16 08:50:47 +02:00
|
|
|
|
ewmh_update_number_of_desktops();
|
2014-06-23 23:44:24 +02:00
|
|
|
|
ewmh_update_desktop_names();
|
2014-06-22 21:20:14 +02:00
|
|
|
|
ewmh_update_desktop_viewport();
|
Handle the EWMH atom _NET_WM_DESKTOP.
We already claim _NET_WM_DESKTOP support in _NET_SUPPORTED since around 2009,
but haven't actually done anything with it. However, especially pagers like
gnome-panel rely on this property to be updated and many tools, like GTK, want
to use the corresponding client messages to make a window sticky, move it
around etc.
This patch implements full support according to the EWMH spec. This means:
* We set the property on all windows when managing it.
* We keep the property updated on all windows at all times.
* We read and respect the property upon managing a window if it was set before
mapping the window.
* We react to client messages for it.
* We remove the property on withdrawn windows.
Note that the special value 0xFFFFFFFF, according to the spec, means that the
window shall be shown on all workspaces. We do this by making it sticky and
float it. This shows it on all workspaces at least on the output it is on.
Furthermore, the spec gives us the freedom to ignore _NET_WM_DESKTOP when
managing a window if we have good reason to. In our case, we give window
swallowing a higher priority since the user would likely expect that and we
want to keep placeholder windows only around for as long as we have to.
However, we do prioritize this property over, for example, startup
notifications.
fixes #2153
fixes #1507
fixes #938
2016-01-11 20:53:26 +01:00
|
|
|
|
ewmh_update_wm_desktop();
|
2010-11-12 17:34:13 +01:00
|
|
|
|
}
|
2010-04-16 20:59:21 +02:00
|
|
|
|
}
|
|
|
|
|
|
2010-04-13 16:43:08 +02:00
|
|
|
|
workspace->fullscreen_mode = CF_OUTPUT;
|
|
|
|
|
LOG("focused now = %p / %s\n", focused, focused->name);
|
2010-11-20 23:04:01 +01:00
|
|
|
|
|
2011-08-11 21:57:22 +02:00
|
|
|
|
/* Set mouse pointer */
|
|
|
|
|
Con *new_output = con_get_output(focused);
|
|
|
|
|
if (old_output != new_output) {
|
2011-08-12 03:54:59 +02:00
|
|
|
|
x_set_warp_to(&next->rect);
|
2011-08-11 21:57:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
2011-03-14 01:09:32 +01:00
|
|
|
|
/* Update the EWMH hints */
|
|
|
|
|
ewmh_update_current_desktop();
|
2015-08-23 13:36:12 +02:00
|
|
|
|
|
2015-08-26 21:06:53 +02:00
|
|
|
|
/* Push any sticky windows to the now visible workspace. */
|
2015-09-17 22:52:56 +02:00
|
|
|
|
output_push_sticky_windows(old_focus);
|
2009-12-21 22:30:08 +01:00
|
|
|
|
}
|
|
|
|
|
|
2011-10-02 23:03:16 +02:00
|
|
|
|
/*
|
|
|
|
|
* Switches to the given workspace
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
void workspace_show(Con *workspace) {
|
2012-05-09 19:56:21 +02:00
|
|
|
|
_workspace_show(workspace);
|
2011-10-02 17:54:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
2011-10-02 23:03:16 +02:00
|
|
|
|
/*
|
|
|
|
|
* Looks up the workspace by name and switches to it.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2011-10-02 17:54:23 +02:00
|
|
|
|
void workspace_show_by_name(const char *num) {
|
|
|
|
|
Con *workspace;
|
2012-09-04 10:51:18 +02:00
|
|
|
|
workspace = workspace_get(num, NULL);
|
2012-05-09 19:56:21 +02:00
|
|
|
|
_workspace_show(workspace);
|
2011-10-02 17:54:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
2011-06-10 16:03:59 +02:00
|
|
|
|
/*
|
|
|
|
|
* Focuses the next workspace.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2014-06-15 19:07:02 +02:00
|
|
|
|
Con *workspace_next(void) {
|
Modify workspace next/prev to account for workspaces on all outputs.
Generally, the traversal goes: numbered workspaces in order, and then
named workspaces in the order in which they appear in the tree.
Example:
Output 1: Output 2:
1 3 D C 2 4 B A
Traversal: 1, 2, 3, 4, D, C, B, A, 1, ...
Note, after the numbered workspaces, we traverse the named workspaces
from output 1, and then output 2, etc.
2011-08-03 23:41:40 +02:00
|
|
|
|
Con *current = con_get_workspace(focused);
|
2015-10-16 17:17:41 +02:00
|
|
|
|
Con *next = NULL, *first = NULL, *first_opposite = NULL;
|
Modify workspace next/prev to account for workspaces on all outputs.
Generally, the traversal goes: numbered workspaces in order, and then
named workspaces in the order in which they appear in the tree.
Example:
Output 1: Output 2:
1 3 D C 2 4 B A
Traversal: 1, 2, 3, 4, D, C, B, A, 1, ...
Note, after the numbered workspaces, we traverse the named workspaces
from output 1, and then output 2, etc.
2011-08-03 23:41:40 +02:00
|
|
|
|
Con *output;
|
|
|
|
|
|
|
|
|
|
if (current->num == -1) {
|
|
|
|
|
/* If currently a named workspace, find next named workspace. */
|
2015-10-16 17:17:41 +02:00
|
|
|
|
if ((next = TAILQ_NEXT(current, nodes)) != NULL)
|
|
|
|
|
return next;
|
Modify workspace next/prev to account for workspaces on all outputs.
Generally, the traversal goes: numbered workspaces in order, and then
named workspaces in the order in which they appear in the tree.
Example:
Output 1: Output 2:
1 3 D C 2 4 B A
Traversal: 1, 2, 3, 4, D, C, B, A, 1, ...
Note, after the numbered workspaces, we traverse the named workspaces
from output 1, and then output 2, etc.
2011-08-03 23:41:40 +02:00
|
|
|
|
bool found_current = false;
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
|
2011-12-22 00:15:32 +01:00
|
|
|
|
/* Skip outputs starting with __, they are internal. */
|
2012-09-23 14:56:56 +02:00
|
|
|
|
if (con_is_internal(output))
|
2011-12-22 00:15:32 +01:00
|
|
|
|
continue;
|
2014-06-19 11:20:32 +02:00
|
|
|
|
NODES_FOREACH(output_get_content(output)) {
|
Modify workspace next/prev to account for workspaces on all outputs.
Generally, the traversal goes: numbered workspaces in order, and then
named workspaces in the order in which they appear in the tree.
Example:
Output 1: Output 2:
1 3 D C 2 4 B A
Traversal: 1, 2, 3, 4, D, C, B, A, 1, ...
Note, after the numbered workspaces, we traverse the named workspaces
from output 1, and then output 2, etc.
2011-08-03 23:41:40 +02:00
|
|
|
|
if (child->type != CT_WORKSPACE)
|
|
|
|
|
continue;
|
2015-10-16 17:17:41 +02:00
|
|
|
|
if (!first)
|
|
|
|
|
first = child;
|
2016-07-31 19:50:01 +02:00
|
|
|
|
if (!first_opposite || (child->num != -1 && child->num < first_opposite->num))
|
2015-10-16 17:17:41 +02:00
|
|
|
|
first_opposite = child;
|
Modify workspace next/prev to account for workspaces on all outputs.
Generally, the traversal goes: numbered workspaces in order, and then
named workspaces in the order in which they appear in the tree.
Example:
Output 1: Output 2:
1 3 D C 2 4 B A
Traversal: 1, 2, 3, 4, D, C, B, A, 1, ...
Note, after the numbered workspaces, we traverse the named workspaces
from output 1, and then output 2, etc.
2011-08-03 23:41:40 +02:00
|
|
|
|
if (child == current) {
|
2015-10-16 17:17:41 +02:00
|
|
|
|
found_current = true;
|
|
|
|
|
} else if (child->num == -1 && found_current) {
|
Modify workspace next/prev to account for workspaces on all outputs.
Generally, the traversal goes: numbered workspaces in order, and then
named workspaces in the order in which they appear in the tree.
Example:
Output 1: Output 2:
1 3 D C 2 4 B A
Traversal: 1, 2, 3, 4, D, C, B, A, 1, ...
Note, after the numbered workspaces, we traverse the named workspaces
from output 1, and then output 2, etc.
2011-08-03 23:41:40 +02:00
|
|
|
|
next = child;
|
2015-10-16 17:17:41 +02:00
|
|
|
|
return next;
|
Modify workspace next/prev to account for workspaces on all outputs.
Generally, the traversal goes: numbered workspaces in order, and then
named workspaces in the order in which they appear in the tree.
Example:
Output 1: Output 2:
1 3 D C 2 4 B A
Traversal: 1, 2, 3, 4, D, C, B, A, 1, ...
Note, after the numbered workspaces, we traverse the named workspaces
from output 1, and then output 2, etc.
2011-08-03 23:41:40 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2011-12-22 00:15:32 +01:00
|
|
|
|
}
|
2015-10-16 17:17:41 +02:00
|
|
|
|
} else {
|
|
|
|
|
/* If currently a numbered workspace, find next numbered workspace. */
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
|
2011-12-22 00:15:32 +01:00
|
|
|
|
/* Skip outputs starting with __, they are internal. */
|
2012-09-23 14:56:56 +02:00
|
|
|
|
if (con_is_internal(output))
|
2011-12-22 00:15:32 +01:00
|
|
|
|
continue;
|
2014-06-19 11:20:32 +02:00
|
|
|
|
NODES_FOREACH(output_get_content(output)) {
|
Modify workspace next/prev to account for workspaces on all outputs.
Generally, the traversal goes: numbered workspaces in order, and then
named workspaces in the order in which they appear in the tree.
Example:
Output 1: Output 2:
1 3 D C 2 4 B A
Traversal: 1, 2, 3, 4, D, C, B, A, 1, ...
Note, after the numbered workspaces, we traverse the named workspaces
from output 1, and then output 2, etc.
2011-08-03 23:41:40 +02:00
|
|
|
|
if (child->type != CT_WORKSPACE)
|
|
|
|
|
continue;
|
2016-07-09 12:56:06 +02:00
|
|
|
|
if (!first || (child->num != -1 && child->num < first->num))
|
2015-10-16 17:17:41 +02:00
|
|
|
|
first = child;
|
|
|
|
|
if (!first_opposite && child->num == -1)
|
|
|
|
|
first_opposite = child;
|
|
|
|
|
if (child->num == -1)
|
|
|
|
|
break;
|
|
|
|
|
/* Need to check child against current and next because we are
|
|
|
|
|
* traversing multiple lists and thus are not guaranteed the
|
|
|
|
|
* relative order between the list of workspaces. */
|
|
|
|
|
if (current->num < child->num && (!next || child->num < next->num))
|
Modify workspace next/prev to account for workspaces on all outputs.
Generally, the traversal goes: numbered workspaces in order, and then
named workspaces in the order in which they appear in the tree.
Example:
Output 1: Output 2:
1 3 D C 2 4 B A
Traversal: 1, 2, 3, 4, D, C, B, A, 1, ...
Note, after the numbered workspaces, we traverse the named workspaces
from output 1, and then output 2, etc.
2011-08-03 23:41:40 +02:00
|
|
|
|
next = child;
|
|
|
|
|
}
|
2011-12-22 00:15:32 +01:00
|
|
|
|
}
|
Modify workspace next/prev to account for workspaces on all outputs.
Generally, the traversal goes: numbered workspaces in order, and then
named workspaces in the order in which they appear in the tree.
Example:
Output 1: Output 2:
1 3 D C 2 4 B A
Traversal: 1, 2, 3, 4, D, C, B, A, 1, ...
Note, after the numbered workspaces, we traverse the named workspaces
from output 1, and then output 2, etc.
2011-08-03 23:41:40 +02:00
|
|
|
|
}
|
2015-10-16 17:17:41 +02:00
|
|
|
|
|
|
|
|
|
if (!next)
|
|
|
|
|
next = first_opposite ? first_opposite : first;
|
|
|
|
|
|
2011-10-02 17:54:23 +02:00
|
|
|
|
return next;
|
2011-06-10 16:03:59 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Focuses the previous workspace.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2014-06-15 19:07:02 +02:00
|
|
|
|
Con *workspace_prev(void) {
|
Modify workspace next/prev to account for workspaces on all outputs.
Generally, the traversal goes: numbered workspaces in order, and then
named workspaces in the order in which they appear in the tree.
Example:
Output 1: Output 2:
1 3 D C 2 4 B A
Traversal: 1, 2, 3, 4, D, C, B, A, 1, ...
Note, after the numbered workspaces, we traverse the named workspaces
from output 1, and then output 2, etc.
2011-08-03 23:41:40 +02:00
|
|
|
|
Con *current = con_get_workspace(focused);
|
2015-10-16 17:17:41 +02:00
|
|
|
|
Con *prev = NULL, *first_opposite = NULL, *last = NULL;
|
Modify workspace next/prev to account for workspaces on all outputs.
Generally, the traversal goes: numbered workspaces in order, and then
named workspaces in the order in which they appear in the tree.
Example:
Output 1: Output 2:
1 3 D C 2 4 B A
Traversal: 1, 2, 3, 4, D, C, B, A, 1, ...
Note, after the numbered workspaces, we traverse the named workspaces
from output 1, and then output 2, etc.
2011-08-03 23:41:40 +02:00
|
|
|
|
Con *output;
|
|
|
|
|
|
|
|
|
|
if (current->num == -1) {
|
|
|
|
|
/* If named workspace, find previous named workspace. */
|
|
|
|
|
prev = TAILQ_PREV(current, nodes_head, nodes);
|
|
|
|
|
if (prev && prev->num != -1)
|
|
|
|
|
prev = NULL;
|
2015-10-16 17:17:41 +02:00
|
|
|
|
if (!prev) {
|
|
|
|
|
bool found_current = false;
|
|
|
|
|
TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
|
|
|
|
|
/* Skip outputs starting with __, they are internal. */
|
|
|
|
|
if (con_is_internal(output))
|
|
|
|
|
continue;
|
|
|
|
|
NODES_FOREACH_REVERSE(output_get_content(output)) {
|
|
|
|
|
if (child->type != CT_WORKSPACE)
|
|
|
|
|
continue;
|
|
|
|
|
if (!last)
|
|
|
|
|
last = child;
|
2016-07-31 19:50:01 +02:00
|
|
|
|
if (!first_opposite || (child->num != -1 && child->num > first_opposite->num))
|
2015-10-16 17:17:41 +02:00
|
|
|
|
first_opposite = child;
|
|
|
|
|
if (child == current) {
|
|
|
|
|
found_current = true;
|
|
|
|
|
} else if (child->num == -1 && found_current) {
|
|
|
|
|
prev = child;
|
2016-07-31 19:27:02 +02:00
|
|
|
|
return prev;
|
2015-10-16 17:17:41 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
Modify workspace next/prev to account for workspaces on all outputs.
Generally, the traversal goes: numbered workspaces in order, and then
named workspaces in the order in which they appear in the tree.
Example:
Output 1: Output 2:
1 3 D C 2 4 B A
Traversal: 1, 2, 3, 4, D, C, B, A, 1, ...
Note, after the numbered workspaces, we traverse the named workspaces
from output 1, and then output 2, etc.
2011-08-03 23:41:40 +02:00
|
|
|
|
} else {
|
|
|
|
|
/* If numbered workspace, find previous numbered workspace. */
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
|
2011-12-22 00:15:32 +01:00
|
|
|
|
/* Skip outputs starting with __, they are internal. */
|
2012-09-23 14:56:56 +02:00
|
|
|
|
if (con_is_internal(output))
|
2011-12-22 00:15:32 +01:00
|
|
|
|
continue;
|
2014-06-19 11:20:32 +02:00
|
|
|
|
NODES_FOREACH_REVERSE(output_get_content(output)) {
|
2015-10-16 17:17:41 +02:00
|
|
|
|
if (child->type != CT_WORKSPACE)
|
|
|
|
|
continue;
|
2016-07-09 12:56:06 +02:00
|
|
|
|
if (!last || (child->num != -1 && last->num < child->num))
|
2015-10-16 17:17:41 +02:00
|
|
|
|
last = child;
|
|
|
|
|
if (!first_opposite && child->num == -1)
|
|
|
|
|
first_opposite = child;
|
|
|
|
|
if (child->num == -1)
|
Modify workspace next/prev to account for workspaces on all outputs.
Generally, the traversal goes: numbered workspaces in order, and then
named workspaces in the order in which they appear in the tree.
Example:
Output 1: Output 2:
1 3 D C 2 4 B A
Traversal: 1, 2, 3, 4, D, C, B, A, 1, ...
Note, after the numbered workspaces, we traverse the named workspaces
from output 1, and then output 2, etc.
2011-08-03 23:41:40 +02:00
|
|
|
|
continue;
|
|
|
|
|
/* Need to check child against current and previous because we
|
|
|
|
|
* are traversing multiple lists and thus are not guaranteed
|
|
|
|
|
* the relative order between the list of workspaces. */
|
|
|
|
|
if (current->num > child->num && (!prev || child->num > prev->num))
|
|
|
|
|
prev = child;
|
|
|
|
|
}
|
2011-12-22 00:15:32 +01:00
|
|
|
|
}
|
Modify workspace next/prev to account for workspaces on all outputs.
Generally, the traversal goes: numbered workspaces in order, and then
named workspaces in the order in which they appear in the tree.
Example:
Output 1: Output 2:
1 3 D C 2 4 B A
Traversal: 1, 2, 3, 4, D, C, B, A, 1, ...
Note, after the numbered workspaces, we traverse the named workspaces
from output 1, and then output 2, etc.
2011-08-03 23:41:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-10-16 17:17:41 +02:00
|
|
|
|
if (!prev)
|
|
|
|
|
prev = first_opposite ? first_opposite : last;
|
2011-06-10 16:03:59 +02:00
|
|
|
|
|
2011-10-02 17:54:23 +02:00
|
|
|
|
return prev;
|
2011-06-10 16:03:59 +02:00
|
|
|
|
}
|
|
|
|
|
|
2011-12-25 03:30:10 +01:00
|
|
|
|
/*
|
|
|
|
|
* Focuses the next workspace on the same output.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2014-06-15 19:07:02 +02:00
|
|
|
|
Con *workspace_next_on_output(void) {
|
2011-12-25 03:30:10 +01:00
|
|
|
|
Con *current = con_get_workspace(focused);
|
|
|
|
|
Con *next = NULL;
|
2014-06-15 19:07:02 +02:00
|
|
|
|
Con *output = con_get_output(focused);
|
2011-12-25 03:30:10 +01:00
|
|
|
|
|
|
|
|
|
if (current->num == -1) {
|
|
|
|
|
/* If currently a named workspace, find next named workspace. */
|
|
|
|
|
next = TAILQ_NEXT(current, nodes);
|
|
|
|
|
} else {
|
|
|
|
|
/* If currently a numbered workspace, find next numbered workspace. */
|
2014-06-19 11:20:32 +02:00
|
|
|
|
NODES_FOREACH(output_get_content(output)) {
|
2011-12-25 03:30:10 +01:00
|
|
|
|
if (child->type != CT_WORKSPACE)
|
|
|
|
|
continue;
|
|
|
|
|
if (child->num == -1)
|
|
|
|
|
break;
|
|
|
|
|
/* Need to check child against current and next because we are
|
|
|
|
|
* traversing multiple lists and thus are not guaranteed the
|
|
|
|
|
* relative order between the list of workspaces. */
|
|
|
|
|
if (current->num < child->num && (!next || child->num < next->num))
|
|
|
|
|
next = child;
|
|
|
|
|
}
|
2014-06-15 19:07:02 +02:00
|
|
|
|
}
|
2011-12-25 03:30:10 +01:00
|
|
|
|
|
|
|
|
|
/* Find next named workspace. */
|
|
|
|
|
if (!next) {
|
|
|
|
|
bool found_current = false;
|
2014-06-19 11:20:32 +02:00
|
|
|
|
NODES_FOREACH(output_get_content(output)) {
|
2011-12-25 03:30:10 +01:00
|
|
|
|
if (child->type != CT_WORKSPACE)
|
|
|
|
|
continue;
|
|
|
|
|
if (child == current) {
|
2015-10-16 17:17:41 +02:00
|
|
|
|
found_current = true;
|
2011-12-25 03:30:10 +01:00
|
|
|
|
} else if (child->num == -1 && (current->num != -1 || found_current)) {
|
|
|
|
|
next = child;
|
|
|
|
|
goto workspace_next_on_output_end;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Find first workspace. */
|
|
|
|
|
if (!next) {
|
2014-06-19 11:20:32 +02:00
|
|
|
|
NODES_FOREACH(output_get_content(output)) {
|
2011-12-25 03:30:10 +01:00
|
|
|
|
if (child->type != CT_WORKSPACE)
|
|
|
|
|
continue;
|
|
|
|
|
if (!next || (child->num != -1 && child->num < next->num))
|
|
|
|
|
next = child;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
workspace_next_on_output_end:
|
|
|
|
|
return next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Focuses the previous workspace on same output.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2014-06-15 19:07:02 +02:00
|
|
|
|
Con *workspace_prev_on_output(void) {
|
2011-12-25 03:30:10 +01:00
|
|
|
|
Con *current = con_get_workspace(focused);
|
|
|
|
|
Con *prev = NULL;
|
2014-06-15 19:07:02 +02:00
|
|
|
|
Con *output = con_get_output(focused);
|
2012-04-09 14:48:43 +02:00
|
|
|
|
DLOG("output = %s\n", output->name);
|
2011-12-25 03:30:10 +01:00
|
|
|
|
|
|
|
|
|
if (current->num == -1) {
|
|
|
|
|
/* If named workspace, find previous named workspace. */
|
|
|
|
|
prev = TAILQ_PREV(current, nodes_head, nodes);
|
|
|
|
|
if (prev && prev->num != -1)
|
|
|
|
|
prev = NULL;
|
|
|
|
|
} else {
|
|
|
|
|
/* If numbered workspace, find previous numbered workspace. */
|
2014-06-19 11:20:32 +02:00
|
|
|
|
NODES_FOREACH_REVERSE(output_get_content(output)) {
|
2011-12-25 03:30:10 +01:00
|
|
|
|
if (child->type != CT_WORKSPACE || child->num == -1)
|
|
|
|
|
continue;
|
2014-06-15 19:07:02 +02:00
|
|
|
|
/* Need to check child against current and previous because we
|
2011-12-25 03:30:10 +01:00
|
|
|
|
* are traversing multiple lists and thus are not guaranteed
|
|
|
|
|
* the relative order between the list of workspaces. */
|
|
|
|
|
if (current->num > child->num && (!prev || child->num > prev->num))
|
|
|
|
|
prev = child;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Find previous named workspace. */
|
|
|
|
|
if (!prev) {
|
|
|
|
|
bool found_current = false;
|
2014-06-19 11:20:32 +02:00
|
|
|
|
NODES_FOREACH_REVERSE(output_get_content(output)) {
|
2011-12-25 03:30:10 +01:00
|
|
|
|
if (child->type != CT_WORKSPACE)
|
|
|
|
|
continue;
|
|
|
|
|
if (child == current) {
|
|
|
|
|
found_current = true;
|
|
|
|
|
} else if (child->num == -1 && (current->num != -1 || found_current)) {
|
|
|
|
|
prev = child;
|
|
|
|
|
goto workspace_prev_on_output_end;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Find last workspace. */
|
|
|
|
|
if (!prev) {
|
2014-06-19 11:20:32 +02:00
|
|
|
|
NODES_FOREACH_REVERSE(output_get_content(output)) {
|
2011-12-25 03:30:10 +01:00
|
|
|
|
if (child->type != CT_WORKSPACE)
|
|
|
|
|
continue;
|
|
|
|
|
if (!prev || child->num > prev->num)
|
|
|
|
|
prev = child;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
workspace_prev_on_output_end:
|
|
|
|
|
return prev;
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-18 00:17:56 +02:00
|
|
|
|
/*
|
|
|
|
|
* Focuses the previously focused workspace.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2012-03-31 10:53:04 +02:00
|
|
|
|
void workspace_back_and_forth(void) {
|
2011-10-18 00:17:56 +02:00
|
|
|
|
if (!previous_workspace_name) {
|
2015-06-30 20:48:35 +02:00
|
|
|
|
DLOG("No previous workspace name set. Not switching.\n");
|
2011-10-18 00:17:56 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
workspace_show_by_name(previous_workspace_name);
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-04 10:51:18 +02:00
|
|
|
|
/*
|
|
|
|
|
* Returns the previously focused workspace con, or NULL if unavailable.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
Con *workspace_back_and_forth_get(void) {
|
|
|
|
|
if (!previous_workspace_name) {
|
2015-06-30 20:48:35 +02:00
|
|
|
|
DLOG("No previous workspace name set.\n");
|
2012-09-04 10:51:18 +02:00
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Con *workspace;
|
|
|
|
|
workspace = workspace_get(previous_workspace_name, NULL);
|
|
|
|
|
|
|
|
|
|
return workspace;
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-02 17:51:58 +02:00
|
|
|
|
static bool get_urgency_flag(Con *con) {
|
|
|
|
|
Con *child;
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(child, &(con->nodes_head), nodes)
|
|
|
|
|
if (child->urgent || get_urgency_flag(child))
|
|
|
|
|
return true;
|
2010-06-02 17:51:58 +02:00
|
|
|
|
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(child, &(con->floating_head), floating_windows)
|
|
|
|
|
if (child->urgent || get_urgency_flag(child))
|
|
|
|
|
return true;
|
2010-06-02 17:51:58 +02:00
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2009-08-08 19:51:51 +02:00
|
|
|
|
|
2009-09-06 22:40:11 +02:00
|
|
|
|
/*
|
|
|
|
|
* Goes through all clients on the given workspace and updates the workspace’s
|
|
|
|
|
* urgent flag accordingly.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2010-06-02 17:51:58 +02:00
|
|
|
|
void workspace_update_urgent_flag(Con *ws) {
|
|
|
|
|
bool old_flag = ws->urgent;
|
|
|
|
|
ws->urgent = get_urgency_flag(ws);
|
|
|
|
|
DLOG("Workspace urgency flag changed from %d to %d\n", old_flag, ws->urgent);
|
2009-09-06 22:40:11 +02:00
|
|
|
|
|
2010-06-02 17:51:58 +02:00
|
|
|
|
if (old_flag != ws->urgent)
|
2014-12-20 05:43:47 +01:00
|
|
|
|
ipc_send_workspace_event("urgent", ws, NULL);
|
2009-09-06 22:40:11 +02:00
|
|
|
|
}
|
2011-02-14 23:05:20 +01:00
|
|
|
|
|
2011-02-14 23:17:30 +01:00
|
|
|
|
/*
|
|
|
|
|
* 'Forces' workspace orientation by moving all cons into a new split-con with
|
Introduce splith/splitv layouts, remove orientation
With this commit, the "default" layout is replaced by the splith and
splitv layouts. splith is equivalent to default with orientation
horizontal and splitv is equivalent to default with orientation
vertical.
The "split h" and "split v" commands continue to work as before, they
split the current container and you will end up in a split container
with layout splith (after "split h") or splitv (after "split v").
To change a splith container into a splitv container, use either "layout
splitv" or "layout toggle split". The latter command is used in the
default config as mod+l (previously "layout default"). In case you have
"layout default" in your config file, it is recommended to just replace
it by "layout toggle split", which will work as "layout default" did
before when pressing it once, but toggle between horizontal/vertical
when pressing it repeatedly.
The rationale behind this commit is that it’s cleaner to have all
parameters that influence how windows are rendered in the layout itself
rather than having a special parameter in combination with only one
layout. This enables us to change existing split containers in all cases
without breaking existing features (see ticket #464). Also, users should
feel more confident about whether they are actually splitting or just
changing an existing split container now.
As a nice side-effect, this commit brings back the "layout toggle"
feature we once had in i3 version 3 (see the userguide).
AFAIK, it is safe to use in-place restart to upgrade into versions
after this commit (switching to an older version will break your layout,
though).
Fixes #464
2012-08-04 03:04:00 +02:00
|
|
|
|
* the same layout as the workspace and then changing the workspace layout.
|
2011-02-14 23:17:30 +01:00
|
|
|
|
*
|
|
|
|
|
*/
|
2011-02-14 23:05:20 +01:00
|
|
|
|
void ws_force_orientation(Con *ws, orientation_t orientation) {
|
|
|
|
|
/* 1: create a new split container */
|
2011-06-02 17:21:38 +02:00
|
|
|
|
Con *split = con_new(NULL, NULL);
|
2011-02-14 23:05:20 +01:00
|
|
|
|
split->parent = ws;
|
|
|
|
|
|
Introduce splith/splitv layouts, remove orientation
With this commit, the "default" layout is replaced by the splith and
splitv layouts. splith is equivalent to default with orientation
horizontal and splitv is equivalent to default with orientation
vertical.
The "split h" and "split v" commands continue to work as before, they
split the current container and you will end up in a split container
with layout splith (after "split h") or splitv (after "split v").
To change a splith container into a splitv container, use either "layout
splitv" or "layout toggle split". The latter command is used in the
default config as mod+l (previously "layout default"). In case you have
"layout default" in your config file, it is recommended to just replace
it by "layout toggle split", which will work as "layout default" did
before when pressing it once, but toggle between horizontal/vertical
when pressing it repeatedly.
The rationale behind this commit is that it’s cleaner to have all
parameters that influence how windows are rendered in the layout itself
rather than having a special parameter in combination with only one
layout. This enables us to change existing split containers in all cases
without breaking existing features (see ticket #464). Also, users should
feel more confident about whether they are actually splitting or just
changing an existing split container now.
As a nice side-effect, this commit brings back the "layout toggle"
feature we once had in i3 version 3 (see the userguide).
AFAIK, it is safe to use in-place restart to upgrade into versions
after this commit (switching to an older version will break your layout,
though).
Fixes #464
2012-08-04 03:04:00 +02:00
|
|
|
|
/* 2: copy layout from workspace */
|
2011-02-14 23:05:20 +01:00
|
|
|
|
split->layout = ws->layout;
|
|
|
|
|
|
|
|
|
|
Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
|
|
|
|
|
|
|
|
|
|
/* 3: move the existing cons of this workspace below the new con */
|
|
|
|
|
DLOG("Moving cons\n");
|
|
|
|
|
while (!TAILQ_EMPTY(&(ws->nodes_head))) {
|
|
|
|
|
Con *child = TAILQ_FIRST(&(ws->nodes_head));
|
|
|
|
|
con_detach(child);
|
|
|
|
|
con_attach(child, split, true);
|
|
|
|
|
}
|
|
|
|
|
|
Introduce splith/splitv layouts, remove orientation
With this commit, the "default" layout is replaced by the splith and
splitv layouts. splith is equivalent to default with orientation
horizontal and splitv is equivalent to default with orientation
vertical.
The "split h" and "split v" commands continue to work as before, they
split the current container and you will end up in a split container
with layout splith (after "split h") or splitv (after "split v").
To change a splith container into a splitv container, use either "layout
splitv" or "layout toggle split". The latter command is used in the
default config as mod+l (previously "layout default"). In case you have
"layout default" in your config file, it is recommended to just replace
it by "layout toggle split", which will work as "layout default" did
before when pressing it once, but toggle between horizontal/vertical
when pressing it repeatedly.
The rationale behind this commit is that it’s cleaner to have all
parameters that influence how windows are rendered in the layout itself
rather than having a special parameter in combination with only one
layout. This enables us to change existing split containers in all cases
without breaking existing features (see ticket #464). Also, users should
feel more confident about whether they are actually splitting or just
changing an existing split container now.
As a nice side-effect, this commit brings back the "layout toggle"
feature we once had in i3 version 3 (see the userguide).
AFAIK, it is safe to use in-place restart to upgrade into versions
after this commit (switching to an older version will break your layout,
though).
Fixes #464
2012-08-04 03:04:00 +02:00
|
|
|
|
/* 4: switch workspace layout */
|
|
|
|
|
ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
|
2012-09-05 22:03:45 +02:00
|
|
|
|
DLOG("split->layout = %d, ws->layout = %d\n", split->layout, ws->layout);
|
2011-02-14 23:05:20 +01:00
|
|
|
|
|
|
|
|
|
/* 5: attach the new split container to the workspace */
|
2012-09-05 22:03:45 +02:00
|
|
|
|
DLOG("Attaching new split (%p) to ws (%p)\n", split, ws);
|
2011-02-14 23:05:20 +01:00
|
|
|
|
con_attach(split, ws, false);
|
|
|
|
|
|
|
|
|
|
/* 6: fix the percentages */
|
|
|
|
|
con_fix_percent(ws);
|
|
|
|
|
|
|
|
|
|
if (old_focused)
|
|
|
|
|
con_focus(old_focused);
|
|
|
|
|
}
|
2011-06-02 17:21:38 +02:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Called when a new con (with a window, not an empty or split con) should be
|
|
|
|
|
* attached to the workspace (for example when managing a new window or when
|
|
|
|
|
* moving an existing window to the workspace level).
|
|
|
|
|
*
|
|
|
|
|
* Depending on the workspace_layout setting, this function either returns the
|
|
|
|
|
* workspace itself (default layout) or creates a new stacked/tabbed con and
|
|
|
|
|
* returns that.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
Con *workspace_attach_to(Con *ws) {
|
|
|
|
|
DLOG("Attaching a window to workspace %p / %s\n", ws, ws->name);
|
|
|
|
|
|
2012-09-16 22:53:41 +02:00
|
|
|
|
if (ws->workspace_layout == L_DEFAULT) {
|
2011-06-02 17:21:38 +02:00
|
|
|
|
DLOG("Default layout, just attaching it to the workspace itself.\n");
|
|
|
|
|
return ws;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DLOG("Non-default layout, creating a new split container\n");
|
|
|
|
|
/* 1: create a new split container */
|
|
|
|
|
Con *new = con_new(NULL, NULL);
|
|
|
|
|
new->parent = ws;
|
|
|
|
|
|
|
|
|
|
/* 2: set the requested layout on the split con */
|
2012-09-16 22:53:41 +02:00
|
|
|
|
new->layout = ws->workspace_layout;
|
2011-06-02 17:21:38 +02:00
|
|
|
|
|
|
|
|
|
/* 4: attach the new split container to the workspace */
|
|
|
|
|
DLOG("Attaching new split %p to workspace %p\n", new, ws);
|
|
|
|
|
con_attach(new, ws, false);
|
|
|
|
|
|
2015-03-28 03:20:28 +01:00
|
|
|
|
/* 5: fix the percentages */
|
|
|
|
|
con_fix_percent(ws);
|
|
|
|
|
|
2011-06-02 17:21:38 +02:00
|
|
|
|
return new;
|
|
|
|
|
}
|
2012-09-28 19:54:24 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a new container and re-parents all of children from the given
|
|
|
|
|
* workspace into it.
|
|
|
|
|
*
|
|
|
|
|
* The container inherits the layout from the workspace.
|
|
|
|
|
*/
|
|
|
|
|
Con *workspace_encapsulate(Con *ws) {
|
|
|
|
|
if (TAILQ_EMPTY(&(ws->nodes_head))) {
|
|
|
|
|
ELOG("Workspace %p / %s has no children to encapsulate\n", ws, ws->name);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Con *new = con_new(NULL, NULL);
|
|
|
|
|
new->parent = ws;
|
|
|
|
|
new->layout = ws->layout;
|
|
|
|
|
|
|
|
|
|
DLOG("Moving children of workspace %p / %s into container %p\n",
|
2014-06-15 19:07:02 +02:00
|
|
|
|
ws, ws->name, new);
|
2012-09-28 19:54:24 +02:00
|
|
|
|
|
|
|
|
|
Con *child;
|
|
|
|
|
while (!TAILQ_EMPTY(&(ws->nodes_head))) {
|
|
|
|
|
child = TAILQ_FIRST(&(ws->nodes_head));
|
|
|
|
|
con_detach(child);
|
|
|
|
|
con_attach(child, new, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
con_attach(new, ws, true);
|
|
|
|
|
|
|
|
|
|
return new;
|
|
|
|
|
}
|
2015-03-04 09:22:25 +01:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Move the given workspace to the specified output.
|
|
|
|
|
* This returns true if and only if moving the workspace was successful.
|
|
|
|
|
*/
|
2015-09-27 23:42:58 +02:00
|
|
|
|
bool workspace_move_to_output(Con *ws, const char *name) {
|
2015-03-04 09:22:25 +01:00
|
|
|
|
LOG("Trying to move workspace %p / %s to output \"%s\".\n", ws, ws->name, name);
|
|
|
|
|
|
|
|
|
|
Con *current_output_con = con_get_output(ws);
|
|
|
|
|
if (!current_output_con) {
|
|
|
|
|
ELOG("Could not get the output container for workspace %p / %s.\n", ws, ws->name);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Output *current_output = get_output_by_name(current_output_con->name);
|
|
|
|
|
if (!current_output) {
|
|
|
|
|
ELOG("Cannot get current output. This is a bug in i3.\n");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
Output *output = get_output_from_string(current_output, name);
|
|
|
|
|
if (!output) {
|
|
|
|
|
ELOG("Could not get output from string \"%s\"\n", name);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Con *content = output_get_content(output->con);
|
|
|
|
|
LOG("got output %p with content %p\n", output, content);
|
|
|
|
|
|
2016-02-25 19:12:09 +01:00
|
|
|
|
Con *previously_visible_ws = TAILQ_FIRST(&(content->focus_head));
|
2015-03-04 09:22:25 +01:00
|
|
|
|
LOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name);
|
|
|
|
|
|
|
|
|
|
bool workspace_was_visible = workspace_is_visible(ws);
|
|
|
|
|
if (con_num_children(ws->parent) == 1) {
|
|
|
|
|
LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name);
|
|
|
|
|
|
|
|
|
|
/* check if we can find a workspace assigned to this output */
|
|
|
|
|
bool used_assignment = false;
|
|
|
|
|
struct Workspace_Assignment *assignment;
|
|
|
|
|
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
|
|
|
|
if (assignment->output == NULL || strcmp(assignment->output, current_output->name) != 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
/* check if this workspace is already attached to the tree */
|
|
|
|
|
Con *workspace = NULL, *out;
|
|
|
|
|
TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
|
|
|
|
|
GREP_FIRST(workspace, output_get_content(out),
|
|
|
|
|
!strcasecmp(child->name, assignment->name));
|
|
|
|
|
if (workspace != NULL)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
/* so create the workspace referenced to by this assignment */
|
|
|
|
|
LOG("Creating workspace from assignment %s.\n", assignment->name);
|
|
|
|
|
workspace_get(assignment->name, NULL);
|
|
|
|
|
used_assignment = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* if we couldn't create the workspace using an assignment, create
|
|
|
|
|
* it on the output */
|
|
|
|
|
if (!used_assignment)
|
|
|
|
|
create_workspace_on_output(current_output, ws->parent);
|
|
|
|
|
|
|
|
|
|
/* notify the IPC listeners */
|
|
|
|
|
ipc_send_workspace_event("init", ws, NULL);
|
|
|
|
|
}
|
|
|
|
|
DLOG("Detaching\n");
|
|
|
|
|
|
|
|
|
|
/* detach from the old output and attach to the new output */
|
|
|
|
|
Con *old_content = ws->parent;
|
|
|
|
|
con_detach(ws);
|
|
|
|
|
if (workspace_was_visible) {
|
|
|
|
|
/* The workspace which we just detached was visible, so focus
|
|
|
|
|
* the next one in the focus-stack. */
|
|
|
|
|
Con *focus_ws = TAILQ_FIRST(&(old_content->focus_head));
|
|
|
|
|
LOG("workspace was visible, focusing %p / %s now\n", focus_ws, focus_ws->name);
|
|
|
|
|
workspace_show(focus_ws);
|
|
|
|
|
}
|
|
|
|
|
con_attach(ws, content, false);
|
|
|
|
|
|
|
|
|
|
/* fix the coordinates of the floating containers */
|
|
|
|
|
Con *floating_con;
|
|
|
|
|
TAILQ_FOREACH(floating_con, &(ws->floating_head), floating_windows)
|
|
|
|
|
floating_fix_coordinates(floating_con, &(old_content->rect), &(content->rect));
|
|
|
|
|
|
|
|
|
|
ipc_send_workspace_event("move", ws, NULL);
|
|
|
|
|
if (workspace_was_visible) {
|
|
|
|
|
/* Focus the moved workspace on the destination output. */
|
|
|
|
|
workspace_show(ws);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* NB: We cannot simply work with previously_visible_ws since it might
|
|
|
|
|
* have been cleaned up by workspace_show() already, depending on the
|
|
|
|
|
* focus order/number of other workspaces on the output.
|
|
|
|
|
* Instead, we loop through the available workspaces and only work with
|
|
|
|
|
* previously_visible_ws if we still find it. */
|
|
|
|
|
TAILQ_FOREACH(ws, &(content->nodes_head), nodes) {
|
|
|
|
|
if (ws != previously_visible_ws)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
/* Call the on_remove_child callback of the workspace which previously
|
|
|
|
|
* was visible on the destination output. Since it is no longer
|
|
|
|
|
* visible, it might need to get cleaned up. */
|
|
|
|
|
CALL(previously_visible_ws, on_remove_child);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|