-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
corresponds to tiling or stacking mode in i3. Therefore, why not let i3 do this
for you? Certainly, it’s faster than you could ever do it.
-The problem with most tiling window managers is that they are too unflexible.
+The problem with most tiling window managers is that they are too inflexible.
In my opinion, a window manager is just another tool, and similar to vim which
can edit all kinds of text files (like source code, HTML, …) and is not limited
to a specific file type, a window manager should not limit itself to a certain
* The override_redirect must not be set. Windows with override_redirect shall
not be managed by a window manager
-Afterwards, i3 gets the intial geometry and reparents the window (see
+Afterwards, i3 gets the initial geometry and reparents the window (see
`reparent_window()`) if it wasn’t already managed.
Reparenting means that for each window which is reparented, a new window,
== What happens when an application is started?
-i3 does not care for applications. All it notices is when new windows are
+i3 does not care about applications. All it notices is when new windows are
mapped (see `src/handlers.c`, `handle_map_request()`). The window is then
reparented (see section "Manage windows").
==== Dock area layout
-This is a special case. Users cannot chose the dock area layout, but it will be
+This is a special case. Users cannot choose the dock area layout, but it will be
set for the dock area containers. In the dockarea layout (at the moment!),
windows will be placed above each other.
== 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
+}
+------------------------------------------
The multi-monitor situation
===========================
-Michael Stapelberg <michael+i3@stapelberg.de>
-September 2011
+Michael Stapelberg <michael@i3wm.org>
+April 2013
-…or: oh no, I have an nVidia graphics card!
+Please upgrade your nVidia driver to version 302.17 or newer and i3 will just
+work. This document is kept around for historic reasons only.
== The quick fix
If you are using the nVidia binary graphics driver (also known as 'blob')
-you need to use the +--force-xinerama+ flag (in your .xsession) when starting
-i3, like so:
+before version 302.17, you need to use the +--force-xinerama+ flag (in your
+.xsession) when starting i3, like so:
.Example:
----------------------------------------------
$ less latest/i3-log-for-04-floating.t
----------------------------------------
+If your attempt to run the tests with a bare call to ./complete-run.pl fails, try this:
+
+---------------------------------------------------
+$ ./complete-run.pl --parallel=1 --keep-xdummy-output
+---------------------------------------------------
+
+One common cause of failures is not having the X dummy server module
+installed. Under Debian and Ubuntu this is the package
++xserver-xorg-video-dummy+.
+
==== IPC interface
The testsuite makes extensive use of the IPC (Inter-Process Communication)
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
== Using i3
Throughout this guide, the keyword +$mod+ will be used to refer to the
-configured modifier. This is the Alt key (Mod1) by default, with windows (Mod4)
-being a popular alternative.
+configured modifier. This is the Alt key (Mod1) by default, with the Windows
+key (Mod4) being a popular alternative.
=== Opening terminals and moving around
=== Restarting i3 inplace
-To restart i3 inplace (and thus get into a clean state if there is a bug, or
+To restart i3 in place (and thus get into a clean state if there is a bug, or
to upgrade to a newer version of i3) you can use +$mod+Shift+r+.
=== Exiting i3
=== Floating
-Floating mode is the opposite of tiling mode. The position and size of a window
-are not managed by i3, but by you. Using this mode violates the tiling
-paradigm but can be useful for some corner cases like "Save as" dialog
-windows, or toolbar windows (GIMP or similar). Those windows usually set the
-appropriate hint and are opened in floating mode by default.
+Floating mode is the opposite of tiling mode. The position and size of
+a window are not managed automatically by i3, but manually by
+you. Using this mode violates the tiling paradigm but can be useful
+for some corner cases like "Save as" dialog windows, or toolbar
+windows (GIMP or similar). Those windows usually set the appropriate
+hint and are opened in floating mode by default.
You can toggle floating mode for a window by pressing +$mod+Shift+Space+. By
dragging the window’s titlebar with your mouse you can move the window
[[configuring]]
== Configuring i3
-This is where the real fun begins ;-). Most things are very dependant on your
+This is where the real fun begins ;-). Most things are very dependent on your
ideal working environment so we can’t make reasonable defaults for them.
While not using a programming language for the configuration, i3 stays
=== Interprocess communication
-i3 uses unix sockets to provide an IPC interface. This allows third-party
+i3 uses Unix sockets to provide an IPC interface. This allows third-party
programs to get information from i3, such as the current workspaces
(to display a workspace bar), and to control i3.
[[command_criteria]]
-Furthermore, you can change the scope of a command, that is, which containers
+Furthermore, you can change the scope of a command - that is, which containers
should be affected by that command, by using various criteria. These are
prefixed in square brackets to every command. If you want to kill all windows
which have the class Firefox, use:
=== 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]
--------------
External workspace bars
=======================
-Michael Stapelberg <michael+i3@stapelberg.de>
-May 2010
+Michael Stapelberg <michael@i3wm.org>
+April 2013
-This document describes why the internal workspace bar is minimal and how an
-external workspace bar can be used. It explains the concepts using +i3-wsbar+
-as the reference implementation.
+i3 comes with i3bar by default, a simple bar that is sufficient for most users.
+In case you are unhappy with it, this document explains how to use a different,
+external workspace bar. Note that we do not provide support for external
+programs.
== Internal and external bars
The internal workspace bar of i3 is meant to be a reasonable default so that
you can use i3 without having too much hassle when setting it up. It is quite
-simple and intended to stay this way. So, there is no way to display your own
-information in this bar (unlike dwm, wmii, awesome, …).
-
-We chose not to implement such a mechanism because that would be duplicating
-already existing functionality of tools such as dzen2, xmobar and similar.
-Instead, you should disable the internal bar and use an external workspace bar
-(which communicates with i3 through its IPC interface).
+simple and intended to stay this way.
== dock mode
specific screen. Also, you don’t want to place the workspace bar somewhere
in your layout by hand. This is where dock mode comes in: When a program sets
the appropriate hint (_NET_WM_WINDOW_TYPE_DOCK), it will be managed in dock
-mode by i3. That means it will be placed at the bottom of the screen (while
-other edges of the screen are possible in the NetWM standard, this is not yet
-implemented in i3), it will not overlap any other window and it will be on
-every workspace for the specific screen it was placed on initially.
+mode by i3. That means it will be placed at the bottom or top of the screen
+(while other edges of the screen are possible in the NetWM standard, this is
+not yet implemented in i3), it will not overlap any other window and it will be
+on every workspace for the specific screen it was placed on initially.
== The IPC interface
LVDS-1, …) configuration. In the other direction, the program has to be able
to switch to specific workspaces.
-By default, the IPC interface is enabled and places its UNIX socket in
-+~/.i3/ipc.sock+.
+By default, the IPC interface is enabled and you can get the path to the socket
+by calling +i3 --get-socketpath+.
To learn more about the protocol which is used for IPC, see +docs/ipc+.
the resolution of any of your screens (or enable/disable an output), the bars
will be adjusted properly.
-== i3-wsbar, the reference implementation
+== i3-wsbar, an example implementation
-Please keep in mind that +i3-wsbar+ is just a reference implementation. It is
-shipped with i3 to have a reasonable default. Thus, +i3-wsbar+ is designed to
-work well with dzen2 and there are no plans to make it more generic.
++i3-wsbar+ used to be the reference implementation before we had +i3bar+.
+Nowadays, it is not shipped with release tarballs, but you can still get it at
+http://code.stapelberg.de/git/i3/tree/contrib/i3-wsbar
=== The big picture
The most common reason to use an external workspace bar is to integrate system
-information such as what +i3status+ provides into the workspace bar (to save
-screen space). So, we have +i3status+ or a similar program, which only provides
+information such as what +i3status+ or +conky+ provide into the workspace bar.
+So, we have +i3status+ or a similar program, which only provides
text output (formatted in some way). To display this text nicely on the screen,
there are programs such as dzen2, xmobar and similar. We will stick to dzen2
from here on. So, we have the output of i3status, which needs to go into dzen2
------------------------------------------
i3status | i3-wsbar -c "dzen2 -x %x -dock"
------------------------------------------
-
-It is recommended to place the above command in your i3 configuration file
-to start it automatically with i3.
#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;
+#if YAJL_MAJOR < 2
+ unsigned int size;
+#else
+ size_t size;
+#endif
+ 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
yajl_parser_config parse_conf = { 0, 0 };
parser = yajl_alloc(&callbacks, &parse_conf, NULL, (void*)&parser_context);
+
+ gen = yajl_gen_alloc(NULL, NULL);
#else
parser = yajl_alloc(&callbacks, NULL, &parser_context);
+
+ gen = yajl_gen_alloc(NULL);
#endif
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 */
/* The name of current binding mode */
static mode binding;
+/* Indicates whether a new binding mode was recently activated */
+bool activated_mode = false;
+
/* The parsed colors */
struct xcb_colors_t {
uint32_t bar_fg;
realloc_sl_buffer();
/* Clear the statusline pixmap. */
- xcb_rectangle_t rect = { 0, 0, root_screen->width_in_pixels, font.height };
+ xcb_rectangle_t rect = { 0, 0, root_screen->width_in_pixels, font.height + 2 };
xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect);
/* Draw the text of each block. */
}
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
modstate = mods & config.modifier;
}
-#define DLOGMOD(modmask, status, barfunc) \
+#define DLOGMOD(modmask, status) \
do { \
switch (modmask) { \
case ShiftMask: \
DLOG("Mod5Mask got " #status "!\n"); \
break; \
} \
- barfunc(); \
} while (0)
if (modstate != mod_pressed) {
if (modstate == 0) {
- DLOGMOD(config.modifier, released, hide_bars);
+ DLOGMOD(config.modifier, released);
+ if (!activated_mode)
+ hide_bars();
} else {
- DLOGMOD(config.modifier, pressed, unhide_bars);
+ DLOGMOD(config.modifier, pressed);
+ activated_mode = false;
+ unhide_bars();
}
mod_pressed = modstate;
}
outputs_walk->bargc,
MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0,
MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3,
- MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font.height);
+ MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font.height + 2);
}
if (config.disable_ws) {
set_font_colors(outputs_walk->bargc, fg_color, bg_color);
draw_text(binding.name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 3, binding.width);
+
+ unhide = true;
}
i = 0;
void set_current_mode(struct mode *current) {
I3STRING_FREE(binding.name);
binding = *current;
+ activated_mode = binding.name != NULL;
return;
}
/**
* 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.
*
* Raises the specified container in the internal stack of X windows. The
* next call to x_push_changes() will make the change visible in X11.
*
+ * If above_all is true, the X11 window will be raised to the top
+ * of the stack. This should only be used for precisely one fullscreen
+ * window per output.
+ *
*/
-void x_raise_con(Con *con);
+void x_raise_con(Con *con, bool above_all);
/**
* Sets the WM_NAME property (so, no UTF8, but used only for debugging anyways)
# 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:
* We use a hand-written parser instead of lex/yacc because our commands are
* easy for humans, not for computers. Thus, it’s quite hard to specify a
* context-free grammar for the commands. A PEG grammar would be easier, but
- * there’s downsides to every PEG parser generator I have come accross so far.
+ * there’s downsides to every PEG parser generator I have come across so far.
*
* This parser is basically a state machine which looks for literals or strings
* and can push either on a stack. After identifying a literal or string, it
/*
* 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 *fullscreen = con_get_fullscreen_con(ws, CF_OUTPUT);
if (fullscreen) {
fullscreen->rect = con->rect;
- x_raise_con(fullscreen);
+ x_raise_con(fullscreen, true);
render_con(fullscreen, true);
return;
}
DLOG("child at (%d, %d) with (%d x %d)\n",
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
- x_raise_con(child);
+ x_raise_con(child, false);
render_con(child, false);
}
}
}
if (fullscreen) {
fullscreen->rect = rect;
- x_raise_con(fullscreen);
+ x_raise_con(fullscreen, false);
render_con(fullscreen, true);
return;
}
}
DLOG("floating child at (%d,%d) with %d x %d\n",
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
- x_raise_con(child);
+ x_raise_con(child, false);
render_con(child, false);
}
}
DLOG("child at (%d, %d) with (%d x %d)\n",
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
- x_raise_con(child);
+ x_raise_con(child, false);
render_con(child, false);
i++;
}
/* in a stacking or tabbed container, we ensure the focused client is raised */
if (con->layout == L_STACKED || con->layout == L_TABBED) {
TAILQ_FOREACH_REVERSE(child, &(con->focus_head), focus_head, focused)
- x_raise_con(child);
+ x_raise_con(child, false);
if ((child = TAILQ_FIRST(&(con->focus_head)))) {
/* By rendering the stacked container again, we handle the case
* that we have a non-leaf-container inside the stack. In that
* top of every stack window. That way, when a new window is opened in
* the stack, the old window will not obscure part of the decoration
* (it’s unmapped afterwards). */
- x_raise_con(con);
+ x_raise_con(con, false);
}
}
}
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
bool mapped;
bool unmap_now;
bool child_mapped;
+ bool above_all;
/** The con for which this state is. */
Con *con;
xcb_configure_window(conn, prev->id, mask, values);
}
+ if (state->above_all) {
+ DLOG("above all: 0x%08x\n", state->id);
+ xcb_configure_window(conn, state->id, XCB_CONFIG_WINDOW_STACK_MODE, (uint32_t[]){ XCB_STACK_MODE_ABOVE });
+ }
state->initial = false;
}
* Raises the specified container in the internal stack of X windows. The
* next call to x_push_changes() will make the change visible in X11.
*
+ * If above_all is true, the X11 window will be raised to the top
+ * of the stack. This should only be used for precisely one fullscreen
+ * window per output.
+ *
*/
-void x_raise_con(Con *con) {
+void x_raise_con(Con *con, bool above_all) {
con_state *state;
state = state_for_frame(con->frame);
//DLOG("raising in new stack: %p / %s / %s / xid %08x\n", con, con->name, con->window ? con->window->name_json : "", state->id);
+ state->above_all = above_all;
+
CIRCLEQ_REMOVE(&state_head, state, state);
CIRCLEQ_INSERT_HEAD(&state_head, state, state);
}
$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;