- sudo /bin/sh -c 'cpanm -n -v X11::XCB || true'
- sudo /bin/sh -c 'cpanm -n -v AnyEvent::I3 || true'
script:
- - make -j
+ - CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Werror" make -j
- (cd testcases && xvfb-run ./complete-run.pl --parallel=1 || (cat latest/complete-run.log; false))
- clang-format-3.5 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false)
--- /dev/null
+
+ ┌──────────────────────────────┐
+ │ Release notes for i3 v4.10.1 │
+ └──────────────────────────────┘
+
+This is i3 v4.10.1. This version is considered stable. All users of i3 are
+strongly encouraged to upgrade.
+
+This release contains mostly bugfixes, but we felt it was necessary since there
+are two important changes in behavior: we have reverted the pango markup
+parsing by default (introduced with i3 v4.9) and the change in how the
+“workspace” command behaves (introduced with i3 v4.9). Both of them broke some
+user’s setups, which is not acceptable. In order to help us avoid such mistakes
+in the future, please consider using the i3 git version — it is typically
+stable.
+
+PS: The v4.10 release did not contain any of the commits we meant to release
+due to a human error in our release automation. Hence the v4.10.1 release.
+
+ ┌────────────────────────────┐
+ │ Changes in i3 v4.10.1 │
+ └────────────────────────────┘
+
+ • i3bar: cut long statuslines from the left
+ • i3bar: add support for the short_text property
+ • i3-sensible-terminal: launch i3-nagbar when no terminal is found
+ • i3-config-wizard: switch modifier on key up/down
+ • docs/layout-saving: added a troubleshooting section
+ • docs: degender all the terms
+ • Revert "Workspace command number selection"
+ • don’t parse blocks as markup by default
+ • Allow escaping backslashes in commands.
+ • switch default font from “DejaVu Sans Mono 8” to “monospace 8”, which is
+ typically a synonym, except for users who prefer a different font.
+ • When renaming a workspace, look for assignments and move the renamed
+ workspace to the appropriate output.
+ • i3-save-tree: make --workspace optional by defaulting to the focused
+ workspace
+ • Allow nop command without argument
+
+ ┌────────────────────────────┐
+ │ Bugfixes │
+ └────────────────────────────┘
+
+ • i3bar: buffer the statusline to avoid flickering
+ • i3bar: fix click events for workspace buttons with long statusline
+ • i3bar: set correct initial position when reconfiguring
+ • i3bar: reconfigure strut partial on reload
+ • i3-nagbar: fix sizes/positioning on hi-dpi displays
+ • i3-config-wizard: fix sizes/positioning on hi-dpi displays
+ • i3-input: fix sizes/positioning on hi-dpi displays
+ • Fix scrolling in window decoration with hidden cursor.
+ • workspace rename focus mismatch
+ • Don’t overwrite border width when already set (placeholders).
+ • fix a segfault during config file validation
+ • Restore placeholder windows after restarting.
+ • Don’t focus placeholder windows.
+
+ ┌────────────────────────────┐
+ │ Thanks! │
+ └────────────────────────────┘
+
+Thanks for testing, bugfixes, discussions and everything I forgot go out to:
+
+ Chih-Chyuan Hwang, Deiz, Diana Dinosaur, Ingo Bürk, Michael Hofmann,
+ Michael Tipton, Micha Rosenbaum, shdown, Tony Crisci
+
+-- Michael Stapelberg, 2015-03-29
+++ /dev/null
-
- ┌──────────────────────────────┐
- │ Release notes for i3 v4.9.1 │
- └──────────────────────────────┘
-
-This is i3 v4.9.1. This version is considered stable. All users of i3 are
-strongly encouraged to upgrade.
-
-This is a bugfix release for i3 v4.9.
-
- ┌────────────────────────────┐
- │ Bugfixes │
- └────────────────────────────┘
-
- • i3bar: fix incorrect y-offset for text
- • fix key bindings on big-endian platforms
- • fix key bindings using Mode_switch
- • fix keyboard layout change detection
- • revert "Handle WM_CHANGE_STATE requests for iconic state" (fixes problems
- with application windows disappearing, like SDL-based games when switching
- workspaces)
- • insert id-based match at HEAD, not TAIL (fixes window swallowing not
- working when the criteria match the placeholder window)
- • improve error messages on failing commands
- • replace ~ in filepath when calling append_layout
- • properly error out when the layout file cannot be read
-
- ┌────────────────────────────┐
- │ Thanks! │
- └────────────────────────────┘
-
-Thanks for testing, bugfixes, discussions and everything I forgot go out to:
-
- Steven McDonald, Ton van den Heuvel, Ingo Bürk
-
--- Michael Stapelberg, 2015-03-07
-i3-wm (4.9.2-1) experimental; urgency=medium
+i3-wm (4.10.2-1) experimental; urgency=medium
* NOT YET RELEASED.
- -- Michael Stapelberg <stapelberg@debian.org> Sat, 07 Mar 2015 20:31:31 +0100
+ -- Michael Stapelberg <stapelberg@debian.org> Sun, 29 Mar 2015 19:10:38 +0200
+
+i3-wm (4.10.1-1) experimental; urgency=medium
+
+ * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org> Sun, 29 Mar 2015 18:54:07 +0200
+
+i3-wm (4.10-1) experimental; urgency=medium
+
+ * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org> Sun, 29 Mar 2015 17:46:09 +0200
i3-wm (4.9.1-1) experimental; urgency=medium
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, x11-utils
Provides: x-window-manager
-Suggests: rxvt-unicode | x-terminal-emulator
-Recommends: xfonts-base, fonts-dejavu-core, libanyevent-i3-perl (>= 0.12), libjson-xs-perl
+Recommends: xfonts-base, fonts-dejavu-core, libanyevent-i3-perl (>= 0.12), libjson-xs-perl, rxvt-unicode | x-terminal-emulator
Description: improved dynamic tiling window manager
Key features of i3 are good documentation, reasonable defaults (changeable in
a simple configuration file) and good multi-monitor support. The user
=== Blocks in detail
full_text::
- The most simple block you can think of is one which just includes the
- only required key, the +full_text+ key. i3bar will display the string
- value parsed as
- https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup].
+ The +full_text+ will be displayed by i3bar on the status line. This is the
+ only required key.
short_text::
Where appropriate, the +short_text+ (string) entry should also be
provided. It will be used in case the status line needs to be shortened
is 9 pixels), since the separator line is drawn in the middle.
markup::
A string that indicates how the text of the block should be parsed. Set to
- +"pango"+ to use https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup]
- (default). Set to +"none"+ to not use any markup.
+ +"pango"+ to use https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup].
+ Set to +"none"+ to not use any markup (default).
If you want to put in your own entries into a block, prefix the key with an
underscore (_). i3bar will ignore all keys it doesn’t understand, and prefixing
for this purpose: It lets you input a command and sends the command to i3. It
can also prefix this command and display a custom prompt for the input dialog.
+The additional +--toggle+ option will remove the mark if the window already has
+this mark, add it if the window has none or replace the current mark if it has
+another mark.
+
*Syntax*:
------------------------------
-mark identifier
+mark [--toggle] identifier
[con_mark="identifier"] focus
unmark identifier
------------------------------
bindsym mod4+s [title="^Sup ::"] scratchpad show
------------------------------------------------
+=== Nop
+
+There is a no operation command +nop+ which allows you to override default
+behavior. This can be useful for, e.g., disabling a focus change on clicks with
+the middle mouse button.
+
+The optional +comment+ argument is ignored, but will be printed to the log file
+for debugging purposes.
+
+*Syntax*:
+---------------
+nop [<comment>]
+---------------
+
+*Example*:
+----------------------------------------------
+# Disable focus change for clicks on titlebars
+# with the middle mouse button
+bindsym button2 nop
+----------------------------------------------
+
=== i3bar control
There are two options in the configuration of each i3bar instance that can be
#include "xcb.h"
#include "libi3.h"
+#define row_y(row) \
+ (((row)-1) * font.height + logical_px(4))
+#define window_height() \
+ (row_y(15) + font.height)
+
enum { STEP_WELCOME,
STEP_GENERATE } current_step = STEP_WELCOME;
enum { MOD_Mod1,
static xcb_get_modifier_mapping_reply_t *modmap_reply;
static i3Font font;
static i3Font bold_font;
+static int char_width;
static char *socket_path;
static xcb_window_t win;
static xcb_pixmap_t pixmap;
void debuglog(char *fmt, ...) {
}
-/*
- * This function resolves ~ in pathnames.
- * It may resolve wildcards in the first part of the path, but if no match
- * or multiple matches are found, it just returns a copy of path as given.
- *
- */
-static char *resolve_tilde(const char *path) {
- static glob_t globbuf;
- char *head, *tail, *result;
-
- tail = strchr(path, '/');
- head = strndup(path, tail ? (size_t)(tail - path) : strlen(path));
-
- int res = glob(head, GLOB_TILDE, NULL, &globbuf);
- free(head);
- /* no match, or many wildcard matches are bad */
- if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1)
- result = strdup(path);
- else if (res != 0) {
- err(1, "glob() failed");
- } else {
- head = globbuf.gl_pathv[0];
- result = calloc(1, strlen(head) + (tail ? strlen(tail) : 0) + 1);
- strncpy(result, head, strlen(head));
- if (tail)
- strncat(result, tail, strlen(tail));
- }
- globfree(&globbuf);
-
- return result;
-}
-
/*
* Handles expose events, that is, draws the window contents.
*
*/
static int handle_expose() {
/* re-draw the background */
- xcb_rectangle_t border = {0, 0, 300, (15 * font.height) + 8};
+ xcb_rectangle_t border = {0, 0, logical_px(300), window_height()};
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")});
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
#define txt(x, row, text) \
draw_text_ascii(text, pixmap, pixmap_gc, \
- x, (row - 1) * font.height + 4, 300 - x * 2)
+ x, row_y(row), logical_px(500) - x * 2)
if (current_step == STEP_WELCOME) {
/* restore font color */
set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
- txt(10, 2, "You have not configured i3 yet.");
- txt(10, 3, "Do you want me to generate ~/.i3/config?");
- txt(85, 5, "Yes, generate ~/.i3/config");
- txt(85, 7, "No, I will use the defaults");
+ txt(logical_px(10), 2, "You have not configured i3 yet.");
+ txt(logical_px(10), 3, "Do you want me to generate a config at");
+
+ char *msg;
+ sasprintf(&msg, "%s?", config_path);
+ txt(logical_px(10), 4, msg);
+ free(msg);
+
+ txt(logical_px(85), 6, "Yes, generate the config");
+ txt(logical_px(85), 8, "No, I will use the defaults");
/* green */
set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000"));
- txt(25, 5, "<Enter>");
+ txt(logical_px(25), 6, "<Enter>");
/* red */
set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000"));
- txt(31, 7, "<ESC>");
+ txt(logical_px(31), 8, "<ESC>");
}
if (current_step == STEP_GENERATE) {
set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
- txt(10, 2, "Please choose either:");
- txt(85, 4, "Win as default modifier");
- txt(85, 5, "Alt as default modifier");
- txt(10, 7, "Afterwards, press");
- txt(85, 9, "to write ~/.i3/config");
- txt(85, 10, "to abort");
+ txt(logical_px(10), 2, "Please choose either:");
+ txt(logical_px(85), 4, "Win as default modifier");
+ txt(logical_px(85), 5, "Alt as default modifier");
+ txt(logical_px(10), 7, "Afterwards, press");
+ txt(logical_px(85), 9, "to write the config");
+ txt(logical_px(85), 10, "to abort");
/* the not-selected modifier */
if (modifier == MOD_Mod4)
- txt(31, 5, "<Alt>");
+ txt(logical_px(31), 5, "<Alt>");
else
- txt(31, 4, "<Win>");
+ txt(logical_px(31), 4, "<Win>");
/* the selected modifier */
set_font(&bold_font);
set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
if (modifier == MOD_Mod4)
- txt(10, 4, "-> <Win>");
+ txt(logical_px(10), 4, "-> <Win>");
else
- txt(10, 5, "-> <Alt>");
+ txt(logical_px(10), 5, "-> <Alt>");
/* green */
set_font(&font);
set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000"));
- txt(25, 9, "<Enter>");
+ txt(logical_px(25), 9, "<Enter>");
/* red */
set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000"));
- txt(31, 10, "<ESC>");
+ txt(logical_px(31), 10, "<ESC>");
}
/* Copy the contents of the pixmap to the real window */
- xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, 500);
+ xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, logical_px(500), logical_px(500));
xcb_flush(conn);
return 1;
finish();
}
+ /* Swap between modifiers when up or down is pressed. */
+ if (sym == XK_Up || sym == XK_Down) {
+ modifier = (modifier == MOD_Mod1) ? MOD_Mod4 : MOD_Mod1;
+ handle_expose();
+ }
+
/* cancel any time */
if (sym == XK_Escape)
exit(0);
if (current_step != STEP_GENERATE)
return;
- if (event->event_x >= 32 && event->event_x <= 68 &&
- event->event_y >= 45 && event->event_y <= 54) {
+ if (event->event_x < logical_px(32) ||
+ event->event_x > (logical_px(32) + char_width * 5))
+ return;
+
+ if (event->event_y >= row_y(4) && event->event_y <= (row_y(4) + font.height)) {
modifier = MOD_Mod4;
handle_expose();
}
- if (event->event_x >= 32 && event->event_x <= 68 &&
- event->event_y >= 56 && event->event_y <= 70) {
+ if (event->event_y >= row_y(5) && event->event_y <= (row_y(5) + font.height)) {
modifier = MOD_Mod1;
handle_expose();
}
}
int main(int argc, char *argv[]) {
- config_path = resolve_tilde("~/.i3/config");
+ char *xdg_config_home;
socket_path = getenv("I3SOCK");
- char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
- char *patternbold = "-misc-fixed-bold-r-normal--13-120-75-75-C-70-iso10646-1";
+ char *pattern = "pango:monospace 8";
+ char *patternbold = "pango:monospace bold 8";
int o, option_index = 0;
static struct option long_options[] = {
}
}
- /* Check if the destination config file does not exist but the path is
- * writable. If not, exit now, this program is not useful in that case. */
- struct stat stbuf;
- if (stat(config_path, &stbuf) == 0) {
- printf("The config file \"%s\" already exists. Exiting.\n", config_path);
+ char *path = get_config_path(NULL, false);
+ if (path != NULL) {
+ printf("The config file \"%s\" already exists. Exiting.\n", path);
+ free(path);
return 0;
}
- /* Create ~/.i3 if it does not yet exist */
- char *config_dir = resolve_tilde("~/.i3");
+ /* Always write to $XDG_CONFIG_HOME/i3/config by default. */
+ if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
+ xdg_config_home = "~/.config";
+
+ xdg_config_home = resolve_tilde(xdg_config_home);
+ sasprintf(&config_path, "%s/i3/config", xdg_config_home);
+
+ /* Create $XDG_CONFIG_HOME/i3 if it does not yet exist */
+ char *config_dir;
+ struct stat stbuf;
+ sasprintf(&config_dir, "%s/i3", xdg_config_home);
if (stat(config_dir, &stbuf) != 0)
- if (mkdir(config_dir, 0755) == -1)
- err(1, "mkdir(%s) failed", config_dir);
+ if (!mkdirp(config_dir))
+ err(EXIT_FAILURE, "mkdirp(%s) failed", config_dir);
free(config_dir);
+ free(xdg_config_home);
int fd;
if ((fd = open(config_path, O_CREAT | O_RDWR, 0644)) == -1) {
font = load_font(pattern, true);
bold_font = load_font(patternbold, true);
+ /* Determine character width in the default font. */
+ set_font(&font);
+ char_width = predict_text_width(i3string_from_utf8("a"));
+
/* Open an input window */
win = xcb_generate_id(conn);
xcb_create_window(
conn,
XCB_COPY_FROM_PARENT,
- win, /* the window id */
- root, /* parent == root */
- 490, 297, 300, 205, /* dimensions */
- 0, /* X11 border = 0, we draw our own */
+ win, /* the window id */
+ root, /* parent == root */
+ logical_px(490), logical_px(297), logical_px(300), window_height(), /* dimensions */
+ 0, /* X11 border = 0, we draw our own */
XCB_WINDOW_CLASS_INPUT_OUTPUT,
XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
/* Create pixmap */
pixmap = xcb_generate_id(conn);
pixmap_gc = xcb_generate_id(conn);
- xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, 500);
+ xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, logical_px(500), logical_px(500));
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
/* Grab the keyboard to get all input */
* of the log. */
wrap_count = header->wrap_count;
const int len = (logbuffer + header->offset_last_wrap) - walk;
- if (write(STDOUT_FILENO, walk, len) != len)
- err(EXIT_FAILURE, "write()");
+ swrite(STDOUT_FILENO, walk, len);
walk = logbuffer + sizeof(i3_shmlog_header);
return 1;
}
static void print_till_end(void) {
check_for_wrap();
const int len = (logbuffer + header->offset_next_write) - walk;
- const int n = write(STDOUT_FILENO, walk, len);
- if (len != n)
- err(EXIT_FAILURE, "write()");
- if (n > 0) {
- walk += n;
- }
+ swrite(STDOUT_FILENO, walk, len);
+ walk += len;
}
int main(int argc, char *argv[]) {
printf("expose!\n");
/* re-draw the background */
- xcb_rectangle_t border = {0, 0, 500, font.height + 8}, inner = {2, 2, 496, font.height + 8 - 4};
+ xcb_rectangle_t border = {0, 0, logical_px(500), font.height + logical_px(8)},
+ inner = {logical_px(2), logical_px(2), logical_px(496), font.height + logical_px(8) - logical_px(4)};
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#FF0000")});
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")});
/* draw the prompt … */
if (prompt != NULL) {
- draw_text(prompt, pixmap, pixmap_gc, 4, 4, 492);
+ draw_text(prompt, pixmap, pixmap_gc, logical_px(4), logical_px(4), logical_px(492));
}
/* … and the text */
if (input_position > 0) {
i3String *input = i3string_from_ucs2(glyphs_ucs, input_position);
- draw_text(input, pixmap, pixmap_gc, prompt_offset + 4, 4, 492);
+ draw_text(input, pixmap, pixmap_gc, prompt_offset + logical_px(4), logical_px(4), logical_px(492));
i3string_free(input);
}
/* Copy the contents of the pixmap to the real window */
- xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font.height + 8);
+ xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, logical_px(500), font.height + logical_px(8));
xcb_flush(conn);
return 1;
static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
printf("Keypress %d, state raw = %d\n", event->detail, event->state);
+ // TODO: port the input handling code from i3lock once libxkbcommon ≥ 0.5.0
+ // is available in distros.
+
/* See the documentation of xcb_key_symbols_get_keysym for this one.
* Basically: We get either col 0 or col 1, depending on whether shift is
* pressed. */
int main(int argc, char *argv[]) {
format = strdup("%s");
socket_path = getenv("I3SOCK");
- char *pattern = sstrdup("-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1");
+ char *pattern = sstrdup("pango:monospace 8");
int o, option_index = 0;
static struct option long_options[] = {
xcb_create_window(
conn,
XCB_COPY_FROM_PARENT,
- win, /* the window id */
- root, /* parent == root */
- 50, 50, 500, font.height + 8, /* dimensions */
- 0, /* X11 border = 0, we draw our own */
+ win, /* the window id */
+ root, /* parent == root */
+ logical_px(50), logical_px(50), logical_px(500), font.height + logical_px(8), /* dimensions */
+ 0, /* X11 border = 0, we draw our own */
XCB_WINDOW_CLASS_INPUT_OUTPUT,
XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK,
/* Create pixmap */
pixmap = xcb_generate_id(conn);
pixmap_gc = xcb_generate_id(conn);
- xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + 8);
+ xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, logical_px(500), font.height + logical_px(8));
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
/* Set input focus (we have override_redirect=1, so the wm will not do
i3_nagbar_SOURCES := $(wildcard i3-nagbar/*.c)
i3_nagbar_HEADERS := $(wildcard i3-nagbar/*.h)
-i3_nagbar_CFLAGS = $(XCB_CFLAGS) $(PANGO_CFLAGS)
-i3_nagbar_LIBS = $(XCB_LIBS) $(PANGO_LIBS)
+i3_nagbar_CFLAGS = $(XCB_CFLAGS) $(XCB_WM_CFLAGS) $(PANGO_CFLAGS)
+i3_nagbar_LIBS = $(XCB_LIBS) $(XCB_WM_LIBS) $(PANGO_LIBS)
i3_nagbar_OBJECTS := $(i3_nagbar_SOURCES:.c=.o)
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_event.h>
+#include <xcb/randr.h>
#include "libi3.h"
#include "i3-nagbar.h"
printf("button released on x = %d, y = %d\n",
event->event_x, event->event_y);
/* If the user hits the close button, we exit(0) */
- if (event->event_x >= (rect.width - 32))
+ if (event->event_x >= (rect.width - logical_px(32)))
exit(0);
button_t *button = get_button_at(event->event_x, event->event_y);
if (!button)
char *link_path;
char *exe_path = get_exe_path(argv0);
sasprintf(&link_path, "%s.nagbar_cmd", script_path);
- symlink(exe_path, link_path);
+ if (symlink(exe_path, link_path) == -1) {
+ err(EXIT_FAILURE, "Failed to symlink %s to %s", link_path, exe_path);
+ }
char *terminal_cmd;
sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path);
/* restore font color */
set_font_colors(pixmap_gc, color_text, color_background);
draw_text(prompt, pixmap, pixmap_gc,
- 4 + 4, 4 + 4, rect.width - 4 - 4);
+ logical_px(4) + logical_px(4),
+ logical_px(4) + logical_px(4),
+ rect.width - logical_px(4) - logical_px(4));
/* render close button */
const char *close_button_label = "X";
- int line_width = 4;
+ int line_width = logical_px(4);
/* set width to the width of the label */
int w = predict_text_width(i3string_from_utf8(close_button_label));
/* account for left/right padding, which seems to be set to 8px (total) below */
- w += 8;
+ w += logical_px(8);
int y = rect.width;
uint32_t values[3];
values[0] = color_button_background;
xcb_point_t points[] = {
{y - w - (2 * line_width), line_width / 2},
{y - (line_width / 2), line_width / 2},
- {y - (line_width / 2), (rect.height - (line_width / 2)) - 2},
- {y - w - (2 * line_width), (rect.height - (line_width / 2)) - 2},
+ {y - (line_width / 2), (rect.height - (line_width / 2)) - logical_px(2)},
+ {y - w - (2 * line_width), (rect.height - (line_width / 2)) - logical_px(2)},
{y - w - (2 * line_width), line_width / 2}};
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points);
values[0] = 1;
set_font_colors(pixmap_gc, color_text, color_button_background);
/* the x term here seems to set left/right padding */
- draw_text_ascii(close_button_label, pixmap, pixmap_gc, y - w - line_width + w / 2 - 4,
- 4 + 4 - 1, rect.width - y + w + line_width - w / 2 + 4);
+ draw_text_ascii(close_button_label, pixmap, pixmap_gc,
+ y - w - line_width + w / 2 - logical_px(4),
+ logical_px(4) + logical_px(3),
+ rect.width - y + w + line_width - w / 2 + logical_px(4));
y -= w;
- y -= 20;
+ y -= logical_px(20);
/* render custom buttons */
line_width = 1;
/* set w to the width of the label */
w = predict_text_width(buttons[c].label);
/* account for left/right padding, which seems to be set to 12px (total) below */
- w += 12;
- y -= 30;
+ w += logical_px(12);
+ y -= logical_px(30);
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_button_background});
- close = (xcb_rectangle_t){y - w - (2 * line_width), 2, w + (2 * line_width), rect.height - 6};
+ close = (xcb_rectangle_t){y - w - (2 * line_width), logical_px(2), w + (2 * line_width), rect.height - logical_px(6)};
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border});
buttons[c].x = y - w - (2 * line_width);
buttons[c].width = w;
xcb_point_t points2[] = {
- {y - w - (2 * line_width), (line_width / 2) + 2},
- {y - (line_width / 2), (line_width / 2) + 2},
- {y - (line_width / 2), (rect.height - 4 - (line_width / 2))},
- {y - w - (2 * line_width), (rect.height - 4 - (line_width / 2))},
- {y - w - (2 * line_width), (line_width / 2) + 2}};
+ {y - w - (2 * line_width), (line_width / 2) + logical_px(2)},
+ {y - (line_width / 2), (line_width / 2) + logical_px(2)},
+ {y - (line_width / 2), (rect.height - logical_px(4) - (line_width / 2))},
+ {y - w - (2 * line_width), (rect.height - logical_px(4) - (line_width / 2))},
+ {y - w - (2 * line_width), (line_width / 2) + logical_px(2)}};
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2);
values[0] = color_text;
set_font_colors(pixmap_gc, color_text, color_button_background);
/* the x term seems to set left/right padding */
draw_text(buttons[c].label, pixmap, pixmap_gc,
- y - w - line_width + 6, 4 + 3, rect.width - y + w + line_width - 6);
+ y - w - line_width + logical_px(6),
+ logical_px(4) + logical_px(3),
+ rect.width - y + w + line_width - logical_px(6));
y -= w;
}
/* border line at the bottom */
- line_width = 2;
+ line_width = logical_px(2);
values[0] = color_border_bottom;
values[1] = line_width;
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
return 1;
}
+/**
+ * Return the position and size the i3-nagbar window should use.
+ * This will be the primary output or a fallback if it cannot be determined.
+ */
+static xcb_rectangle_t get_window_position(void) {
+ /* Default values if we cannot determine the primary output or its CRTC info. */
+ xcb_rectangle_t result = (xcb_rectangle_t){50, 50, 500, font.height + logical_px(8) + logical_px(8)};
+
+ xcb_randr_get_screen_resources_current_cookie_t rcookie = xcb_randr_get_screen_resources_current(conn, root);
+ xcb_randr_get_output_primary_cookie_t pcookie = xcb_randr_get_output_primary(conn, root);
+
+ xcb_randr_get_output_primary_reply_t *primary = NULL;
+ xcb_randr_get_screen_resources_current_reply_t *res = NULL;
+
+ if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL) {
+ DLOG("Could not determine the primary output.\n");
+ goto free_resources;
+ }
+
+ if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) {
+ goto free_resources;
+ }
+
+ xcb_randr_get_output_info_reply_t *output =
+ xcb_randr_get_output_info_reply(conn,
+ xcb_randr_get_output_info(conn, primary->output, res->config_timestamp),
+ NULL);
+ if (output == NULL || output->crtc == XCB_NONE)
+ goto free_resources;
+
+ xcb_randr_get_crtc_info_reply_t *crtc =
+ xcb_randr_get_crtc_info_reply(conn,
+ xcb_randr_get_crtc_info(conn, output->crtc, res->config_timestamp),
+ NULL);
+ if (crtc == NULL)
+ goto free_resources;
+
+ DLOG("Found primary output on position x = %i / y = %i / w = %i / h = %i",
+ crtc->x, crtc->y, crtc->width, crtc->height);
+ if (crtc->width == 0 || crtc->height == 0) {
+ DLOG("Primary output is not active, ignoring it.\n");
+ goto free_resources;
+ }
+
+ result.x = crtc->x;
+ result.y = crtc->y;
+ goto free_resources;
+
+free_resources:
+ FREE(res);
+ FREE(primary);
+ return result;
+}
+
int main(int argc, char *argv[]) {
/* The following lines are a terribly horrible kludge. Because terminal
* emulators have different ways of interpreting the -e command line
argv0 = argv[0];
- char *pattern = sstrdup("-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1");
+ char *pattern = sstrdup("pango:monospace 8");
int o, option_index = 0;
enum { TYPE_ERROR = 0,
TYPE_WARNING = 1 } bar_type = TYPE_ERROR;
font = load_font(pattern, true);
set_font(&font);
+ xcb_rectangle_t win_pos = get_window_position();
+
/* Open an input window */
win = xcb_generate_id(conn);
XCB_COPY_FROM_PARENT,
win, /* the window id */
root, /* parent == root */
- 50, 50, 500, font.height + 8 + 8 /* 8 px padding */, /* dimensions */
+ win_pos.x, win_pos.y, win_pos.width, win_pos.height, /* dimensions */
0, /* x11 border = 0, we draw our own */
XCB_WINDOW_CLASS_INPUT_OUTPUT,
XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
} __attribute__((__packed__)) strut_partial;
memset(&strut_partial, 0, sizeof(strut_partial));
- strut_partial.top = font.height + 6;
+ strut_partial.top = font.height + logical_px(6);
strut_partial.top_start_x = 0;
strut_partial.top_end_x = 800;
/* Create pixmap */
pixmap = xcb_generate_id(conn);
pixmap_gc = xcb_generate_id(conn);
- xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + 8);
+ xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + logical_px(8));
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
/* Grab the keyboard to get all input */
exec $terminal "$@"
fi
done
+
+i3-nagbar -m 'i3-sensible-terminal could not find a terminal emulator. Please install one.'
char *message;
va_list args;
va_start(args, format);
- vasprintf(&message, format, args);
+ (void)vasprintf(&message, format, args);
struct status_block *err_block = scalloc(sizeof(struct status_block));
err_block->full_text = i3string_from_utf8("Error: ");
/* Default width of the separator block. */
ctx->block.sep_block_width = logical_px(9);
- /* Use markup by default */
- ctx->block.is_markup = true;
-
return 1;
}
parser_ctx *ctx = context;
if (strcasecmp(ctx->last_map_key, "urgent") == 0) {
ctx->block.urgent = val;
+ return 1;
}
if (strcasecmp(ctx->last_map_key, "separator") == 0) {
ctx->block.no_separator = !val;
+ return 1;
}
+
return 1;
}
parser_ctx *ctx = context;
if (strcasecmp(ctx->last_map_key, "full_text") == 0) {
ctx->block.full_text = i3string_from_markup_with_length((const char *)val, len);
+ return 1;
}
if (strcasecmp(ctx->last_map_key, "short_text") == 0) {
ctx->block.short_text = i3string_from_markup_with_length((const char *)val, len);
+ return 1;
}
if (strcasecmp(ctx->last_map_key, "color") == 0) {
sasprintf(&(ctx->block.color), "%.*s", len, val);
+ return 1;
}
if (strcasecmp(ctx->last_map_key, "markup") == 0) {
ctx->block.is_markup = (len == strlen("pango") && !strncasecmp((const char *)val, "pango", strlen("pango")));
+ return 1;
}
if (strcasecmp(ctx->last_map_key, "align") == 0) {
if (len == strlen("center") && !strncmp((const char *)val, "center", strlen("center"))) {
} else {
ctx->block.align = ALIGN_LEFT;
}
- } else if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
+ return 1;
+ }
+ if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
char *copy = (char *)malloc(len + 1);
strncpy(copy, (const char *)val, len);
copy[len] = 0;
ctx->block.min_width_str = copy;
+ return 1;
}
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;
+ return 1;
}
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;
}
+
return 1;
}
parser_ctx *ctx = context;
if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
ctx->block.min_width = (uint32_t)val;
+ return 1;
}
if (strcasecmp(ctx->last_map_key, "separator_block_width") == 0) {
ctx->block.sep_block_width = (uint32_t)val;
+ return 1;
}
+
return 1;
}
if (child.click_events) {
const unsigned char *output;
size_t size;
+ ssize_t n;
yajl_gen_get_buf(gen, &output, &size);
- write(child_stdin, output, size);
- write(child_stdin, "\n", 1);
+
+ n = writeall(child_stdin, output, size);
+ if (n != -1)
+ n = writeall(child_stdin, "\n", 1);
+
yajl_gen_clear(gen);
+
+ if (n == -1) {
+ child.click_events = false;
+ kill_child();
+ set_statusline_error("child_write_output failed");
+ draw_bars(false);
+ }
}
}
if (payload != NULL)
strncpy(walk, payload, len);
- uint32_t written = 0;
-
- while (to_write > 0) {
- int n = write(i3_connection->fd, buffer + written, to_write);
- if (n == -1) {
- ELOG("write() failed: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- to_write -= n;
- written += n;
- }
+ swrite(i3_connection->fd, buffer, to_write);
FREE(buffer);
struct xcb_colors_t colors;
/* Horizontal offset between a workspace label and button borders */
-const static int ws_hoff_px = 4;
+static const int ws_hoff_px = 4;
/* Vertical offset between a workspace label and button borders */
-const static int ws_voff_px = 3;
+static const int ws_voff_px = 3;
/* Offset between two workspace buttons */
-const static int ws_spacing_px = 1;
+static const int ws_spacing_px = 1;
/* Offset between the statusline and 1) workspace buttons on the left
* 2) the tray or screen edge on the right */
-const static int sb_hoff_px = 4;
+static const int sb_hoff_px = 4;
/* Additional offset between the tray and the statusline, if the tray is not empty */
-const static int tray_loff_px = 2;
+static const int tray_loff_px = 2;
/* Vertical offset between the bar and a separator */
-const static int sep_voff_px = 4;
+static const int sep_voff_px = 4;
/* We define xcb_request_failed as a macro to include the relevant line number */
#define xcb_request_failed(cookie, err_msg) _xcb_request_failed(cookie, err_msg, __LINE__)
size_t namelen = 0;
const char *utf8_name = cur_ws->canonical_name;
for (const char *walk = utf8_name; *walk != '\0'; walk++) {
- if (*walk == '"')
+ if (*walk == '"' || *walk == '\\')
num_quotes++;
/* While we’re looping through the name anyway, we can save one
* strlen(). */
for (inpos = 0, outpos = strlen("workspace \"");
inpos < namelen;
inpos++, outpos++) {
- if (utf8_name[inpos] == '"') {
+ if (utf8_name[inpos] == '"' || utf8_name[inpos] == '\\') {
buffer[outpos] = '\\';
outpos++;
}
}
}
+/* Strut partial tells i3 where to reserve space for i3bar. This is determined
+ * by the `position` bar config directive. */
+xcb_void_cookie_t config_strut_partial(i3_output *output) {
+ /* A local struct to save the strut_partial property */
+ struct {
+ uint32_t left;
+ uint32_t right;
+ uint32_t top;
+ uint32_t bottom;
+ uint32_t left_start_y;
+ uint32_t left_end_y;
+ uint32_t right_start_y;
+ uint32_t right_end_y;
+ uint32_t top_start_x;
+ uint32_t top_end_x;
+ uint32_t bottom_start_x;
+ uint32_t bottom_end_x;
+ } __attribute__((__packed__)) strut_partial;
+ memset(&strut_partial, 0, sizeof(strut_partial));
+
+ switch (config.position) {
+ case POS_NONE:
+ break;
+ case POS_TOP:
+ strut_partial.top = bar_height;
+ strut_partial.top_start_x = output->rect.x;
+ strut_partial.top_end_x = output->rect.x + output->rect.w;
+ break;
+ case POS_BOT:
+ strut_partial.bottom = bar_height;
+ strut_partial.bottom_start_x = output->rect.x;
+ strut_partial.bottom_end_x = output->rect.x + output->rect.w;
+ break;
+ }
+ return xcb_change_property(xcb_connection,
+ XCB_PROP_MODE_REPLACE,
+ output->bar,
+ atoms[_NET_WM_STRUT_PARTIAL],
+ XCB_ATOM_CARDINAL,
+ 32,
+ 12,
+ &strut_partial);
+}
+
/*
* Reconfigure all bars and create new bars for recently activated outputs
*
1,
(unsigned char *)&atoms[_NET_WM_WINDOW_TYPE_DOCK]);
- /* We need to tell i3, where to reserve space for i3bar */
- /* left, right, top, bottom, left_start_y, left_end_y,
- * right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x,
- * bottom_end_x */
- /* A local struct to save the strut_partial property */
- struct {
- uint32_t left;
- uint32_t right;
- uint32_t top;
- uint32_t bottom;
- uint32_t left_start_y;
- uint32_t left_end_y;
- uint32_t right_start_y;
- uint32_t right_end_y;
- uint32_t top_start_x;
- uint32_t top_end_x;
- uint32_t bottom_start_x;
- uint32_t bottom_end_x;
- } __attribute__((__packed__)) strut_partial;
- memset(&strut_partial, 0, sizeof(strut_partial));
-
- switch (config.position) {
- case POS_NONE:
- break;
- case POS_TOP:
- strut_partial.top = bar_height;
- strut_partial.top_start_x = walk->rect.x;
- strut_partial.top_end_x = walk->rect.x + walk->rect.w;
- break;
- case POS_BOT:
- strut_partial.bottom = bar_height;
- strut_partial.bottom_start_x = walk->rect.x;
- strut_partial.bottom_end_x = walk->rect.x + walk->rect.w;
- break;
- }
- xcb_void_cookie_t strut_cookie = xcb_change_property(xcb_connection,
- XCB_PROP_MODE_REPLACE,
- walk->bar,
- atoms[_NET_WM_STRUT_PARTIAL],
- XCB_ATOM_CARDINAL,
- 32,
- 12,
- &strut_partial);
+ xcb_void_cookie_t strut_cookie = config_strut_partial(walk);
/* We also want a graphics context for the bars (it defines the properties
* with which we draw to them) */
values[3] = bar_height;
values[4] = XCB_STACK_MODE_ABOVE;
+ DLOG("Reconfiguring strut partial property for output %s\n", walk->name);
+ xcb_void_cookie_t strut_cookie = config_strut_partial(walk);
+
DLOG("Destroying buffer for output %s\n", walk->name);
xcb_free_pixmap(xcb_connection, walk->buffer);
if (xcb_request_failed(cfg_cookie, "Could not reconfigure window") ||
xcb_request_failed(chg_cookie, "Could not change window") ||
xcb_request_failed(pm_cookie, "Could not create pixmap") ||
+ xcb_request_failed(strut_cookie, "Could not set strut") ||
(redraw_bars && (xcb_request_failed(umap_cookie, "Could not unmap window") ||
(config.hide_on_modifier == M_DOCK && xcb_request_failed(map_cookie, "Could not map window"))))) {
exit(EXIT_FAILURE);
DLOG("Printing statusline!\n");
int tray_width = get_tray_width(outputs_walk->trayclients);
- int max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - 2 * logical_px(sb_hoff_px);
+ uint32_t max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - 2 * logical_px(sb_hoff_px);
/* If the statusline is too long, try to use short texts. */
if (statusline_width > max_statusline_width)
void cmd_workspace_name(I3_CMD, char *name);
/**
- * Implementation of 'mark <mark>'
+ * Implementation of 'mark [--toggle] <mark>'
*
*/
-void cmd_mark(I3_CMD, char *mark);
+void cmd_mark(I3_CMD, char *mark, char *toggle);
/**
* Implementation of 'unmark [mark]'
int size, uint32_t message_size, \
uint32_t message_type)
-/**
- * Emulates mkdir -p (creates any missing folders)
- *
- */
-bool mkdirp(const char *path);
-
/**
* Handler for activity on the listening socket, meaning that a new client
* has just connected and we should accept() him. Sets up the event handler
*/
int sasprintf(char **strp, const char *fmt, ...);
+/**
+ * Wrapper around correct write which returns -1 (meaning that
+ * write failed) or count (meaning that all bytes were written)
+ *
+ */
+ssize_t writeall(int fd, const void *buf, size_t count);
+
+/**
+ * Safe-wrapper around writeall which exits if it returns -1 (meaning that
+ * write failed)
+ *
+ */
+ssize_t swrite(int fd, const void *buf, size_t count);
+
/**
* Build an i3String from an UTF-8 encoded string.
* Returns the newly-allocated i3String.
*
*/
int logical_px(const int logical);
+
+/**
+ * This function resolves ~ in pathnames.
+ * It may resolve wildcards in the first part of the path, but if no match
+ * or multiple matches are found, it just returns a copy of path as given.
+ *
+ */
+char *resolve_tilde(const char *path);
+
+/**
+ * Get the path of the first configuration file found. If override_configpath
+ * is specified, that path is returned and saved for further calls. Otherwise,
+ * checks the home directory first, then the system directory first, always
+ * taking into account the XDG Base Directory Specification ($XDG_CONFIG_HOME,
+ * $XDG_CONFIG_DIRS)
+ *
+ */
+char *get_config_path(const char *override_configpath, bool use_system_paths);
+
+/**
+ * Emulates mkdir -p (creates any missing folders)
+ *
+ */
+bool mkdirp(const char *path);
void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie,
char *err_message);
-/**
- * This function resolves ~ in pathnames.
- * It may resolve wildcards in the first part of the path, but if no match
- * or multiple matches are found, it just returns a copy of path as given.
- *
- */
-char *resolve_tilde(const char *path);
-
/**
* Checks if the given path exists by calling stat().
*
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2015 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include "libi3.h"
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+/*
+ * Checks if the given path exists by calling stat().
+ *
+ */
+static bool path_exists(const char *path) {
+ struct stat buf;
+ return (stat(path, &buf) == 0);
+}
+
+/*
+ * Get the path of the first configuration file found. If override_configpath
+ * is specified, that path is returned and saved for further calls. Otherwise,
+ * checks the home directory first, then the system directory first, always
+ * taking into account the XDG Base Directory Specification ($XDG_CONFIG_HOME,
+ * $XDG_CONFIG_DIRS)
+ *
+ */
+char *get_config_path(const char *override_configpath, bool use_system_paths) {
+ char *xdg_config_home, *xdg_config_dirs, *config_path;
+
+ static const char *saved_configpath = NULL;
+
+ if (override_configpath != NULL) {
+ saved_configpath = override_configpath;
+ return sstrdup(saved_configpath);
+ }
+
+ if (saved_configpath != NULL)
+ return sstrdup(saved_configpath);
+
+ /* 1: check the traditional path under the home directory */
+ config_path = resolve_tilde("~/.i3/config");
+ if (path_exists(config_path))
+ return config_path;
+ free(config_path);
+
+ /* 2: check for $XDG_CONFIG_HOME/i3/config */
+ if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
+ xdg_config_home = "~/.config";
+
+ xdg_config_home = resolve_tilde(xdg_config_home);
+ sasprintf(&config_path, "%s/i3/config", xdg_config_home);
+ free(xdg_config_home);
+
+ if (path_exists(config_path))
+ return config_path;
+ free(config_path);
+
+ /* The below paths are considered system-level, and can be skipped if the
+ * caller only wants user-level configs. */
+ if (!use_system_paths)
+ return NULL;
+
+ /* 3: check the traditional path under /etc */
+ config_path = SYSCONFDIR "/i3/config";
+ if (path_exists(config_path))
+ return sstrdup(config_path);
+
+ /* 4: check for $XDG_CONFIG_DIRS/i3/config */
+ if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL)
+ xdg_config_dirs = "/etc/xdg";
+
+ char *buf = sstrdup(xdg_config_dirs);
+ char *tok = strtok(buf, ":");
+ while (tok != NULL) {
+ tok = resolve_tilde(tok);
+ sasprintf(&config_path, "%s/i3/config", tok);
+ free(tok);
+ if (path_exists(config_path)) {
+ free(buf);
+ return config_path;
+ }
+ free(config_path);
+ tok = strtok(NULL, ":");
+ }
+ free(buf);
+
+ return NULL;
+}
.size = message_size,
.type = message_type};
- size_t sent_bytes = 0;
- int n = 0;
+ if (writeall(sockfd, ((void *)&header), sizeof(i3_ipc_header_t)) == -1)
+ return -1;
- /* This first loop is basically unnecessary. No operating system has
- * buffers which cannot fit 14 bytes into them, so the write() will only be
- * called once. */
- while (sent_bytes < sizeof(i3_ipc_header_t)) {
- if ((n = write(sockfd, ((void *)&header) + sent_bytes, sizeof(i3_ipc_header_t) - sent_bytes)) == -1) {
- if (errno == EAGAIN)
- continue;
- return -1;
- }
-
- sent_bytes += n;
- }
-
- sent_bytes = 0;
-
- while (sent_bytes < message_size) {
- if ((n = write(sockfd, payload + sent_bytes, message_size - sent_bytes)) == -1) {
- if (errno == EAGAIN)
- continue;
- return -1;
- }
-
- sent_bytes += n;
- }
+ if (writeall(sockfd, payload, message_size) == -1)
+ return -1;
return 0;
}
--- /dev/null
+#include "libi3.h"
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+/*
+ * Emulates mkdir -p (creates any missing folders)
+ *
+ */
+bool mkdirp(const char *path) {
+ if (mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0)
+ return true;
+ if (errno != ENOENT) {
+ ELOG("mkdir(%s) failed: %s\n", path, strerror(errno));
+ return false;
+ }
+ char *copy = sstrdup(path);
+ /* strip trailing slashes, if any */
+ while (copy[strlen(copy) - 1] == '/')
+ copy[strlen(copy) - 1] = '\0';
+
+ char *sep = strrchr(copy, '/');
+ if (sep == NULL) {
+ if (copy != NULL) {
+ free(copy);
+ copy = NULL;
+ }
+ return false;
+ }
+ *sep = '\0';
+ bool result = false;
+ if (mkdirp(copy))
+ result = mkdirp(path);
+ free(copy);
+
+ return result;
+}
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2015 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+
+#include "libi3.h"
+#include <err.h>
+#include <glob.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * This function resolves ~ in pathnames.
+ * It may resolve wildcards in the first part of the path, but if no match
+ * or multiple matches are found, it just returns a copy of path as given.
+ *
+ */
+char *resolve_tilde(const char *path) {
+ static glob_t globbuf;
+ char *head, *tail, *result;
+
+ tail = strchr(path, '/');
+ head = strndup(path, tail ? (size_t)(tail - path) : strlen(path));
+
+ int res = glob(head, GLOB_TILDE, NULL, &globbuf);
+ free(head);
+ /* no match, or many wildcard matches are bad */
+ if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1)
+ result = sstrdup(path);
+ else if (res != 0) {
+ err(EXIT_FAILURE, "glob() failed");
+ } else {
+ head = globbuf.gl_pathv[0];
+ result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1);
+ strncpy(result, head, strlen(head));
+ if (tail)
+ strncat(result, tail, strlen(tail));
+ }
+ globfree(&globbuf);
+
+ return result;
+}
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
+#include <unistd.h>
#include <stdio.h>
#include <err.h>
+#include <errno.h>
#include "libi3.h"
va_end(args);
return result;
}
+
+ssize_t writeall(int fd, const void *buf, size_t count) {
+ size_t written = 0;
+ ssize_t n = 0;
+
+ while (written < count) {
+ n = write(fd, buf + written, count - written);
+ if (n == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return n;
+ }
+ written += (size_t)n;
+ }
+
+ return written;
+}
+
+ssize_t swrite(int fd, const void *buf, size_t count) {
+ ssize_t n;
+
+ n = writeall(fd, buf, count);
+ if (n == -1)
+ err(EXIT_FAILURE, "Failed to write %d", fd);
+ else
+ return n;
+}
<refentrytitle>{mantitle}</refentrytitle>
<manvolnum>{manvolnum}</manvolnum>
<refmiscinfo class="source">i3</refmiscinfo>
-<refmiscinfo class="version">4.9.1</refmiscinfo>
+<refmiscinfo class="version">4.10.1</refmiscinfo>
<refmiscinfo class="manual">i3 Manual</refmiscinfo>
</refmeta>
<refnamediv>
floating = 'enable', 'disable', 'toggle'
-> call cmd_floating($floating)
-# mark <mark>
+# mark [--toggle] <mark>
state MARK:
+ toggle = '--toggle'
+ ->
mark = string
- -> call cmd_mark($mark)
+ -> call cmd_mark($mark, $toggle)
# unmark [mark]
state UNMARK:
state NOP:
comment = string
-> call cmd_nop($comment)
+ end
+ -> call cmd_nop(NULL)
state SCRATCHPAD:
'show'
#!/bin/zsh
# This script is used to prepare a new release of i3.
-export RELEASE_VERSION="4.9.1"
-export PREVIOUS_VERSION="4.9"
-export RELEASE_BRANCH="master"
+export RELEASE_VERSION="4.10.1"
+export PREVIOUS_VERSION="4.10"
+export RELEASE_BRANCH="next"
if [ ! -e "../i3.github.io" ]
then
git merge --no-ff next -m "Merge branch 'next' into master"
fi
+git remote remove origin
+git remote add origin git@github.com:i3/i3.git
+git config --add remote.origin.push "+refs/tags/*:refs/tags/*"
+git config --add remote.origin.push "+refs/heads/next:refs/heads/next"
+git config --add remote.origin.push "+refs/heads/master:refs/heads/master"
+
################################################################################
# Section 2: Debian packaging
################################################################################
git commit -a -m "update docs for ${RELEASE_VERSION}"
+git remote remove origin
+git remote add origin git@github.com:i3/i3.github.io.git
+
################################################################################
-# Section 4: final push instructions
+# Section 4: prepare release announcement email
+################################################################################
+
+cd ${TMPDIR}
+cat >email.txt <<EOT
+From: Michael Stapelberg <michael@i3wm.org>
+To: i3-announce@i3.zekjur.net
+Subject: i3 v${RELEASE_VERSION} released
+
+Hi,
+
+I just released i3 v${RELEASE_VERSION}. Release notes follow:
+EOT
+cat ${TMPDIR}/i3/RELEASE-NOTES-${RELEASE_VERSION}.txt >>email.txt
+
+################################################################################
+# Section 5: final push instructions
################################################################################
echo "As a final sanity check, install the debian package and see whether i3 works."
echo " cd ${TMPDIR}/i3"
echo " git checkout next"
echo " vi debian/changelog"
-# TODO: can we just set up the remote spec properly?
-echo " git push git@github.com:i3/i3 next"
-echo " git push git@github.com:i3/i3 master"
-echo " git push git@github.com:i3/i3 --tags"
+echo " git push"
echo ""
echo " cd ${TMPDIR}/i3.github.io"
-# TODO: can we just set up the remote spec properly?
-echo " git push git@github.com:i3/i3.github.io master"
+echo " git push"
echo ""
echo " cd ${TMPDIR}/debian"
echo " dput *.changes"
echo ""
+echo " cd ${TMPDIR}"
+echo " sendmail < email.txt"
+echo ""
echo "Announce on:"
echo " twitter"
echo " google+"
case BORDER_BOTTOM:
search_direction = D_DOWN;
break;
+ default:
+ assert(false);
+ break;
}
bool res = resize_find_tiling_participants(&first, &second, search_direction);
}
/*
- * Implementation of 'mark <mark>'
+ * Implementation of 'mark [--toggle] <mark>'
*
*/
-void cmd_mark(I3_CMD, char *mark) {
- DLOG("Clearing all windows which have that mark first\n");
+void cmd_mark(I3_CMD, char *mark, char *toggle) {
+ HANDLE_EMPTY_MATCH;
+ owindow *current;
+ TAILQ_FOREACH(current, &owindows, owindows) {
+ DLOG("matching: %p / %s\n", current->con, current->con->name);
+ if (toggle != NULL && current->con->mark && strcmp(current->con->mark, mark) == 0) {
+ DLOG("removing window mark %s\n", mark);
+ FREE(current->con->mark);
+ } else {
+ DLOG("marking window with str %s\n", mark);
+ FREE(current->con->mark);
+ current->con->mark = sstrdup(mark);
+ }
+ }
+
+ DLOG("Clearing all non-matched windows with this mark\n");
Con *con;
TAILQ_FOREACH(con, &all_cons, all_cons) {
+ /* Skip matched windows, we took care of them already. */
+ bool matched = false;
+ TAILQ_FOREACH(current, &owindows, owindows) {
+ if (current->con == con) {
+ matched = true;
+ break;
+ }
+ }
+ if (matched)
+ continue;
+
if (con->mark && strcmp(con->mark, mark) == 0)
FREE(con->mark);
}
- DLOG("marking window with str %s\n", mark);
- owindow *current;
-
- HANDLE_EMPTY_MATCH;
-
- TAILQ_FOREACH(current, &owindows, owindows) {
- DLOG("matching: %p / %s\n", current->con, current->con->name);
- current->con->mark = sstrdup(mark);
- }
-
cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply
ysuccess(true);
if (**walk == '"') {
beginning++;
(*walk)++;
- while (**walk != '\0' && (**walk != '"' || *(*walk - 1) == '\\'))
- (*walk)++;
+ for (; **walk != '\0' && **walk != '"'; (*walk)++)
+ if (**walk == '\\' && *(*walk + 1) != '\0')
+ (*walk)++;
} else {
if (!as_word) {
/* For a string (starting with 's'), the delimiters are
for (inpos = 0, outpos = 0;
inpos < (*walk - beginning);
inpos++, outpos++) {
- /* We only handle escaped double quotes to not break
- * backwards compatibility with people using \w in
- * regular expressions etc. */
- if (beginning[inpos] == '\\' && beginning[inpos + 1] == '"')
+ /* We only handle escaped double quotes and backslashes to not break
+ * backwards compatibility with people using \w in regular expressions
+ * etc. */
+ if (beginning[inpos] == '\\' && (beginning[inpos + 1] == '"' || beginning[inpos + 1] == '\\'))
inpos++;
str[outpos] = beginning[inpos];
}
}
}
+ /* Save the urgency state so that we can restore it. */
+ bool urgent = con->urgent;
+
/* Save the current workspace. So we can call workspace_show() by the end
* of this function. */
Con *current_ws = con_get_workspace(focused);
if (source_ws == current_ws)
con_focus(con_descend_focused(focus_next));
- /* If anything within the container is associated with a startup sequence,
+ /* 9. If anything within the container is associated with a startup sequence,
* delete it so child windows won't be created on the old workspace. */
struct Startup_Sequence *sequence;
xcb_get_property_cookie_t cookie;
CALL(parent, on_remove_child);
+ /* 10. If the container was marked urgent, move the urgency hint. */
+ if (urgent) {
+ workspace_update_urgent_flag(source_ws);
+ con_set_urgency(con, true);
+ }
+
ipc_send_window_event("move", con);
}
}
}
-/*
- * Get the path of the first configuration file found. If override_configpath
- * is specified, that path is returned and saved for further calls. Otherwise,
- * checks the home directory first, then the system directory first, always
- * taking into account the XDG Base Directory Specification ($XDG_CONFIG_HOME,
- * $XDG_CONFIG_DIRS)
- *
- */
-static char *get_config_path(const char *override_configpath) {
- char *xdg_config_home, *xdg_config_dirs, *config_path;
-
- static const char *saved_configpath = NULL;
-
- if (override_configpath != NULL) {
- saved_configpath = override_configpath;
- return sstrdup(saved_configpath);
- }
-
- if (saved_configpath != NULL)
- return sstrdup(saved_configpath);
-
- /* 1: check the traditional path under the home directory */
- config_path = resolve_tilde("~/.i3/config");
- if (path_exists(config_path))
- return config_path;
- free(config_path);
-
- /* 2: check for $XDG_CONFIG_HOME/i3/config */
- if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
- xdg_config_home = "~/.config";
-
- xdg_config_home = resolve_tilde(xdg_config_home);
- sasprintf(&config_path, "%s/i3/config", xdg_config_home);
- free(xdg_config_home);
-
- if (path_exists(config_path))
- return config_path;
- free(config_path);
-
- /* 3: check the traditional path under /etc */
- config_path = SYSCONFDIR "/i3/config";
- if (path_exists(config_path))
- return sstrdup(config_path);
-
- /* 4: check for $XDG_CONFIG_DIRS/i3/config */
- if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL)
- xdg_config_dirs = "/etc/xdg";
-
- char *buf = sstrdup(xdg_config_dirs);
- char *tok = strtok(buf, ":");
- while (tok != NULL) {
- tok = resolve_tilde(tok);
- sasprintf(&config_path, "%s/i3/config", tok);
- free(tok);
- if (path_exists(config_path)) {
- free(buf);
- return config_path;
- }
- free(config_path);
- tok = strtok(NULL, ":");
- }
- free(buf);
-
- die("Unable to find the configuration file (looked at "
- "~/.i3/config, $XDG_CONFIG_HOME/i3/config, " SYSCONFDIR "/i3/config and $XDG_CONFIG_DIRS/i3/config)");
-}
-
/*
* Finds the configuration file to use (either the one specified by
* override_configpath), the user’s one or the system default) and calls
*
*/
bool parse_configuration(const char *override_configpath, bool use_nagbar) {
- char *path = get_config_path(override_configpath);
+ char *path = get_config_path(override_configpath, true);
+ if (path == NULL) {
+ die("Unable to find the configuration file (looked at "
+ "~/.i3/config, $XDG_CONFIG_HOME/i3/config, " SYSCONFDIR "/i3/config and $XDG_CONFIG_DIRS/i3/config)");
+ }
+
LOG("Parsing configfile %s\n", path);
FREE(current_configpath);
current_configpath = path;
/* write the whole config file to the pipe, the script will read everything
* immediately */
- int written = 0;
- int ret;
- while (written < size) {
- if ((ret = write(writepipe[1], input + written, size - written)) < 0) {
- warn("Could not write to pipe");
- return NULL;
- }
- written += ret;
+ if (writeall(writepipe[1], input, size) == -1) {
+ warn("Could not write to pipe");
+ return NULL;
}
close(writepipe[1]);
/* read the script’s output */
int conv_size = 65535;
char *converted = malloc(conv_size);
- int read_bytes = 0;
+ int read_bytes = 0, ret;
do {
if (read_bytes == conv_size) {
conv_size += 65535;
.root_y = y_root,
.event_x = x_root - (con->rect.x),
.event_y = y_root - (con->rect.y)};
- if (direction == _NET_WM_MOVERESIZE_MOVE) {
- floating_drag_window(con->parent, &fake);
- } else if (direction >= _NET_WM_MOVERESIZE_SIZE_TOPLEFT && direction <= _NET_WM_MOVERESIZE_SIZE_LEFT) {
- floating_resize_window(con->parent, FALSE, &fake);
- } else {
- DLOG("_NET_WM_MOVERESIZE direction %d not implemented\n", direction);
+ switch (direction) {
+ case _NET_WM_MOVERESIZE_MOVE:
+ floating_drag_window(con->parent, &fake);
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_TOPLEFT... _NET_WM_MOVERESIZE_SIZE_LEFT:
+ floating_resize_window(con->parent, FALSE, &fake);
+ break;
+ default:
+ DLOG("_NET_WM_MOVERESIZE direction %d not implemented\n", direction);
+ break;
}
} else {
DLOG("unhandled clientmessage\n");
return true;
}
+/*
+ * Handles the _NET_WM_STRUT_PARTIAL property for allocating space for dock clients.
+ *
+ */
+static bool handle_strut_partial_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
+ xcb_atom_t name, xcb_get_property_reply_t *prop) {
+ DLOG("strut partial change for window 0x%08x\n", window);
+
+ Con *con;
+ if ((con = con_by_window_id(window)) == NULL || con->window == NULL) {
+ return false;
+ }
+
+ if (prop == NULL) {
+ xcb_generic_error_t *err = NULL;
+ xcb_get_property_cookie_t strut_cookie = xcb_get_property(conn, false, window, A__NET_WM_STRUT_PARTIAL,
+ XCB_GET_PROPERTY_TYPE_ANY, 0, UINT32_MAX);
+ prop = xcb_get_property_reply(conn, strut_cookie, &err);
+
+ if (err != NULL) {
+ DLOG("got error when getting strut partial property: %d\n", err->error_code);
+ free(err);
+ return false;
+ }
+
+ if (prop == NULL) {
+ return false;
+ }
+ }
+
+ DLOG("That is con %p / %s\n", con, con->name);
+
+ window_update_strut_partial(con->window, prop);
+
+ /* we only handle this change for dock clients */
+ if (con->parent == NULL || con->parent->type != CT_DOCKAREA) {
+ return true;
+ }
+
+ Con *search_at = croot;
+ Con *output = con_get_output(con);
+ if (output != NULL) {
+ DLOG("Starting search at output %s\n", output->name);
+ search_at = output;
+ }
+
+ /* find out the desired position of this dock window */
+ if (con->window->reserved.top > 0 && con->window->reserved.bottom == 0) {
+ DLOG("Top dock client\n");
+ con->window->dock = W_DOCK_TOP;
+ } else if (con->window->reserved.top == 0 && con->window->reserved.bottom > 0) {
+ DLOG("Bottom dock client\n");
+ con->window->dock = W_DOCK_BOTTOM;
+ } else {
+ DLOG("Ignoring invalid reserved edges (_NET_WM_STRUT_PARTIAL), using position as fallback:\n");
+ if (con->geometry.y < (search_at->rect.height / 2)) {
+ DLOG("geom->y = %d < rect.height / 2 = %d, it is a top dock client\n",
+ con->geometry.y, (search_at->rect.height / 2));
+ con->window->dock = W_DOCK_TOP;
+ } else {
+ DLOG("geom->y = %d >= rect.height / 2 = %d, it is a bottom dock client\n",
+ con->geometry.y, (search_at->rect.height / 2));
+ con->window->dock = W_DOCK_BOTTOM;
+ }
+ }
+
+ /* find the dockarea */
+ Con *dockarea = con_for_window(search_at, con->window, NULL);
+ assert(dockarea != NULL);
+
+ /* attach the dock to the dock area */
+ con_detach(con);
+ con->parent = dockarea;
+ TAILQ_INSERT_HEAD(&(dockarea->focus_head), con, focused);
+ TAILQ_INSERT_HEAD(&(dockarea->nodes_head), con, nodes);
+
+ tree_render();
+
+ return true;
+}
+
/* Returns false if the event could not be processed (e.g. the window could not
* be found), true otherwise */
typedef bool (*cb_property_handler_t)(void *data, xcb_connection_t *c, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property);
{0, UINT_MAX, handle_clientleader_change},
{0, UINT_MAX, handle_transient_for},
{0, 128, handle_windowrole_change},
- {0, 128, handle_class_change}};
+ {0, 128, handle_class_change},
+ {0, UINT_MAX, handle_strut_partial_change}};
#define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
/*
property_handlers[5].atom = XCB_ATOM_WM_TRANSIENT_FOR;
property_handlers[6].atom = A_WM_WINDOW_ROLE;
property_handlers[7].atom = XCB_ATOM_WM_CLASS;
+ property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL;
}
static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
err(-1, "Could not set O_NONBLOCK");
}
-/*
- * Emulates mkdir -p (creates any missing folders)
- *
- */
-bool mkdirp(const char *path) {
- if (mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0)
- return true;
- if (errno != ENOENT) {
- ELOG("mkdir(%s) failed: %s\n", path, strerror(errno));
- return false;
- }
- char *copy = sstrdup(path);
- /* strip trailing slashes, if any */
- while (copy[strlen(copy) - 1] == '/')
- copy[strlen(copy) - 1] = '\0';
-
- char *sep = strrchr(copy, '/');
- if (sep == NULL) {
- FREE(copy);
- return false;
- }
- *sep = '\0';
- bool result = false;
- if (mkdirp(copy))
- result = mkdirp(path);
- free(copy);
-
- return result;
-}
-
/*
* Sends the specified event to all IPC clients which are currently connected
* and subscribed to this kind of event.
int cnt = 1;
while (workspace != NULL) {
FREE(json_node->name);
- asprintf(&(json_node->name), "%s_%d", base, cnt++);
+ sasprintf(&(json_node->name), "%s_%d", base, cnt++);
workspace = NULL;
TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, json_node->name));
int stdin_pipe[2],
stdout_pipe[2];
- pipe(stdin_pipe);
- pipe(stdout_pipe);
+ if (pipe(stdin_pipe) == -1) {
+ ELOG("Failed to init stdin_pipe\n");
+ return -1;
+ }
+ if (pipe(stdout_pipe) == -1) {
+ ELOG("Failed to init stdout_pipe\n");
+ return -1;
+ }
/* close standard streams in case i3 is started from a terminal; gdb
* needs to run without controlling terminal for it to work properly in
}
}
-/*
- * This function resolves ~ in pathnames.
- * It may resolve wildcards in the first part of the path, but if no match
- * or multiple matches are found, it just returns a copy of path as given.
- *
- */
-char *resolve_tilde(const char *path) {
- static glob_t globbuf;
- char *head, *tail, *result;
-
- tail = strchr(path, '/');
- head = strndup(path, tail ? (size_t)(tail - path) : strlen(path));
-
- int res = glob(head, GLOB_TILDE, NULL, &globbuf);
- free(head);
- /* no match, or many wildcard matches are bad */
- if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1)
- result = sstrdup(path);
- else if (res != 0) {
- die("glob() failed");
- } else {
- head = globbuf.gl_pathv[0];
- result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1);
- strncpy(result, head, strlen(head));
- if (tail)
- strncat(result, tail, strlen(tail));
- }
- globfree(&globbuf);
-
- return result;
-}
-
/*
* Checks if the given path exists by calling stat().
*
return NULL;
}
- size_t written = 0;
- while (written < length) {
- int n = write(fd, payload + written, length - written);
- /* TODO: correct error-handling */
- if (n == -1) {
- perror("write()");
- free(filename);
- close(fd);
- return NULL;
- }
- if (n == 0) {
- DLOG("write == 0?\n");
- free(filename);
- close(fd);
- return NULL;
- }
- written += n;
- DLOG("written: %zd of %zd\n", written, length);
+ if (writeall(fd, payload, length) == -1) {
+ ELOG("Could not write restart layout to \"%s\", layout will be lost: %s\n", filename, strerror(errno));
+ free(filename);
+ close(fd);
+ return NULL;
}
+
close(fd);
if (length > 0) {
DLOG("Attaching new split %p to workspace %p\n", new, ws);
con_attach(new, ws, false);
+ /* 5: fix the percentages */
+ con_fix_percent(ws);
+
return new;
}
@docked = get_dock_clients('top');
is(@docked, 1, 'dock client on top');
+# now change strut_partial to reserve space on the bottom and the dock should
+# be moved to the bottom dock area
+$x->change_property(
+ PROP_MODE_REPLACE,
+ $window->id,
+ $atomname->id,
+ $atomtype->id,
+ 32, # 32 bit integer
+ 12,
+ pack('L12', 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 1280, 0)
+);
+
+sync_with_i3;
+@docked = get_dock_clients('bottom');
+is(@docked, 1, 'dock client on bottom');
+
$window->destroy;
wait_for_unmap $window;
my $ws = get_ws($tmp);
ok(!$ws->{urgent}, 'urgent flag not set on workspace');
+##############################################################################
+# Regression test for #1187: Urgency hint moves to new workspace when moving
+# a container to another workspace.
+##############################################################################
+
+ my $tmp_source = fresh_workspace;
+ my $tmp_target = fresh_workspace;
+ cmd 'workspace ' . $tmp_source;
+ sync_with_i3;
+ my $w1 = open_window;
+ my $w2 = open_window;
+ sync_with_i3;
+ cmd '[id="' . $w1->id . '"] focus';
+ sync_with_i3;
+ cmd 'mark urgent_con';
+ cmd '[id="' . $w2->id . '"] focus';
+ set_urgency($w1, 1, $type);
+ sync_with_i3;
+ cmd '[con_mark="urgent_con"] move container to workspace ' . $tmp_target;
+ sync_with_i3;
+ my $source_ws = get_ws($tmp_source);
+ my $target_ws = get_ws($tmp_target);
+ ok(!$source_ws->{urgent}, 'Source workspace is no longer marked urgent');
+ is($target_ws->{urgent}, 1, 'Target workspace is now marked urgent');
+
+##############################################################################
+
exit_gracefully($pid);
}
'error for unknown literal ok');
################################################################################
-# 3: Verify that escaping of double quotes works correctly
+# 3: Verify that escaping works correctly
################################################################################
is(parser_calls('workspace "foo"'),
'cmd_workspace_name(foo "bar)',
'Command with escaped double quotes ok');
+is(parser_calls('workspace "foo \\'),
+ 'cmd_workspace_name(foo \\)',
+ 'Command with single backslash in the end ok');
+
+is(parser_calls('workspace "foo\\\\bar"'),
+ 'cmd_workspace_name(foo\\bar)',
+ 'Command with escaped backslashes ok');
+
+is(parser_calls('workspace "foo\\\\\\"bar"'),
+ 'cmd_workspace_name(foo\\"bar)',
+ 'Command with escaped double quotes after escaped backslashes ok');
+
################################################################################
# 4: Verify that resize commands with a "px or ppt"-construction are parsed
# correctly
#
# checks if mark and unmark work correctly
use i3test;
+use List::Util qw(first);
sub get_marks {
return i3(get_socket_path())->get_marks->recv;
}
+sub get_mark_for_window_on_workspace {
+ my ($ws, $con) = @_;
+
+ my $current = first { $_->{window} == $con->{id} } @{get_ws_content($ws)};
+ return $current->{mark};
+}
+
##############################################################
# 1: check that there are no marks set yet
##############################################################
is_deeply(get_marks(), [], 'all marks removed');
+##############################################################
+# 4: mark a con, use same mark to mark another con,
+# check that only the latter is marked
+##############################################################
+
+my $first = open_window;
+my $second = open_window;
+
+cmd 'mark important';
+cmd 'focus left';
+cmd 'mark important';
+
+is(get_mark_for_window_on_workspace($tmp, $first), 'important', 'first container now has the mark');
+ok(!get_mark_for_window_on_workspace($tmp, $second), 'second container lost the mark');
+
+##############################################################
+# 5: mark a con, toggle the mark, check that the mark is gone
+##############################################################
+
+my $con = open_window;
+cmd 'mark important';
+cmd 'mark --toggle important';
+ok(!get_mark_for_window_on_workspace($tmp, $con), 'container no longer has the mark');
+
+##############################################################
+# 6: toggle a mark on an unmarked con, check it is marked
+##############################################################
+
+my $con = open_window;
+cmd 'mark --toggle important';
+is(get_mark_for_window_on_workspace($tmp, $con), 'important', 'container now has the mark');
+
+##############################################################
+# 7: mark a con, toggle a different mark, check it is marked
+# with the new mark
+##############################################################
+
+my $con = open_window;
+cmd 'mark boring';
+cmd 'mark --toggle important';
+is(get_mark_for_window_on_workspace($tmp, $con), 'important', 'container has the most recent mark');
+
+##############################################################
+# 8: mark a con, toggle the mark on another con,
+# check only the latter has the mark
+##############################################################
+
+my $first = open_window;
+my $second = open_window;
+
+cmd 'mark important';
+cmd 'focus left';
+cmd 'mark --toggle important';
+
+is(get_mark_for_window_on_workspace($tmp, $first), 'important', 'left container has the mark now');
+ok(!get_mark_for_window_on_workspace($tmp, $second), 'second containr no longer has the mark');
+
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)
+#
+# Verifies that i3 does not crash when floating and then unfloating an
+# unfocused window within a tabbed container.
+# Ticket: #1484
+# Bug still in: 4.9.1-124-g856e1f9
+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
+workspace_layout tabbed
+EOT
+
+my $pid = launch_with_config($config);
+
+open_window;
+open_window;
+
+# Mark the second window, then focus the workspace.
+cmd 'mark foo, focus parent, focus parent';
+
+# Float and unfloat the marked window (without it being focused).
+cmd '[con_mark=foo] floating enable, floating disable';
+
+does_i3_live;
+
+exit_gracefully($pid);
+
+done_testing;