-i3-wm (4.4.1-0) unstable; urgency=low
+i3-wm (4.5.2-1) experimental; urgency=low
* NOT YET RELEASED
- -- Michael Stapelberg <stapelberg@debian.org> Wed, 12 Dec 2012 00:23:32 +0100
+ -- Michael Stapelberg <stapelberg@debian.org> Mon, 18 Mar 2013 23:01:30 +0100
+
+i3-wm (4.5.1-1) experimental; urgency=low
+
+ * New upstream release
+
+ -- Michael Stapelberg <stapelberg@debian.org> Mon, 18 Mar 2013 22:50:12 +0100
+
+i3-wm (4.5-1) experimental; urgency=low
+
+ * New upstream release
+
+ -- Michael Stapelberg <stapelberg@debian.org> Tue, 12 Mar 2013 13:51:04 +0100
i3-wm (4.4-1) experimental; urgency=low
$(MAKE) -C docs
override_dh_installchangelogs:
- dh_installchangelogs RELEASE-NOTES-4.4
+ dh_installchangelogs RELEASE-NOTES-4.5.1
override_dh_install:
$(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install
== Using git / sending patches
+=== Introduction
+
For a short introduction into using git, see
-http://www.spheredev.org/wiki/Git_for_the_lazy or, for more documentation, see
-http://git-scm.com/documentation
+http://web.archive.org/web/20121024222556/http://www.spheredev.org/wiki/Git_for_the_lazy
+or, for more documentation, see http://git-scm.com/documentation
Please talk to us before working on new features to see whether they will be
accepted. There are a few things which we don’t want to see in i3, e.g. a
http://cr.i3wm.org/. In order to make your review go as fast as possible, you
could have a look at previous reviews and see what the common mistakes are.
+=== Which branch to use?
+
+Work on i3 generally happens in two branches: “master” and “next”. Since
+“master” is what people get when they check out the git repository, its
+contents are always stable. That is, it contains the source code of the latest
+release, plus any bugfixes that were applied since that release.
+
+New features are only found in the “next” branch. Therefore, if you are working
+on a new feature, use the “next” branch. If you are working on a bugfix, use
+the “next” branch, too, but make sure your code also works on “master”.
+
== Thought experiments
In this section, we collect thought experiments, so that we don’t forget our
*All features example*:
------------------------------
-{ "version": 1, "stop_signal": 10, "cont_signal": 12 }
+{ "version": 1, "stop_signal": 10, "cont_signal": 12, "click_events": true }
------------------------------
(Note that before i3 v4.3 the precise format had to be +{"version":1}+,
Specify to i3bar the signal (as an integer)to send to continue your
processing.
The default value (if none is specified) is SIGCONT.
+click_events::
+ If specified and true i3bar will write a infinite array (same as above)
+ to your stdin.
=== Blocks in detail
"separator_block_width": 9
}
------------------------------------------
+
+=== Click events
+
+If enabled i3bar will send you notifications if the user clicks on a block and
+looks like this:
+
+name::
+ Name of the block, if set
+instance::
+ Instance of the block, if set
+x, y::
+ X11 root window coordinates where the click occured
+button:
+ X11 button ID (for example 1 to 3 for left/middle/right mouse button)
+
+*Example*:
+------------------------------------------
+{
+ "name": "ethernet",
+ "instance": "eth0",
+ "button": 1,
+ "x": 1320,
+ "y": 1400
+}
+------------------------------------------
i3 User’s Guide
===============
Michael Stapelberg <michael@i3wm.org>
-February 2013
+March 2013
This document contains all the information you need to configure and use the i3
window manager. If it does not, please check http://faq.i3wm.org/ first, then
=== Manipulating layout
-Use +layout toggle split+, +layout stacking+ or +layout tabbed+ to change the
-current container layout to splith/splitv, stacking or tabbed layout,
-respectively.
+Use +layout toggle split+, +layout stacking+, +layout tabbed+, +layout splitv+
+or +layout splith+ to change the current container layout to splith/splitv,
+stacking, tabbed layout, splitv or splith, respectively.
To make the current window (!) fullscreen, use +fullscreen+, to make
it floating (or tiling again) use +floating enable+ respectively +floating disable+
*Syntax*:
--------------
-layout <tabbed|stacking>
+layout <default|tabbed|stacking|splitv|splith>
layout toggle [split|all]
--------------
#endif
/* For systems without getline, fall back to fgetln */
-#if defined(__APPLE__) || (defined(__FreeBSD__) && __FreeBSD_version < 800000)
+#if defined(__APPLE__)
#define USE_FGETLN
#elif defined(__FreeBSD__)
/* Defining this macro before including stdio.h is necessary in order to have
last;
}
if (!defined($app)) {
- die "Invalid input: “$choice” does not match any application.";
+ warn "Invalid input: “$choice” does not match any application. Trying to execute nevertheless.";
+ $app->{Name} = '';
+ $app->{Exec} = $choice;
+ # We assume that the app is old and does not support startup
+ # notifications because it doesn’t ship a desktop file.
+ $app->{StartupNotify} = 0;
+ $app->{_Location} = '';
}
}
* The signal requested by the client to inform it of theun hidden state of i3bar
*/
int cont_signal;
+
+ /**
+ * Enable click events
+ */
+ bool click_events;
+ bool click_events_init;
} i3bar_child;
/*
*/
void cont_child(void);
+/*
+ * Generates a click event, if enabled.
+ *
+ */
+void send_block_clicked(int button, const char *name, const char *instance, int x, int y);
+
#endif
uint32_t x_offset;
uint32_t x_append;
+ /* Optional */
+ char *name;
+ char *instance;
+
TAILQ_ENTRY(status_block) blocks;
};
#include <yajl/yajl_common.h>
#include <yajl/yajl_parse.h>
#include <yajl/yajl_version.h>
+#include <yajl/yajl_gen.h>
#include "common.h"
yajl_callbacks callbacks;
yajl_handle parser;
+/* JSON generator for stdout */
+yajl_gen gen;
+
typedef struct parser_ctx {
/* True if one of the parsed blocks was urgent */
bool has_urgent;
struct statusline_head statusline_head = TAILQ_HEAD_INITIALIZER(statusline_head);
char *statusline_buffer = NULL;
+int child_stdin;
+
/*
* Stop and free() the stdin- and sigchild-watchers
*
first = TAILQ_FIRST(&statusline_head);
I3STRING_FREE(first->full_text);
FREE(first->color);
+ FREE(first->name);
+ FREE(first->instance);
TAILQ_REMOVE(&statusline_head, first, blocks);
free(first);
}
ctx->block.min_width = (uint32_t)predict_text_width(text);
i3string_free(text);
}
+ if (strcasecmp(ctx->last_map_key, "name") == 0) {
+ char *copy = (char*)malloc(len+1);
+ strncpy(copy, (const char *)val, len);
+ copy[len] = 0;
+ ctx->block.name = copy;
+ }
+ if (strcasecmp(ctx->last_map_key, "instance") == 0) {
+ char *copy = (char*)malloc(len+1);
+ strncpy(copy, (const char *)val, len);
+ copy[len] = 0;
+ ctx->block.instance = copy;
+ }
return 1;
}
cleanup();
}
+void child_write_output(void) {
+ if (child.click_events) {
+ const unsigned char *output;
+ size_t size;
+ yajl_gen_get_buf(gen, &output, &size);
+ write(child_stdin, output, size);
+ write(child_stdin, "\n", 1);
+ yajl_gen_clear(gen);
+ }
+}
+
/*
* Start a child-process with the specified command and reroute stdin.
* We actually start a $SHELL to execute the command so we don't have to care
parser = yajl_alloc(&callbacks, NULL, &parser_context);
#endif
+ gen = yajl_gen_alloc(NULL);
+
if (command != NULL) {
- int fd[2];
- if (pipe(fd) == -1)
- err(EXIT_FAILURE, "pipe(fd)");
+ int pipe_in[2]; /* pipe we read from */
+ int pipe_out[2]; /* pipe we write to */
+
+ if (pipe(pipe_in) == -1)
+ err(EXIT_FAILURE, "pipe(pipe_in)");
+ if (pipe(pipe_out) == -1)
+ err(EXIT_FAILURE, "pipe(pipe_out)");
child.pid = fork();
switch (child.pid) {
ELOG("Couldn't fork(): %s\n", strerror(errno));
exit(EXIT_FAILURE);
case 0:
- /* Child-process. Reroute stdout and start shell */
- close(fd[0]);
+ /* Child-process. Reroute streams and start shell */
+
+ close(pipe_in[0]);
+ close(pipe_out[1]);
- dup2(fd[1], STDOUT_FILENO);
+ dup2(pipe_in[1], STDOUT_FILENO);
+ dup2(pipe_out[0], STDIN_FILENO);
static const char *shell = NULL;
execl(shell, shell, "-c", command, (char*) NULL);
return;
default:
- /* Parent-process. Rerout stdin */
- close(fd[1]);
+ /* Parent-process. Reroute streams */
- dup2(fd[0], STDIN_FILENO);
+ close(pipe_in[1]);
+ close(pipe_out[0]);
+
+ dup2(pipe_in[0], STDIN_FILENO);
+ child_stdin = pipe_out[1];
break;
}
atexit(kill_child_at_exit);
}
+void child_click_events_initialize(void) {
+ if (!child.click_events_init) {
+ yajl_gen_array_open(gen);
+ child_write_output();
+ child.click_events_init = true;
+ }
+}
+
+void child_click_events_key(const char *key) {
+ yajl_gen_string(gen, (const unsigned char *)key, strlen(key));
+}
+
+/*
+ * Generates a click event, if enabled.
+ *
+ */
+void send_block_clicked(int button, const char *name, const char *instance, int x, int y) {
+ if (child.click_events) {
+ child_click_events_initialize();
+
+ yajl_gen_map_open(gen);
+
+ if (name) {
+ child_click_events_key("name");
+ yajl_gen_string(gen, (const unsigned char *)name, strlen(name));
+ }
+
+ if (instance) {
+ child_click_events_key("instance");
+ yajl_gen_string(gen, (const unsigned char *)instance, strlen(instance));
+ }
+
+ child_click_events_key("button");
+ yajl_gen_integer(gen, button);
+
+ child_click_events_key("x");
+ yajl_gen_integer(gen, x);
+
+ child_click_events_key("y");
+ yajl_gen_integer(gen, y);
+
+ yajl_gen_map_close(gen);
+ child_write_output();
+ }
+}
+
/*
* kill()s the child-process (if any). Called when exit()ing.
*
}
void print_usage(char *elf_name) {
- printf("Usage: %s [-b bar_id] [-s sock_path] [-h] [-v]\n", elf_name);
+ printf("Usage: %s -b bar_id [-s sock_path] [-h] [-v]\n", elf_name);
printf("\n");
- printf("--bar_id <bar_id>\tBar ID for which to get the configuration\n");
- printf("-s <sock_path>\tConnect to i3 via <sock_path>\n");
- printf("-h\t\tDisplay this help-message and exit\n");
- printf("-v\t\tDisplay version number and exit\n");
+ printf("-b, --bar_id <bar_id>\tBar ID for which to get the configuration\n");
+ printf("-s, --socket <sock_path>\tConnect to i3 via <sock_path>\n");
+ printf("-h, --help Display this help-message and exit\n");
+ printf("-v, --version Display version number and exit\n");
printf("\n");
printf(" PLEASE NOTE that i3bar will be automatically started by i3\n"
" as soon as there is a 'bar' configuration block in your\n"
static struct option long_opt[] = {
{ "socket", required_argument, 0, 's' },
- { "bar_id", required_argument, 0, 0 },
+ { "bar_id", required_argument, 0, 'b' },
{ "help", no_argument, 0, 'h' },
{ "version", no_argument, 0, 'v' },
{ NULL, 0, 0, 0}
};
- while ((opt = getopt_long(argc, argv, "s:hv", long_opt, &option_index)) != -1) {
+ while ((opt = getopt_long(argc, argv, "b:s:hv", long_opt, &option_index)) != -1) {
switch (opt) {
case 's':
socket_path = expand_path(optarg);
printf("i3bar version " I3_VERSION " © 2010-2011 Axel Wagner and contributors\n");
exit(EXIT_SUCCESS);
break;
- case 0:
- if (!strcmp(long_opt[option_index].name, "bar_id")) {
- FREE(config.bar_id);
- config.bar_id = sstrdup(optarg);
- }
+ case 'b':
+ config.bar_id = sstrdup(optarg);
break;
default:
print_usage(argv[0]);
KEY_VERSION,
KEY_STOP_SIGNAL,
KEY_CONT_SIGNAL,
+ KEY_CLICK_EVENTS,
NO_KEY
} current_key;
default:
break;
}
+
+ return 1;
+}
+
+static int header_boolean(void *ctx, int val) {
+ i3bar_child *child = ctx;
+
+ switch (current_key) {
+ case KEY_CLICK_EVENTS:
+ child->click_events = val;
+ break;
+ default:
+ break;
+ }
+
return 1;
}
current_key = KEY_STOP_SIGNAL;
} else if (CHECK_KEY("cont_signal")) {
current_key = KEY_CONT_SIGNAL;
+ } else if (CHECK_KEY("click_events")) {
+ current_key = KEY_CLICK_EVENTS;
}
return 1;
}
static yajl_callbacks version_callbacks = {
NULL, /* null */
- NULL, /* boolean */
+ &header_boolean, /* boolean */
&header_integer,
NULL, /* double */
NULL, /* number */
}
int32_t x = event->event_x >= 0 ? event->event_x : 0;
+ int32_t original_x = x;
DLOG("Got Button %d\n", event->detail);
switch (event->detail) {
- case 1:
- /* Left Mousbutton. We determine, which button was clicked
- * and set cur_ws accordingly */
- TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) {
- DLOG("x = %d\n", x);
- if (x >= 0 && x < cur_ws->name_width + 10) {
- break;
- }
- x -= cur_ws->name_width + 11;
- }
- if (cur_ws == NULL) {
- return;
- }
- break;
case 4:
/* Mouse wheel up. We select the previous ws, if any.
* If there is no more workspace, don’t even send the workspace
cur_ws = TAILQ_NEXT(cur_ws, tailq);
break;
+ default:
+ /* Check if this event regards a workspace button */
+ TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) {
+ DLOG("x = %d\n", x);
+ if (x >= 0 && x < cur_ws->name_width + 10) {
+ break;
+ }
+ x -= cur_ws->name_width + 11;
+ }
+ if (cur_ws == NULL) {
+ /* No workspace button was pressed.
+ * Check if a status block has been clicked.
+ * This of course only has an effect,
+ * if the child reported bidirectional protocol usage. */
+
+ /* First calculate width of tray area */
+ trayclient *trayclient;
+ int tray_width = 0;
+ TAILQ_FOREACH_REVERSE(trayclient, walk->trayclients, tc_head, tailq) {
+ if (!trayclient->mapped)
+ continue;
+ tray_width += (font.height + 2);
+ }
+
+ int block_x = 0, last_block_x;
+ int offset = (walk->rect.w - (statusline_width + tray_width)) - 10;
+
+ x = original_x - offset;
+ if (x < 0)
+ return;
+
+ struct status_block *block;
+
+ TAILQ_FOREACH(block, &statusline_head, blocks) {
+ last_block_x = block_x;
+ block_x += block->width + block->x_offset + block->x_append;
+
+ if (x <= block_x && x >= last_block_x) {
+ send_block_clicked(event->detail, block->name, block->instance, event->root_x, event->root_y);
+ return;
+ }
+ }
+ return;
+ }
+ if (event->detail != 1)
+ return;
}
/* To properly handle workspace names with double quotes in them, we need
/**
* Create a new container (and attach it to the given parent, if not NULL).
- * This function initializes the data structures and creates the appropriate
- * X11 IDs using x_con_init().
+ * This function only initializes the data structures.
+ *
+ */
+Con *con_new_skeleton(Con *parent, i3Window *window);
+
+
+/* A wrapper for con_new_skeleton, to retain the old con_new behaviour
*
*/
Con *con_new(Con *parent, i3Window *window);
/* The ID of this container before restarting. Necessary to correctly
* interpret back-references in the JSON (such as the focus stack). */
int old_id;
+
+ /* Depth of the container window */
+ uint16_t depth;
};
#endif
*/
Output *get_output_containing(int x, int y);
+/*
+ * In contained_by_output, we check if any active output contains part of the container.
+ * We do this by checking if the output rect is intersected by the Rect.
+ * This is the 2-dimensional counterpart of get_output_containing.
+ * Since we don't actually need the outputs intersected by the given Rect (There could
+ * be many), we just return true or false for convenience.
+ *
+ */
+bool contained_by_output(Rect rect);
+
/**
* Gets the output which is the next one in the given direction.
*
# We ignore comments and 'set' lines (variables).
state IGNORE_LINE:
- end, string
+ line
-> INITIAL
# floating_minimum_size <width> x <height>
# We ignore comments and 'set' lines (variables).
state MODE_IGNORE_LINE:
- end, string
+ line
-> MODE
state MODE_BINDING:
# We ignore comments and 'set' lines (variables).
state BAR_IGNORE_LINE:
- end, string
+ line
-> BAR
state BAR_BAR_COMMAND:
# We ignore comments and 'set' lines (variables).
state BAR_COLORS_IGNORE_LINE:
- end, string
+ line
-> BAR_COLORS
state BAR_COLORS_SINGLE:
/*
* Create a new container (and attach it to the given parent, if not NULL).
- * This function initializes the data structures and creates the appropriate
- * X11 IDs using x_con_init().
+ * This function only initializes the data structures.
*
*/
-Con *con_new(Con *parent, i3Window *window) {
+Con *con_new_skeleton(Con *parent, i3Window *window) {
Con *new = scalloc(sizeof(Con));
new->on_remove_child = con_on_remove_child;
TAILQ_INSERT_TAIL(&all_cons, new, all_cons);
new->window = window;
new->border_style = config.default_border;
new->current_border_width = -1;
+ if (window)
+ new->depth = window->depth;
+ else
+ new->depth = XCB_COPY_FROM_PARENT;
static int cnt = 0;
DLOG("opening window %d\n", cnt);
cnt++;
if ((cnt % (sizeof(colors) / sizeof(char*))) == 0)
cnt = 0;
- if (window)
- x_con_init(new, window->depth);
- else
- x_con_init(new, XCB_COPY_FROM_PARENT);
TAILQ_INIT(&(new->floating_head));
TAILQ_INIT(&(new->nodes_head));
return new;
}
+/* A wrapper for con_new_skeleton, to retain the old con_new behaviour
+ *
+ */
+Con *con_new(Con *parent, i3Window *window) {
+ Con *new = con_new_skeleton(parent, window);
+ x_con_init(new, new->depth);
+ return new;
+}
+
/*
* Attaches the given container to the given parent. This happens when moving
* a container or when inserting a new container at a specific place in the
}
}
+ if (strcmp(token->name, "line") == 0) {
+ while (*walk != '\0' && *walk != '\n' && *walk != '\r')
+ walk++;
+ next_state(token);
+ token_handled = true;
+ linecnt++;
+ walk++;
+ break;
+ }
+
if (strcmp(token->name, "end") == 0) {
//printf("checking for end: *%s*\n", walk);
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
void floating_reposition(Con *con, Rect newrect) {
/* Sanity check: Are the new coordinates on any output? If not, we
* ignore that request. */
- Output *output = get_output_containing(
- newrect.x + (newrect.width / 2),
- newrect.y + (newrect.height / 2));
-
- if (!output) {
+ if (!contained_by_output(newrect)) {
ELOG("No output found at destination coordinates. Not repositioning.\n");
return;
}
}
y(array_close);
+ if (inplace_restart && con->window != NULL) {
+ ystr("depth");
+ y(integer, con->depth);
+ }
+
y(map_close);
}
if (last_key && strcasecmp(last_key, "floating_nodes") == 0) {
DLOG("New floating_node\n");
Con *ws = con_get_workspace(json_node);
- json_node = con_new(NULL, NULL);
+ json_node = con_new_skeleton(NULL, NULL);
json_node->parent = ws;
DLOG("Parent is workspace = %p\n", ws);
} else {
Con *parent = json_node;
- json_node = con_new(NULL, NULL);
+ json_node = con_new_skeleton(NULL, NULL);
json_node->parent = parent;
}
}
if (!parsing_swallows && !parsing_rect && !parsing_window_rect && !parsing_geometry) {
LOG("attaching\n");
con_attach(json_node, json_node->parent, true);
+ LOG("Creating window\n");
+ x_con_init(json_node, json_node->depth);
json_node = json_node->parent;
}
if (parsing_rect)
if (strcasecmp(last_key, "current_border_width") == 0)
json_node->current_border_width = val;
+ if (strcasecmp(last_key, "depth") == 0)
+ json_node->depth = val;
+
if (!parsing_swallows && strcasecmp(last_key, "id") == 0)
json_node->old_id = val;
void init_logging(void) {
if (!errorfilename) {
if (!(errorfilename = get_process_filename("errorlog")))
- ELOG("Could not initialize errorlog\n");
+ fprintf(stderr, "Could not initialize errorlog\n");
else {
errorfile = fopen(errorfilename, "w");
if (fcntl(fileno(errorfile), F_SETFD, FD_CLOEXEC)) {
- ELOG("Could not set close-on-exec flag\n");
+ fprintf(stderr, "Could not set close-on-exec flag\n");
}
}
}
sysconf(_SC_PAGESIZE);
#endif
logbuffer_size = min(physical_mem_bytes * 0.01, shmlog_size);
+#if defined(__FreeBSD__)
+ sasprintf(&shmlogname, "/tmp/i3-log-%d", getpid());
+#else
sasprintf(&shmlogname, "/i3-log-%d", getpid());
+#endif
logbuffer_shm = shm_open(shmlogname, O_RDWR | O_CREAT, S_IREAD | S_IWRITE);
if (logbuffer_shm == -1) {
- ELOG("Could not shm_open SHM segment for the i3 log: %s\n", strerror(errno));
+ fprintf(stderr, "Could not shm_open SHM segment for the i3 log: %s\n", strerror(errno));
return;
}
if (ftruncate(logbuffer_shm, logbuffer_size) == -1) {
close(logbuffer_shm);
- shm_unlink("/i3-log-");
- ELOG("Could not ftruncate SHM segment for the i3 log: %s\n", strerror(errno));
+ shm_unlink(shmlogname);
+ fprintf(stderr, "Could not ftruncate SHM segment for the i3 log: %s\n", strerror(errno));
return;
}
logbuffer = mmap(NULL, logbuffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, logbuffer_shm, 0);
if (logbuffer == MAP_FAILED) {
close(logbuffer_shm);
- shm_unlink("/i3-log-");
- ELOG("Could not mmap SHM segment for the i3 log: %s\n", strerror(errno));
+ shm_unlink(shmlogname);
+ fprintf(stderr, "Could not mmap SHM segment for the i3 log: %s\n", strerror(errno));
logbuffer = NULL;
return;
}
pthread_condattr_t cond_attr;
pthread_condattr_init(&cond_attr);
if (pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED) != 0)
- ELOG("pthread_condattr_setpshared() failed, i3-dump-log -f will not work!\n");
+ fprintf(stderr, "pthread_condattr_setpshared() failed, i3-dump-log -f will not work!\n");
pthread_cond_init(&(header->condvar), &cond_attr);
logwalk = logbuffer + sizeof(i3_shmlog_header);
return NULL;
}
+/*
+ * In contained_by_output, we check if any active output contains part of the container.
+ * We do this by checking if the output rect is intersected by the Rect.
+ * This is the 2-dimensional counterpart of get_output_containing.
+ * Since we don't actually need the outputs intersected by the given Rect (There could
+ * be many), we just return true or false for convenience.
+ *
+ */
+bool contained_by_output(Rect rect){
+ Output *output;
+ int lx = rect.x, uy = rect.y;
+ int rx = rect.x + rect.width, by = rect.y + rect.height;
+ TAILQ_FOREACH(output, &outputs, outputs) {
+ if (!output->active)
+ continue;
+ DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
+ rect.x, rect.y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
+ if (rx >= (int)output->rect.x && lx <= (int)(output->rect.x + output->rect.width) &&
+ by >= (int)output->rect.y && uy <= (int)(output->rect.y + output->rect.height))
+ return true;
+ }
+ return false;
+
+}
+
/*
* Like get_output_next with close_far == CLOSEST_OUTPUT, but wraps.
*
con_toggle_fullscreen(focused, CF_OUTPUT);
}
+ /* If this was 'scratchpad show' without criteria, we check if the
+ * currently focused window is a scratchpad window and should be hidden
+ * again. */
+ if (!con &&
+ (floating = con_inside_floating(focused)) &&
+ floating->scratchpad_state != SCRATCHPAD_NONE) {
+ DLOG("Focused window is a scratchpad window, hiding it.\n");
+ scratchpad_move(focused);
+ return;
+ }
+
/* If this was 'scratchpad show' without criteria, we check if there is a
* unfocused scratchpad on the current workspace and focus it */
Con *walk_con;
Con *focused_ws = con_get_workspace(focused);
TAILQ_FOREACH(walk_con, &(focused_ws->floating_head), floating_windows) {
- if ((floating = con_inside_floating(walk_con)) &&
+ if (!con && (floating = con_inside_floating(walk_con)) &&
floating->scratchpad_state != SCRATCHPAD_NONE &&
floating != con_inside_floating(focused)) {
DLOG("Found an unfocused scratchpad window on this workspace\n");
focused_ws = con_get_workspace(focused);
TAILQ_FOREACH(walk_con, &all_cons, all_cons) {
Con *walk_ws = con_get_workspace(walk_con);
- if (walk_ws &&
+ if (!con && walk_ws &&
!con_is_internal(walk_ws) && focused_ws != walk_ws &&
(floating = con_inside_floating(walk_con)) &&
floating->scratchpad_state != SCRATCHPAD_NONE) {
}
}
- /* If this was 'scratchpad show' without criteria, we check if the
- * currently focused window is a scratchpad window and should be hidden
- * again. */
- if (!con &&
- (floating = con_inside_floating(focused)) &&
- floating->scratchpad_state != SCRATCHPAD_NONE) {
- DLOG("Focused window is a scratchpad window, hiding it.\n");
- scratchpad_move(focused);
+ /* If this was 'scratchpad show' with criteria, we check if the window
+ * is actually in the scratchpad */
+ if (con && con->parent->scratchpad_state == SCRATCHPAD_NONE) {
+ DLOG("Window is not in the scratchpad, doing nothing.\n");
return;
}
} else
con_focus(next);
- ipc_send_workspace_focus_event(workspace, old);
+ ipc_send_workspace_focus_event(workspace, current);
DLOG("old = %p / %s\n", old, (old ? old->name : "(null)"));
/* Close old workspace if necessary. This must be done *after* doing
$expected,
'errors dont harm subsequent statements');
+################################################################################
+# Regression: semicolons end comments, but shouldn’t
+################################################################################
+
+$config = <<'EOT';
+# "foo" client.focused #4c7899 #285577 #ffffff #2e9ef4
+EOT
+
+$expected = <<'EOT';
+
+EOT
+
+is(parser_calls($config),
+ $expected,
+ 'semicolon does not end a comment line');
################################################################################
# Error message with 2+2 lines of context
# Verifies that using criteria to address scratchpad windows works.
use i3test;
-################################################################################
+#####################################################################
# Verify that using scratchpad show with criteria works as expected:
-# When matching a scratchpad window which is visible, it should hide it.
-# When matching a scratchpad window which is on __i3_scratch, it should show it.
-# When matching a non-scratchpad window, it should be a no-op.
-################################################################################
+# - When matching a scratchpad window which is visible,
+# it should hide it.
+# - When matching a scratchpad window which is on __i3_scratch,
+# it should show it.
+# - When matching a non-scratchpad window, it should be a no-op.
+# - When matching a scratchpad window,
+# non-matching windows shouldn't appear
+######################################################################
my $tmp = fresh_workspace;
my $third_window = open_window(name => 'scratch-match');
cmd 'move scratchpad';
-# Verify that using 'scratchpad show' without any matching windows is a no-op.
+#####################################################################
+# Verify that using 'scratchpad show' without any matching windows is
+# a no-op.
+#####################################################################
my $old_focus = get_focused($tmp);
cmd '[title="nomatch"] scratchpad show';
is(get_focused($tmp), $old_focus, 'non-matching criteria have no effect');
+#####################################################################
# Verify that we can use criteria to show a scratchpad window.
+#####################################################################
cmd '[title="scratch-match"] scratchpad show';
my $scratch_focus = get_focused($tmp);
isnt(get_focused($tmp), $scratch_focus, 'matching criteria works');
is(get_focused($tmp), $old_focus, 'focus restored');
+
+#####################################################################
# Verify that we cannot use criteria to show a non-scratchpad window.
+#####################################################################
my $tmp2 = fresh_workspace;
my $non_scratch_window = open_window(name => 'non-scratch');
cmd "workspace $tmp";
is(get_focused($tmp), $old_focus, 'focus still ok');
-cmd '[title="non-match"] scratchpad show';
+cmd '[title="non-scratch"] scratchpad show';
is(get_focused($tmp), $old_focus, 'focus unchanged');
+#####################################################################
+# Verify that non-matching windows doesn't appear
+#####################################################################
+# Subroutine to clear scratchpad
+sub clear_scratchpad {
+ while (scalar @{get_ws('__i3_scratch')->{floating_nodes}}) {
+ cmd 'scratchpad show';
+ cmd 'kill';
+ }
+}
+
+#Start from an empty fresh workspace
+my $empty_ws = fresh_workspace;
+cmd "workspace $empty_ws";
+
+my $no_focused = get_focused($empty_ws);
+cmd '[title="nothingmatchthistitle"] scratchpad show';
+#Check nothing match
+is(get_focused($empty_ws), $no_focused, "no window to focus on");
+
+clear_scratchpad;
+
+open_window(name => "my-scratch-window");
+my $w1_focus = get_focused($empty_ws);
+cmd 'move scratchpad';
+cmd '[title="my-scratch-window"] scratchpad show';
+#Check we created and shown a scratchpad window
+is(get_focused($empty_ws), $w1_focus, "focus on scratchpad window");
+
+#Switching workspace
+my $empty_ws2 = fresh_workspace;
+cmd "workspace $empty_ws2";
+open_window(name => "my-second-scratch-window");
+
+my $w2_focus = get_focused($empty_ws2);
+cmd 'move scratchpad';
+cmd '[title="my-second-scratch-window"] scratchpad show';
+
+#Check we got the correct window
+is(get_focused($empty_ws2), $w2_focus, "focus is on second window");
+
+#####################################################################
+# Verify that 'scratchpad show' correctly hide multiple scratchpad
+# windows
+#####################################################################
+clear_scratchpad;
+
+sub check_floating {
+ my($rws, $n) = @_;
+ my $ws = get_ws($rws);
+ is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
+ is(scalar @{$ws->{floating_nodes}}, $n, "$n floating windows on ws");
+}
+
+my $empty_ws3 = fresh_workspace;
+cmd "workspace $empty_ws3";
+
+check_floating($empty_ws3, 0);
+
+#Creating two scratchpad windows
+open_window(name => "toggle-1");
+cmd 'move scratchpad';
+open_window(name => "toggle-2");
+cmd 'move scratchpad';
+check_floating($empty_ws3, 0);
+#Showing both
+cmd '[title="toggle-"] scratchpad show';
+
+check_floating($empty_ws3, 2);
+
+#Hiding both
+cmd '[title="toggle-"] scratchpad show';
+check_floating($empty_ws3, 0);
+
+#Showing both again
+cmd '[title="toggle-"] scratchpad show';
+check_floating($empty_ws3, 2);
+
+
+#Hiding one
+cmd 'scratchpad show';
+check_floating($empty_ws3, 1);
+
+#Hiding the last
+cmd 'scratchpad show';
+check_floating($empty_ws3, 0);
+
done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Ticket: #990
+# Bug still in: 4.5.1-23-g82b5978
+
+use i3test i3_autostart => 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+fake-outputs 1024x768+0+0,1024x768+1024+0
+EOT
+
+my $pid = launch_with_config($config);
+
+my $i3 = i3(get_socket_path());
+
+$i3->connect()->recv;
+
+################################
+# Workspaces requests and events
+################################
+
+my $focused = get_ws(focused_ws());
+
+# Events
+
+# We are switching to an empty workpspace on the output to the right from an empty workspace on the output on the left, so we expect
+# to receive "init", "focus", and "empty".
+my $focus = AnyEvent->condvar;
+$i3->subscribe({
+ workspace => sub {
+ my ($event) = @_;
+ if ($event->{change} eq 'focus') {
+ # Check that we have the old and new workspace
+ $focus->send(
+ $event->{current}->{name} == '2' &&
+ $event->{old}->{name} == $focused->{name}
+ );
+ }
+ }
+})->recv;
+
+cmd 'focus output right';
+
+my $t;
+$t = AnyEvent->timer(
+ after => 0.5,
+ cb => sub {
+ $focus->send(0);
+ }
+);
+
+ok($focus->recv, 'Workspace "focus" event received');
+
+exit_gracefully($pid);
+
+done_testing;