i3-command-parser.stamp
i3-config-parser.stamp
.clang_complete
+LAST_VERSION
include docs/docs.mk
include man/man.mk
+# Update $(TOPDIR)/LAST_VERSION if it differs from $I3_VERSION
+CACHED_VERSION := '$(shell [ -f $(TOPDIR)/LAST_VERSION ] && cat $(TOPDIR)/LAST_VERSION)'
+ifneq ($(CACHED_VERSION),$(I3_VERSION))
+$(shell echo -n ${I3_VERSION} > $(TOPDIR)/LAST_VERSION)
+endif
+
real-all: $(ALL_TARGETS)
install: $(INSTALL_TARGETS)
UNAME=$(shell uname)
DEBUG=1
-COVERAGE=0
INSTALL=install
LN=ln
ifndef PREFIX
-i3-wm (4.10.1-1) experimental; urgency=medium
+i3-wm (4.10.2-1) experimental; urgency=medium
* NOT YET RELEASED.
- -- Michael Stapelberg <stapelberg@debian.org> Sun, 29 Mar 2015 18:08:13 +0200
+ -- 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
== _NET_WM_STATE
-Only the _NET_WM_STATE_FULLSCREEN atom is handled. It calls
-``toggle_fullscreen()'' for the specific client which just configures the
-client to use the whole screen on which it currently is. Also, it is set as
-fullscreen_client for the i3Screen.
+Only the _NET_WM_STATE_FULLSCREEN and _NET_WM_STATE_DEMANDS_ATTENTION atoms
+are handled.
+
+The former calls ``toggle_fullscreen()'' for the specific client which just
+configures the client to use the whole screen on which it currently is.
+Also, it is set as fullscreen_client for the i3Screen.
+
+The latter is used to set, read and display urgency hints.
== WM_NAME
+x_draw_decoration+ draws window decorations. It is run for every leaf
container (representing an actual X11 window) and for every non-leaf container
which is in a stacked/tabbed container (because stacked/tabbed containers
-display a window decoration for split containers, which at the moment just says
-"another container").
+display a window decoration for split containers, which consists of a representation
+of the child container's names.
Then, parameters are collected to be able to determine whether this decoration
drawing is actually necessary or was already done. This saves a substantial
In earlier versions of i3, interpreting these commands was done using lex and
yacc, but experience has shown that lex and yacc are not well suited for our
command language. Therefore, starting from version 4.2, we use a custom parser
-for user commands (not yet for the configuration file).
+for user commands and the configuration file.
The input specification for this parser can be found in the file
-+parser-specs/commands.spec+. Should you happen to use Vim as an editor, use
++parser-specs/*.spec+. Should you happen to use Vim as an editor, use
:source parser-specs/highlighting.vim to get syntax highlighting for this file
(highlighting files for other editors are welcome).
# workspace next|prev|next_on_output|prev_on_output
# workspace back_and_forth
# workspace <name>
+# workspace number <number>
state WORKSPACE:
direction = 'next_on_output', 'prev_on_output', 'next', 'prev'
-> call cmd_workspace($direction)
'back_and_forth'
-> call cmd_workspace_back_and_forth()
+ 'number'
+ -> WORKSPACE_NUMBER
workspace = string
-> call cmd_workspace_name($workspace)
----------------------------------------------------------------
single quotes), but just called string. Other possible tokens are word
(the same as string, but stops matching at a whitespace) and end
(matches the end of the input).
+workspace number <number>::
+ The workspace command has to be followed by the keyword +number+. It
+ then transitions into the state +WORKSPACE_NUMBER+, where the actual
+ parameter will be read.
=== Introducing a new command
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
-command which will focus windows in an alt+tab like way.
+accepted. A good way for this is to open an issue and asking for opinions on it.
+Even for accepted features, this can be a good way to refine an idea upfront. However,
+we don't want to see certain features in i3, e.g., switching window focus in an
+Alt+Tab like way.
When working on bugfixes, please make sure you mention that you are working on
-it in the corresponding bugreport at https://github.com/i3/i3/issues In case
-there is no bugreport yet, please create one.
+it in the corresponding bug report at https://github.com/i3/i3/issues. In case
+there is no bug report yet, please create one.
-After you are done, please submit your work for review at https://github.com/i3/i3
+After you are done, please submit your work for review as a pull request at
+https://github.com/i3/i3.
Do not send emails to the mailing list or any author directly, and don’t submit
them in the bugtracker, since all reviews should be done in public at
-http://cr.i3wm.org/. In order to make your review go as fast as possible, you
+https://github.com/i3/i3. 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?
This will show the output of Xephyr, which is the X server implementation we
use for testing.
+==== Coverage testing
+
+Coverage testing is possible with +lcov+, the front-end for GCC's coverage
+testing tool +gcov+. The testcases can generate a nice html report that tells
+you which functions and lines were covered during a run of the tests. You can
+use this tool to judge how effective your tests are.
+
+To use test coverage tools, first compile with coverage enabled.
+
+---------------------------------------------------
+COVERAGE=1 make
+---------------------------------------------------
+
+Then run the tests with the +--coverage-testing+ flag.
+
+---------------------------------------------------
+./complete-run.pl --coverage-testing
+---------------------------------------------------
+
+Then open +latest/i3-coverage/index.html+ in your web browser.
+
==== IPC interface
The testsuite makes extensive use of the IPC (Inter-Process Communication)
you moved down is directly attached to the workspace and appears on the bottom
of the screen. A new (horizontal) container was created to accommodate the
other two terminal windows. You will notice this when switching to tabbed mode
-(for example). You would end up having one tab called "another container" and
-the other one being the terminal window you moved down.
+(for example). You would end up having one tab with a representation of the split
+container (e.g., "H[urxvt firefox]") and the other one being the terminal window
+you moved down.
[[configuring]]
== Configuring i3
The valid criteria are the same as those for commands, see <<command_criteria>>.
+=== Don't focus window upon opening
+
+[[no_focus]]
+
+When a new window appears, it will be focused. The +no_focus+ directive allows preventing
+this from happening and can be used in combination with <<command_criteria>>.
+
+Note that this does not apply to all cases, e.g., when feeding data into a running application
+causing it to request being focused. To configure the behavior in such cases, refer to
+<<focus_on_window_activation>>.
+
+*Syntax*:
+-------------------
+no_focus <criteria>
+-------------------
+
+*Example*:
+-------------------------------
+no_focus [window_role="pop-up"]
+-------------------------------
+
=== Variables
As you learned in the section about keyboard bindings, you will have
force_display_urgency_hint 500 ms
---------------------------------
+=== Focus on window activation
+
+[[focus_on_window_activation]]
+
+If a window is activated, e.g., via +google-chrome www.google.com+, it may request
+to take focus. Since this may not preferable, different reactions can be configured.
+
+Note that this may not affect windows that are being opened. To prevent new windows
+from being focused, see <<no_focus>>.
+
+*Syntax*:
+----------------------------------------------------
+focus_on_window_activation <smart|urgent|focus|none>
+----------------------------------------------------
+
+The different modes will act as follows:
+
+smart::
+ This is the default behavior. If the window requesting focus is on an active
+ workspace, it will receive the focus. Otherwise, the urgency hint will be set.
+urgent::
+ The window will always be marked urgent, but the focus will not be stolen.
+focus::
+ The window will always be focused and not be marked urgent.
+none::
+ The window will neither be focused, nor be marked urgent.
+
+=== Drawing marks on window decoration
+
+If activated, marks on windows are drawn in their window decoration. However,
+any mark starting with an underscore in its name (+_+) will not be drawn even if
+this option is activated.
+
+The default for this option is +yes+.
+
+*Syntax*:
+-------------------
+show_marks [yes|no]
+-------------------
+
+*Example*:
+--------------
+show_marks yes
+--------------
+
== Configuring i3bar
The bar at the bottom of your monitor is drawn by a separate process called
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.
+
+Refer to +show_marks+ if you don't want marks to be shown in the window decoration.
+
*Syntax*:
------------------------------
-mark identifier
+mark [--toggle] identifier
[con_mark="identifier"] focus
unmark identifier
------------------------------
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.
*
set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
txt(logical_px(10), 2, "You have not configured i3 yet.");
- txt(logical_px(10), 3, "Do you want me to generate ~/.i3/config?");
- txt(logical_px(85), 5, "Yes, generate ~/.i3/config");
- txt(logical_px(85), 7, "No, I will use the defaults");
+ 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(logical_px(25), 5, "<Enter>");
+ txt(logical_px(25), 6, "<Enter>");
/* red */
set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000"));
- txt(logical_px(31), 7, "<ESC>");
+ txt(logical_px(31), 8, "<ESC>");
}
if (current_step == STEP_GENERATE) {
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 ~/.i3/config");
+ txt(logical_px(85), 9, "to write the config");
txt(logical_px(85), 10, "to abort");
/* the not-selected modifier */
}
int main(int argc, char *argv[]) {
- config_path = resolve_tilde("~/.i3/config");
+ char *xdg_config_home;
socket_path = getenv("I3SOCK");
char *pattern = "pango:monospace 8";
char *patternbold = "pango:monospace bold 8";
}
}
- /* 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) {
return 1;
}
+static xcb_rectangle_t get_window_position(void) {
+ xcb_rectangle_t result = (xcb_rectangle_t){logical_px(50), logical_px(50), logical_px(500), font.height + logical_px(8)};
+
+ xcb_get_input_focus_reply_t *input_focus = NULL;
+ xcb_get_geometry_reply_t *geometry = NULL;
+ xcb_translate_coordinates_reply_t *coordinates = NULL;
+
+ /* In rare cases, the window holding the input focus might disappear while we are figuring out its
+ * position. To avoid this, we grab the server in the meantime. */
+ xcb_grab_server(conn);
+
+ input_focus = xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL);
+ if (input_focus == NULL || input_focus->focus == XCB_NONE) {
+ DLOG("Failed to receive the current input focus or no window has the input focus right now.\n");
+ goto free_resources;
+ }
+
+ geometry = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, input_focus->focus), NULL);
+ if (geometry == NULL) {
+ DLOG("Failed to received window geometry.\n");
+ goto free_resources;
+ }
+
+ coordinates = xcb_translate_coordinates_reply(
+ conn, xcb_translate_coordinates(conn, input_focus->focus, root, geometry->x, geometry->y), NULL);
+ if (coordinates == NULL) {
+ DLOG("Failed to translate coordinates.\n");
+ goto free_resources;
+ }
+
+ DLOG("Determined coordinates of window with input focus at x = %i / y = %i", coordinates->dst_x, coordinates->dst_y);
+ result.x += coordinates->dst_x;
+ result.y += coordinates->dst_y;
+
+free_resources:
+ xcb_ungrab_server(conn);
+ xcb_flush(conn);
+
+ FREE(input_focus);
+ FREE(geometry);
+ FREE(coordinates);
+ return result;
+}
+
int main(int argc, char *argv[]) {
format = strdup("%s");
socket_path = getenv("I3SOCK");
if (prompt != NULL)
prompt_offset = predict_text_width(prompt);
+ const xcb_rectangle_t win_pos = get_window_position();
+
/* Open an input window */
win = xcb_generate_id(conn);
xcb_create_window(
conn,
XCB_COPY_FROM_PARENT,
- 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 */
+ win, /* the window id */
+ root, /* parent == root */
+ 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 */
XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK,
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"
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
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_create_window(
conn,
XCB_COPY_FROM_PARENT,
- win, /* the window id */
- root, /* parent == root */
- 50, 50, 500, font.height + logical_px(8) + logical_px(8) /* 8 px padding */, /* dimensions */
- 0, /* x11 border = 0, we draw our own */
+ win, /* the window id */
+ root, /* parent == root */
+ 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 */
XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
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]'
* flag can be delayed using an urgency timer. */
float workspace_urgency_timer;
+ /** Behavior when a window sends a NET_ACTIVE_WINDOW message. */
+ enum {
+ /* Focus if the target workspace is visible, set urgency hint otherwise. */
+ FOWA_SMART,
+ /* Always set the urgency hint. */
+ FOWA_URGENT,
+ /* Always focus the window. */
+ FOWA_FOCUS,
+ /* Ignore the request (no focus, no urgency hint). */
+ FOWA_NONE
+ } focus_on_window_activation;
+
+ /** Specifies whether or not marks should be displayed in the window
+ * decoration. Marks starting with a "_" will be ignored either way. */
+ bool show_marks;
+
/** The default border style for new windows. */
border_style_t default_border;
CFGFUN(force_xinerama, const char *value);
CFGFUN(fake_outputs, const char *outputs);
CFGFUN(force_display_urgency_hint, const long duration_ms);
+CFGFUN(focus_on_window_activation, const char *mode);
+CFGFUN(show_marks, const char *value);
CFGFUN(hide_edge_borders, const char *borders);
CFGFUN(assign, const char *workspace);
+CFGFUN(no_focus);
CFGFUN(ipc_socket, const char *path);
CFGFUN(restart_state, const char *path);
CFGFUN(popup_during_fullscreen, const char *value);
*
* A_COMMAND = run the specified command for the matching window
* A_TO_WORKSPACE = assign the matching window to the specified workspace
+ * A_NO_FOCUS = don't focus matched window when it is managed
*
* While the type is a bitmask, only one value can be set at a time. It is
* a bitmask to allow filtering for multiple types, for example in the
enum {
A_ANY = 0,
A_COMMAND = (1 << 0),
- A_TO_WORKSPACE = (1 << 1)
+ A_TO_WORKSPACE = (1 << 1),
+ A_NO_FOCUS = (1 << 2)
} type;
/** the criteria to check if a window matches */
/* user-definable mark to jump to this container later */
char *mark;
+ /* cached to decide whether a redraw is needed */
+ bool mark_changed;
double percent;
*/
bool floating_maybe_reassign_ws(Con *con);
+/**
+ * Centers a floating con above the specified rect.
+ *
+ */
+void floating_center(Con *con, Rect rect);
+
#if 0
/**
* Removes the floating client from its workspace and attaches it to the new
#include "data.h"
#include "xcb.h"
+/** Git commit identifier, from version.c */
+extern const char *i3_version;
+
/** The original value of RLIMIT_CORE when i3 was started. We need to restore
* this before starting any other process, since we set RLIMIT_CORE to
* RLIM_INFINITY for i3 debugging versions. */
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 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;
+}
--- /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;
+}
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:
'hide_edge_borders' -> HIDE_EDGE_BORDERS
'for_window' -> FOR_WINDOW
'assign' -> ASSIGN
+ 'no_focus' -> NO_FOCUS
'focus_follows_mouse' -> FOCUS_FOLLOWS_MOUSE
'mouse_warping' -> MOUSE_WARPING
'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING
'workspace_auto_back_and_forth' -> WORKSPACE_BACK_AND_FORTH
'fake_outputs', 'fake-outputs' -> FAKE_OUTPUTS
'force_display_urgency_hint' -> FORCE_DISPLAY_URGENCY_HINT
+ 'focus_on_window_activation' -> FOCUS_ON_WINDOW_ACTIVATION
+ 'show_marks' -> SHOW_MARKS
'workspace' -> WORKSPACE
'ipc_socket', 'ipc-socket' -> IPC_SOCKET
'restart_state' -> RESTART_STATE
workspace = string
-> call cfg_assign($workspace)
+# no_focus <criteria>
+state NO_FOCUS:
+ '['
+ -> call cfg_criteria_init(NO_FOCUS_END); CRITERIA
+
+state NO_FOCUS_END:
+ end
+ -> call cfg_no_focus()
+
# Criteria: Used by for_window and assign.
state CRITERIA:
ctype = 'class' -> CRITERION
duration_ms = number
-> FORCE_DISPLAY_URGENCY_HINT_MS
+# show_marks
+state SHOW_MARKS:
+ value = word
+ -> call cfg_show_marks($value)
+
state FORCE_DISPLAY_URGENCY_HINT_MS:
'ms'
->
end
-> call cfg_force_display_urgency_hint(&duration_ms)
+# focus_on_window_activation <smart|urgent|focus|none>
+state FOCUS_ON_WINDOW_ACTIVATION:
+ mode = word
+ -> call cfg_focus_on_window_activation($mode)
+
# workspace <workspace> output <output>
state WORKSPACE:
workspace = word
state MODE_BINDING:
release = '--release'
->
+ whole_window = '--whole-window'
+ ->
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod'
->
'+'
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+"
if (con->parent->type == CT_DOCKAREA)
goto done;
+ const bool is_left_or_right_click = (event->detail == XCB_BUTTON_INDEX_1 ||
+ event->detail == XCB_BUTTON_INDEX_3);
+
/* if the user has bound an action to this click, it should override the
* default behavior. */
if (dest == CLICK_DECORATION || dest == CLICK_INSIDE) {
return 1;
}
- if (!in_stacked && dest == CLICK_DECORATION) {
+ if (!in_stacked && dest == CLICK_DECORATION &&
+ is_left_or_right_click) {
/* try tiling resize, but continue if it doesn’t work */
DLOG("tiling resize with fallback\n");
if (tiling_resize(con, event, dest))
return 1;
}
- if (dest == CLICK_BORDER) {
+ if (dest == CLICK_BORDER && is_left_or_right_click) {
DLOG("floating resize due to border click\n");
floating_resize_window(floatingcon, proportional, event);
return 1;
/* 6: dragging, if this was a click on a decoration (which did not lead
* to a resize) */
- if (!in_stacked && dest == CLICK_DECORATION) {
+ if (!in_stacked && dest == CLICK_DECORATION &&
+ (event->detail == XCB_BUTTON_INDEX_1)) {
floating_drag_window(floatingcon, event);
return 1;
}
}
/* 8: otherwise, check for border/decoration clicks and resize */
else if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) &&
- (event->detail == XCB_BUTTON_INDEX_1 ||
- event->detail == XCB_BUTTON_INDEX_3)) {
+ is_left_or_right_click) {
DLOG("Trying to resize (tiling)\n");
tiling_resize(con, event, dest);
}
}
/*
- * 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;
- Con *con;
- TAILQ_FOREACH(con, &all_cons, all_cons) {
- if (con->mark && strcmp(con->mark, mark) == 0)
- FREE(con->mark);
+ owindow *current = TAILQ_FIRST(&owindows);
+ if (current == NULL) {
+ ysuccess(false);
+ return;
}
- DLOG("marking window with str %s\n", mark);
- owindow *current;
-
- HANDLE_EMPTY_MATCH;
+ /* Marks must be unique, i.e., no two windows must have the same mark. */
+ if (current != TAILQ_LAST(&owindows, owindows_head)) {
+ yerror("A mark must not be put onto more than one window");
+ return;
+ }
- TAILQ_FOREACH(current, &owindows, owindows) {
- DLOG("matching: %p / %s\n", current->con, current->con->name);
+ DLOG("matching: %p / %s\n", current->con, current->con->name);
+ current->con->mark_changed = true;
+ 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 window, we took care of it already. */
+ if (current->con == con)
+ continue;
+
+ if (con->mark && strcmp(con->mark, mark) == 0) {
+ FREE(con->mark);
+ con->mark_changed = true;
+ }
+ }
+
cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply
ysuccess(true);
if (mark == NULL) {
Con *con;
TAILQ_FOREACH(con, &all_cons, all_cons) {
+ if (con->mark == NULL)
+ continue;
+
FREE(con->mark);
+ con->mark_changed = true;
}
DLOG("removed all window marks");
} else {
Con *con;
TAILQ_FOREACH(con, &all_cons, all_cons) {
- if (con->mark && strcmp(con->mark, mark) == 0)
+ if (con->mark && strcmp(con->mark, mark) == 0) {
FREE(con->mark);
+ con->mark_changed = true;
+ }
}
DLOG("removed window mark %s\n", mark);
}
}
if (strcmp(method, "absolute") == 0) {
- Rect *rect = &focused->parent->rect;
-
DLOG("moving to absolute center\n");
- rect->x = croot->rect.width / 2 - rect->width / 2;
- rect->y = croot->rect.height / 2 - rect->height / 2;
+ floating_center(focused->parent, croot->rect);
floating_maybe_reassign_ws(focused->parent);
cmd_output->needs_tree_render = true;
}
if (strcmp(method, "position") == 0) {
- Rect *wsrect = &con_get_workspace(focused)->rect;
- Rect newrect = focused->parent->rect;
-
DLOG("moving to center\n");
- newrect.x = wsrect->width / 2 - newrect.width / 2;
- newrect.y = wsrect->height / 2 - newrect.height / 2;
+ floating_center(focused->parent, con_get_workspace(focused)->rect);
- floating_reposition(focused->parent, newrect);
+ cmd_output->needs_tree_render = true;
}
// XXX: default reply for now, make this a better reply
}
}
+ /* 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);
}
* with an orientation). Since we switched to splith/splitv layouts,
* using the "default" layout (which "only" should happen when using
* legacy configs) is using the last split layout (either splith or
- * splitv) in order to still do the same thing.
- *
- * Starting from v4.6 though, we will nag users about using "layout
- * default", and in v4.9 we will remove it entirely (with an
- * appropriate i3-migrate-config mechanism). */
+ * splitv) in order to still do the same thing. */
con->layout = con->last_split_layout;
/* In case last_split_layout was not initialized… */
if (con->layout == L_DEFAULT)
}
}
-/*
- * 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;
INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888", "#000000");
INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff", "#000000");
+ config.show_marks = true;
+
config.default_border = BS_NORMAL;
config.default_floating_border = BS_NORMAL;
config.default_border_width = logical_px(2);
config.workspace_urgency_timer = duration_ms / 1000.0;
}
+CFGFUN(focus_on_window_activation, const char *mode) {
+ if (strcmp(mode, "smart") == 0)
+ config.focus_on_window_activation = FOWA_SMART;
+ else if (strcmp(mode, "urgent") == 0)
+ config.focus_on_window_activation = FOWA_URGENT;
+ else if (strcmp(mode, "focus") == 0)
+ config.focus_on_window_activation = FOWA_FOCUS;
+ else if (strcmp(mode, "none") == 0)
+ config.focus_on_window_activation = FOWA_NONE;
+ else {
+ ELOG("Unknown focus_on_window_activation mode \"%s\", ignoring it.\n", mode);
+ return;
+ }
+
+ DLOG("Set new focus_on_window_activation mode = %i", config.focus_on_window_activation);
+}
+
+CFGFUN(show_marks, const char *value) {
+ config.show_marks = eval_boolstr(value);
+}
+
CFGFUN(workspace, const char *workspace, const char *output) {
DLOG("Assigning workspace \"%s\" to output \"%s\"\n", workspace, output);
/* Check for earlier assignments of the same workspace so that we
TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
}
+CFGFUN(no_focus) {
+ if (match_is_empty(current_match)) {
+ ELOG("Match is empty, ignoring this assignment\n");
+ return;
+ }
+
+ DLOG("new assignment, using above criteria, to ignore focus on manage");
+ Assignment *assignment = scalloc(sizeof(Assignment));
+ match_copy(&(assignment->match), current_match);
+ assignment->type = A_NO_FOCUS;
+ TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
+}
+
/*******************************************************************************
* Bar configuration (i3bar)
******************************************************************************/
check_for_duplicate_bindings(context);
if (use_nagbar && (context->has_errors || context->has_warnings)) {
- ELOG("FYI: You are using i3 version " I3_VERSION "\n");
+ ELOG("FYI: You are using i3 version %s\n", i3_version);
if (version == 3)
ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n");
if (con->window && con->window->leader != XCB_NONE &&
(leader = con_by_window_id(con->window->leader)) != NULL) {
DLOG("Centering above leader\n");
- nc->rect.x = leader->rect.x + (leader->rect.width / 2) - (nc->rect.width / 2);
- nc->rect.y = leader->rect.y + (leader->rect.height / 2) - (nc->rect.height / 2);
+ floating_center(nc, leader->rect);
} else {
/* center the window on workspace as fallback */
- nc->rect.x = ws->rect.x + (ws->rect.width / 2) - (nc->rect.width / 2);
- nc->rect.y = ws->rect.y + (ws->rect.height / 2) - (nc->rect.height / 2);
+ floating_center(nc, ws->rect);
}
}
}
ELOG("No output found at destination coordinates, centering floating window on current ws\n");
- nc->rect.x = ws->rect.x + (ws->rect.width / 2) - (nc->rect.width / 2);
- nc->rect.y = ws->rect.y + (ws->rect.height / 2) - (nc->rect.height / 2);
+ floating_center(nc, ws->rect);
ipc_send_window_event("floating", con);
}
return true;
}
+/*
+ * Centers a floating con above the specified rect.
+ *
+ */
+void floating_center(Con *con, Rect rect) {
+ con->rect.x = rect.x + (rect.width / 2) - (con->rect.width / 2);
+ con->rect.y = rect.y + (rect.height / 2) - (con->rect.height / 2);
+}
+
DRAGGING_CB(drag_window_callback) {
const struct xcb_button_press_event_t *event = extra;
workspace_show(ws);
con_focus(con);
} else {
- /* If the request is from an application, only focus if the
- * workspace is visible. Otherwise set the urgency hint. */
- if (workspace_is_visible(ws)) {
- DLOG("Request to focus con on a visible workspace. Focusing con = %p\n", con);
+ /* Request is from an application. */
+
+ if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) {
+ DLOG("Focusing con = %p\n", con);
workspace_show(ws);
con_focus(con);
- } else {
- DLOG("Request to focus con on a hidden workspace. Setting urgent con = %p\n", con);
+ } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
+ DLOG("Marking con = %p urgent\n", con);
con_set_urgency(con, true);
- }
+ } else
+ DLOG("Ignoring request for con = %p", con);
}
tree_render();
echo "[i3] PCH all.h"
$(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -x c-header include/all.h -o include/all.h.pch
+src/version.o: src/version.c LAST_VERSION $(i3_HEADERS_DEP)
+ echo "[i3] CC $<"
+ $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(PCH_FLAGS) -c -o $@ ${canonical_path}/$<
+
src/%.o: src/%.c $(i3_HEADERS_DEP)
echo "[i3] CC $<"
$(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(PCH_FLAGS) -c -o $@ ${canonical_path}/$<
clean-i3:
echo "[i3] Clean"
- rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3-config-parser.stamp i3 test.config_parser test.commands_parser src/*.gcno src/cfgparse.* src/cmdparse.*
+ rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3-config-parser.stamp i3 test.config_parser test.commands_parser src/*.gcno src/cfgparse.* src/cmdparse.* LAST_VERSION
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.
y(integer, PATCH_VERSION);
ystr("human_readable");
- ystr(I3_VERSION);
+ ystr(i3_version);
y(map_close);
int main(int argc, char *argv[]) {
/* Keep a symbol pointing to the I3_VERSION string constant so that we have
* it in gdb backtraces. */
- const char *i3_version __attribute__((unused)) = I3_VERSION;
+ const char *_i3_version __attribute__((unused)) = i3_version;
char *override_configpath = NULL;
bool autostart = true;
char *layout_path = NULL;
only_check_config = true;
break;
case 'v':
- printf("i3 version " I3_VERSION " © 2009-2014 Michael Stapelberg and contributors\n");
+ printf("i3 version %s © 2009-2014 Michael Stapelberg and contributors\n", i3_version);
exit(EXIT_SUCCESS);
break;
case 'm':
- printf("Binary i3 version: " I3_VERSION " © 2009-2014 Michael Stapelberg and contributors\n");
+ printf("Binary i3 version: %s © 2009-2014 Michael Stapelberg and contributors\n", i3_version);
display_running_version();
exit(EXIT_SUCCESS);
break;
free(cwd);
}
- LOG("i3 " I3_VERSION " starting\n");
+ LOG("i3 %s starting\n", i3_version);
conn = xcb_connect(NULL, &conn_screen);
if (xcb_connection_has_error(conn))
/* Defer setting focus after the 'new' event has been sent to ensure the
* proper window event sequence. */
if (set_focus && !nc->window->doesnt_accept_focus && nc->mapped) {
- DLOG("Now setting focus.\n");
- con_focus(nc);
+ if (assignment_for(cwindow, A_NO_FOCUS) == NULL) {
+ DLOG("Now setting focus.\n");
+ con_focus(nc);
+ }
}
tree_render();
? AFTER
: BEFORE);
insert_con_into(con, target, position);
+ } else if (con->parent->parent->type == CT_WORKSPACE &&
+ con->parent->layout != L_DEFAULT &&
+ con_num_children(con->parent) == 1) {
+ /* Con is the lone child of a non-default layout container at the edge
+ * of the workspace. Treat it as though the workspace is its parent
+ * and move it to the next output. */
+ DLOG("Grandparent is workspace\n");
+ move_to_output_directed(con, direction);
} else {
DLOG("Moving into container above\n");
position = (direction == D_UP || direction == D_LEFT ? BEFORE : AFTER);
con->rect.width = output->rect.width * 0.5;
con->rect.height = output->rect.height * 0.75;
floating_check_size(con);
- con->rect.x = output->rect.x +
- ((output->rect.width / 2.0) - (con->rect.width / 2.0));
- con->rect.y = output->rect.y +
- ((output->rect.height / 2.0) - (con->rect.height / 2.0));
+ floating_center(con, con_get_workspace(con)->rect);
}
/* Activate active workspace if window is from another workspace to ensure
}
}
-/*
- * 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().
*
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2015 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * Stores the latest Git commit identifier so that it can be linked into i3
+ * and used dynamically without recompiling every object file.
+ *
+ */
+const char *i3_version = I3_VERSION;
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;
}
(con->window == NULL || !con->window->name_x_changed) &&
!parent->pixmap_recreated &&
!con->pixmap_recreated &&
+ !con->mark_changed &&
memcmp(p, con->deco_render_params, sizeof(struct deco_render_params)) == 0) {
free(p);
goto copy_pixmaps;
parent->pixmap_recreated = false;
con->pixmap_recreated = false;
+ con->mark_changed = false;
/* 2: draw the client.background, but only for the parts around the client_rect */
if (con->window != NULL) {
//DLOG("indent_level = %d, indent_mult = %d\n", indent_level, indent_mult);
int indent_px = (indent_level * 5) * indent_mult;
+ int mark_width = 0;
+ if (config.show_marks && con->mark != NULL && (con->mark)[0] != '_') {
+ char *formatted_mark;
+ sasprintf(&formatted_mark, "[%s]", con->mark);
+ i3String *mark = i3string_from_utf8(formatted_mark);
+ FREE(formatted_mark);
+ mark_width = predict_text_width(mark);
+
+ draw_text(mark, parent->pixmap, parent->pm_gc,
+ con->deco_rect.x + con->deco_rect.width - mark_width - logical_px(2),
+ con->deco_rect.y + text_offset_y, mark_width);
+
+ I3STRING_FREE(mark);
+ }
+
draw_text(win->name,
parent->pixmap, parent->pm_gc,
- con->deco_rect.x + 2 + indent_px, con->deco_rect.y + text_offset_y,
- con->deco_rect.width - 2 - indent_px);
+ con->deco_rect.x + logical_px(2) + indent_px, con->deco_rect.y + text_offset_y,
+ con->deco_rect.width - logical_px(2) - indent_px - mark_width - logical_px(2));
after_title:
/* Since we don’t clip the text at all, it might in some cases be painted
MIN_PERL_VERSION => '5.010000', # 5.10.0
PREREQ_PM => {
'AnyEvent' => 0,
- 'AnyEvent::I3' => '0.15',
- 'X11::XCB' => '0.09',
+ 'AnyEvent::I3' => '0.16',
+ 'X11::XCB' => '0.12',
'Inline' => 0,
'Inline::C' => 0,
'ExtUtils::PkgConfig' => 0,
use utf8;
# the following are modules which ship with Perl (>= 5.10):
use Pod::Usage;
-use Cwd qw(abs_path);
use File::Temp qw(tempfile tempdir);
use Getopt::Long;
use POSIX ();
use TAP::Parser::Aggregator;
use Time::HiRes qw(time);
use IO::Handle;
+
+my $dirname;
+
+BEGIN {
+ use File::Basename;
+ use Cwd qw(abs_path);
+
+ # fileparse()[1] contains the directory portion of the specified path.
+ # See File::Basename(3p) for more details.
+ $dirname = (fileparse(abs_path($0)))[1];
+}
+
# these are shipped with the testsuite
-use lib qw(lib);
+use lib $dirname . 'lib';
use StartXServer;
use StatusLine;
use TestWorker;
pod2usage(-verbose => 2, -exitcode => 0) if $help;
+chdir $dirname or die "Could not chdir into $dirname";
+
# Check for missing executables
my @binaries = qw(
../i3
die "$binary is not an executable" unless -x $binary;
}
+if ($options{coverage}) {
+ qx(command -v lcov &> /dev/null);
+ die "Cannot find lcov needed for coverage testing." if $?;
+ qx(command -v genhtml &> /dev/null);
+ die "Cannot find genhtml needed for coverage testing." if $?;
+
+ # clean out the counters that may be left over from previous tests.
+ qx(lcov -d ../ --zerocounters &> /dev/null);
+}
+
qx(Xephyr -help 2>&1);
die "Xephyr was not found in your path. Please install Xephyr (xserver-xephyr on Debian)." if $?;
END { cleanup() }
+if ($options{coverage}) {
+ print("\nGenerating test coverage report...\n");
+ qx(lcov -d ../ -b ../ --capture -o latest/i3-coverage.info);
+ qx(genhtml -o latest/i3-coverage latest/i3-coverage.info);
+ if ($?) {
+ print("Could not generate test coverage html. Did you compile i3 with test coverage support?\n");
+ } else {
+ print("Test coverage report generated in latest/i3-coverage\n");
+ }
+}
+
exit ($aggregator->failed > 0);
#
=item B<--coverage-testing>
-Exits i3 cleanly (instead of kill -9) to make coverage testing work properly.
+Generates a test coverage report at C<latest/i3-coverage>. Exits i3 cleanly
+during tests (instead of kill -9) to make coverage testing work properly.
=item B<--parallel>
-Number of Xephyr instances to start (if you don’t want to start num_cores * 2
+Number of Xephyr instances to start (if you don't want to start num_cores * 2
instances for some reason).
# Run all tests on a single Xephyr instance
./complete-run.pl -p 1
+
+=back
=head2 cmd($command)
-Sends the specified command to i3.
+Sends the specified command to i3 and returns the output.
my $ws = unused_workspace;
cmd "workspace $ws";
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);
}
is_num_children($first_ws, 3, 'three containers on the first workspace');
+# empty 'from' workspaces should not crash the renaming of startup sequences
+cmd "workspace $first_ws";
+cmd "rename workspace to temp";
+cmd "rename workspace to $first_ws";
+
# Switch to the first workspace and move the focused window to the
# second workspace.
cmd "workspace $first_ws";
bindsym Mod1 + Shift + x resize grow
bindcode Mod1+44 resize shrink
bindsym --release Mod1+x exec foo
+ bindsym --whole-window button3 nop
+ bindsym --release --whole-window button3 nop
}
EOT
cfg_mode_binding(bindsym, Mod1,Shift, x, (null), (null), resize grow)
cfg_mode_binding(bindcode, Mod1, 44, (null), (null), resize shrink)
cfg_mode_binding(bindsym, Mod1, x, --release, (null), exec foo)
+cfg_mode_binding(bindsym, (null), button3, (null), --whole-window, nop)
+cfg_mode_binding(bindsym, (null), button3, --release, --whole-window, nop)
EOT
is(parser_calls($config),
$expected,
- 'single number (move workspace 3) ok');
+ 'mode bindings ok');
################################################################################
# exec and exec_always
EOT
my $expected_all_tokens = <<'EOT';
-ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'mouse_warping', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent', 'client.placeholder'
+ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'no_focus', 'focus_follows_mouse', 'mouse_warping', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'focus_on_window_activation', 'show_marks', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent', 'client.placeholder'
EOT
my $expected_end = <<'EOT';
#
# 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');
+
+##############################################################
+# 9: try to mark two cons with the same mark and check that
+# it fails
+##############################################################
+
+my $first = open_window(wm_class => 'iamnotunique');
+my $second = open_window(wm_class => 'iamnotunique');
+
+my $result = cmd "[instance=iamnotunique] mark important";
+
+is($result->[0]->{success}, 0, 'command was unsuccessful');
+is($result->[0]->{error}, 'A mark must not be put onto more than one window', 'correct error is returned');
+ok(!get_mark_for_window_on_workspace($tmp, $first), 'first container is not marked');
+ok(!get_mark_for_window_on_workspace($tmp, $second), 'second containr is not marked');
+
+##############################################################
+
done_testing;
my $pid = launch_with_config($config);
sub change_window_class {
- my ($window, $class) = @_;
+ my ($window, $class, $length) = @_;
my $atomname = $x->atom(name => 'WM_CLASS');
my $atomtype = $x->atom(name => 'STRING');
+ $length ||= length($class) + 1;
$x->change_property(
PROP_MODE_REPLACE,
$window->id,
$atomname->id,
$atomtype->id,
8,
- length($class) + 1,
+ $length,
$class
);
sync_with_i3;
is($con->{mark}, 'special_class_mark',
'A `for_window` assignment should run for a match when the window changes class');
+change_window_class($win, "abcdefghijklmnopqrstuv\0abcd", 24);
+
+$con = @{get_ws_content($ws)}[0];
+
+is($con->{window_properties}->{class}, 'a',
+ 'Non-null-terminated strings should be handled correctly');
+
exit_gracefully($pid);
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)
+#
+# Tests for the focus_on_window_activation directive
+# Ticket: #1426
+use i3test i3_autostart => 0;
+use List::Util qw(first);
+
+sub send_net_active_window {
+ my ($id) = @_;
+
+ my $msg = pack "CCSLLLLLLL",
+ X11::XCB::CLIENT_MESSAGE, # response_type
+ 32, # format
+ 0, # sequence
+ $id, # destination window
+ $x->atom(name => '_NET_ACTIVE_WINDOW')->id,
+ 0, # source
+ 0, 0, 0, 0;
+
+ $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
+}
+
+sub get_urgency_for_window_on_workspace {
+ my ($ws, $con) = @_;
+
+ my $current = first { $_->{window} == $con->{id} } @{get_ws_content($ws)};
+ return $current->{urgent};
+}
+
+#####################################################################
+# 1: If mode is set to 'urgent' and the target workspace is visible,
+# check that the urgent flag is set and focus is not lost.
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_on_window_activation urgent
+EOT
+
+my $pid = launch_with_config($config);
+
+my $ws = fresh_workspace;
+my $first = open_window;
+my $second = open_window;
+
+send_net_active_window($first->id);
+sync_with_i3;
+
+is($x->input_focus, $second->id, 'second window is still focused');
+is(get_urgency_for_window_on_workspace($ws, $first), 1, 'first window is marked urgent');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 2: If mode is set to 'urgent' and the target workspace is not
+# visible, check that the urgent flag is set and focus is not lost.
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_on_window_activation urgent
+EOT
+
+my $pid = launch_with_config($config);
+
+my $ws1 = fresh_workspace;
+my $first = open_window;
+my $ws2 = fresh_workspace;
+my $second = open_window;
+
+send_net_active_window($first->id);
+sync_with_i3;
+
+is(focused_ws(), $ws2, 'second workspace is still focused');
+is($x->input_focus, $second->id, 'second window is still focused');
+is(get_urgency_for_window_on_workspace($ws1, $first), 1, 'first window is marked urgent');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 3: If mode is set to 'focus' and the target workspace is visible,
+# check that the focus is switched.
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_on_window_activation focus
+EOT
+
+my $pid = launch_with_config($config);
+
+my $ws = fresh_workspace;
+my $first = open_window;
+my $second = open_window;
+
+send_net_active_window($first->id);
+sync_with_i3;
+
+is($x->input_focus, $first->id, 'first window is now focused');
+ok(!get_urgency_for_window_on_workspace($ws, $first), 'first window is not marked urgent');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 4: If mode is set to 'focus' and the target workspace is not
+# visible, check that the focus switched.
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_on_window_activation focus
+EOT
+
+my $pid = launch_with_config($config);
+
+my $ws1 = fresh_workspace;
+my $first = open_window;
+my $ws2 = fresh_workspace;
+my $second = open_window;
+
+send_net_active_window($first->id);
+sync_with_i3;
+
+is(focused_ws(), $ws1, 'first workspace is now focused');
+is($x->input_focus, $first->id, 'first window is now focused');
+ok(!get_urgency_for_window_on_workspace($ws1, $first), 'first window is not marked urgent');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 5: If mode is set to 'none' and the target workspace is visible,
+# check that nothing happens.
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_on_window_activation none
+EOT
+
+my $pid = launch_with_config($config);
+
+my $ws = fresh_workspace;
+my $first = open_window;
+my $second = open_window;
+
+send_net_active_window($first->id);
+sync_with_i3;
+
+is($x->input_focus, $second->id, 'second window is still focused');
+ok(!get_urgency_for_window_on_workspace($ws, $first), 'first window is not marked urgent');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 6: If mode is set to 'none' and the target workspace is not
+# visible, check that nothing happens.
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_on_window_activation none
+EOT
+
+my $pid = launch_with_config($config);
+
+my $ws1 = fresh_workspace;
+my $first = open_window;
+my $ws2 = fresh_workspace;
+my $second = open_window;
+
+send_net_active_window($first->id);
+sync_with_i3;
+
+is(focused_ws(), $ws2, 'second workspace is still focused');
+is($x->input_focus, $second->id, 'second window is still focused');
+ok(!get_urgency_for_window_on_workspace($ws1, $first), 'first window is not marked urgent');
+
+exit_gracefully($pid);
+
+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;
--- /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 most of i3's centering methods produce consistent results.
+# Decorations are disabled to avoid floating_enable's logic which shifts
+# windows upwards dependent on their decoration height.
+#
+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
+
+new_window none
+new_float none
+EOT
+
+my $pid = launch_with_config($config);
+
+#####################################################################
+# Open a floating window, verifying that its initial position is
+# centered, and also verify that both centering methods leave it in
+# its original spot.
+#####################################################################
+
+my $first = open_floating_window;
+
+my $initial = $first->rect;
+is(int($initial->{x} + $initial->{width} / 2), int($x->root->rect->width / 2),
+ 'x coordinates match');
+is(int($initial->{y} + $initial->{height} / 2), int($x->root->rect->height / 2),
+ 'y coordinates match');
+
+cmd 'move position center';
+
+my $new = $first->rect;
+is($initial->{x}, $new->{x}, 'x coordinates match');
+is($initial->{y}, $new->{y}, 'y coordinates match');
+
+cmd 'move absolute position center';
+
+$new = $first->rect;
+is($initial->{x}, $new->{x}, 'x coordinates match');
+is($initial->{y}, $new->{y}, 'y coordinates match');
+
+#####################################################################
+# Create a second window and move it into and out of the scratchpad.
+# Because it hasn't been moved or resized, it should be floated in
+# the center of the screen when pulled out of the scratchpad.
+#####################################################################
+
+my $second = open_window;
+
+cmd 'move scratchpad, scratchpad show';
+
+$new = $second->rect;
+my $mid_init = $initial->{x} + int($initial->{width} / 2);
+my $mid_new = $new->{x} + int($new->{width} / 2);
+is($mid_init, $mid_new, 'x midpoint is ws center');
+
+$mid_init = $initial->{y} + int($initial->{height} / 2);
+$mid_new = $new->{y} + int($new->{height} / 2);
+is($mid_init, $mid_new, 'y midpoint is ws center');
+
+#####################################################################
+# Verify that manually floating a tiled window results in proper
+# centering.
+#####################################################################
+
+my $third = open_window;
+
+cmd 'floating enable';
+
+$new = $third->rect;
+is($initial->{x}, $new->{x}, 'x coordinates match');
+is($initial->{y}, $new->{y}, 'y coordinates match');
+
+#####################################################################
+# Create a child window of the previous window, which should result
+# in the new window being centered over the last one.
+#####################################################################
+
+my $fourth = open_window( dont_map => 1, client_leader => $third );
+$fourth->map;
+sync_with_i3;
+
+my $child = $fourth->rect;
+is($new->{x}, $child->{x}, 'x coordinates match');
+is($new->{y}, $child->{y}, 'y coordinates match');
+
+exit_gracefully($pid);
+
+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)
+#
+# Test the 'no_focus' directive.
+# Ticket: #1416
+use i3test i3_autostart => 0;
+
+#####################################################################
+# 1: open a window and check that it takes focus
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config);
+
+my $ws = fresh_workspace;
+my $first = open_window;
+my $focused = get_focused($ws);
+my $second = open_window;
+
+isnt(get_focused($ws), $focused, 'focus has changed');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 2: open a window matched by a no_focus directive and check that
+# it doesn't take focus
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+no_focus [instance=notme]
+EOT
+
+my $pid = launch_with_config($config);
+
+my $ws = fresh_workspace;
+my $first = open_window;
+my $focused = get_focused($ws);
+my $second = open_window(wm_class => 'notme');
+
+is(get_focused($ws), $focused, 'focus has not changed');
+
+exit_gracefully($pid);
+
+#####################################################################
+
+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 'move position center' moves floating cons to the center of
+# the appropriate output.
+# Ticket: #1211
+# Bug still in: 4.9.1-108-g037cb31
+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
+
+workspace left output fake-0
+workspace right output fake-1
+EOT
+
+my $pid = launch_with_config($config);
+
+#####################################################################
+# Verify that 'move position center' on a floating window does not
+# move it to another output.
+#####################################################################
+
+cmd 'workspace left';
+
+# Sync in case focus switched outputs due to the workspace change.
+sync_with_i3;
+
+my $floating = open_floating_window;
+
+# Center the window on the left workspace
+cmd 'move position center';
+sync_with_i3;
+
+is(scalar @{get_ws('left')->{floating_nodes}}, 1, 'one floating node on left ws');
+is(scalar @{get_ws('right')->{floating_nodes}}, 0, 'no floating nodes on right ws');
+
+# Center the window on the right workspace
+cmd 'move workspace right; workspace right; move position center';
+sync_with_i3;
+
+is(scalar @{get_ws('left')->{floating_nodes}}, 0, 'no floating nodes on left ws');
+is(scalar @{get_ws('right')->{floating_nodes}}, 1, 'one floating node on right ws');
+
+exit_gracefully($pid);
+
+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)
+#
+# Tests the behaviour of 'move <direction>' when moving containers across
+# outputs on workspaces that have non-default layouts.
+# Ticket: #1603
+# Bug still in: 4.10.1-40-g0ad097e
+use List::Util qw(first);
+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,1024x768+1024+768,1024x768+0+768
+
+workspace left-top output fake-0
+workspace right-top output fake-1
+workspace right-bottom output fake-2
+workspace left-bottom output fake-3
+
+workspace_layout stacked
+EOT
+
+my $pid = launch_with_config($config);
+
+#####################################################################
+# Create two windows in the upper left workspace and move them
+# clockwise around the workspaces until the end up where they began.
+#####################################################################
+
+cmd 'workspace left-top';
+
+my $first = open_window(wm_class => 'first');
+my $second = open_window(wm_class => 'second');
+
+is_num_children('left-top', 1, 'one child on left-top');
+is_num_children('right-top', 0, 'no children on right-top');
+
+# Move the second window into its own stacked container.
+cmd 'move right';
+is_num_children('left-top', 2, 'two children on left-top');
+
+# Move the second window onto the upper right workspace.
+cmd 'move right';
+is_num_children('left-top', 1, 'one child on left-top');
+is_num_children('right-top', 1, 'one child on right-top');
+
+# Move the first window onto the upper right workspace.
+cmd '[class="first"] move right';
+is_num_children('left-top', 0, 'no children on left-top');
+is_num_children('right-top', 2, 'two children on right-top');
+
+# Move the second window onto the lower right workspace.
+cmd '[class="second"] move down, move down';
+is_num_children('right-top', 1, 'one child on right-top');
+is_num_children('right-bottom', 1, 'two children on right-bottom');
+
+# Move the first window onto the lower right workspace.
+cmd '[class="first"] move down';
+is_num_children('right-top', 0, 'no children on right-top');
+is_num_children('right-bottom', 2, 'two children on right-bottom');
+
+# Move the second windo onto the lower left workspace.
+cmd '[class="second"] move left, move left';
+is_num_children('right-bottom', 1, 'one child on right-bottom');
+is_num_children('left-bottom', 1, 'one on left-bottom');
+
+# Move the first window onto the lower left workspace.
+cmd '[class="first"] move left';
+is_num_children('right-bottom', 0, 'no children on right-bottom');
+is_num_children('left-bottom', 2, 'two children on left-bottom');
+
+# Move the second window onto the upper left workspace.
+cmd '[class="second"] move up, move up';
+is_num_children('left-bottom', 1, 'one child on left-bottom');
+is_num_children('left-top', 1, 'one child on left-top');
+
+# Move the first window onto the upper left workspace.
+cmd '[class="first"] move up';
+is_num_children('left-bottom', 0, 'no children on left-bottom');
+is_num_children('left-top', 2, 'two children on left-top');
+
+exit_gracefully($pid);
+
+done_testing;
+++ /dev/null
-/* $OpenBSD: queue.h,v 1.1 2007/10/26 03:14:08 niallo Exp $ */
-/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */
-
-/*
- * Copyright (c) 1991, 1993
- * The Regents of the University of California. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- * @(#)queue.h 8.5 (Berkeley) 8/20/94
- */
-
-#pragma once
-
-/*
- * This file defines five types of data structures: singly-linked lists,
- * lists, simple queues, tail queues, and circular queues.
- *
- *
- * A singly-linked list is headed by a single forward pointer. The elements
- * are singly linked for minimum space and pointer manipulation overhead at
- * the expense of O(n) removal for arbitrary elements. New elements can be
- * added to the list after an existing element or at the head of the list.
- * Elements being removed from the head of the list should use the explicit
- * macro for this purpose for optimum efficiency. A singly-linked list may
- * only be traversed in the forward direction. Singly-linked lists are ideal
- * for applications with large datasets and few or no removals or for
- * implementing a LIFO queue.
- *
- * A list is headed by a single forward pointer (or an array of forward
- * pointers for a hash table header). The elements are doubly linked
- * so that an arbitrary element can be removed without a need to
- * traverse the list. New elements can be added to the list before
- * or after an existing element or at the head of the list. A list
- * may only be traversed in the forward direction.
- *
- * A simple queue is headed by a pair of pointers, one the head of the
- * list and the other to the tail of the list. The elements are singly
- * linked to save space, so elements can only be removed from the
- * head of the list. New elements can be added to the list before or after
- * an existing element, at the head of the list, or at the end of the
- * list. A simple queue may only be traversed in the forward direction.
- *
- * A tail queue is headed by a pair of pointers, one to the head of the
- * list and the other to the tail of the list. The elements are doubly
- * linked so that an arbitrary element can be removed without a need to
- * traverse the list. New elements can be added to the list before or
- * after an existing element, at the head of the list, or at the end of
- * the list. A tail queue may be traversed in either direction.
- *
- * A circle queue is headed by a pair of pointers, one to the head of the
- * list and the other to the tail of the list. The elements are doubly
- * linked so that an arbitrary element can be removed without a need to
- * traverse the list. New elements can be added to the list before or after
- * an existing element, at the head of the list, or at the end of the list.
- * A circle queue may be traversed in either direction, but has a more
- * complex end of list detection.
- *
- * For details on the use of these macros, see the queue(3) manual page.
- */
-
-#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC))
-#define _Q_INVALIDATE(a) (a) = ((void *)-1)
-#else
-#define _Q_INVALIDATE(a)
-#endif
-
-/*
- * Singly-linked List definitions.
- */
-#define SLIST_HEAD(name, type) \
- struct name { \
- struct type *slh_first; /* first element */ \
- }
-
-#define SLIST_HEAD_INITIALIZER(head) \
- { NULL }
-
-#define SLIST_ENTRY(type) \
- struct { \
- struct type *sle_next; /* next element */ \
- }
-
-/*
- * Singly-linked List access methods.
- */
-#define SLIST_FIRST(head) ((head)->slh_first)
-#define SLIST_END(head) NULL
-#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head))
-#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
-
-#define SLIST_FOREACH(var, head, field) \
- for ((var) = SLIST_FIRST(head); \
- (var) != SLIST_END(head); \
- (var) = SLIST_NEXT(var, field))
-
-#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
- for ((varp) = &SLIST_FIRST((head)); \
- ((var) = *(varp)) != SLIST_END(head); \
- (varp) = &SLIST_NEXT((var), field))
-
-/*
- * Singly-linked List functions.
- */
-#define SLIST_INIT(head) \
- { \
- SLIST_FIRST(head) = SLIST_END(head); \
- }
-
-#define SLIST_INSERT_AFTER(slistelm, elm, field) \
- do { \
- (elm)->field.sle_next = (slistelm)->field.sle_next; \
- (slistelm)->field.sle_next = (elm); \
- } while (0)
-
-#define SLIST_INSERT_HEAD(head, elm, field) \
- do { \
- (elm)->field.sle_next = (head)->slh_first; \
- (head)->slh_first = (elm); \
- } while (0)
-
-#define SLIST_REMOVE_NEXT(head, elm, field) \
- do { \
- (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \
- } while (0)
-
-#define SLIST_REMOVE_HEAD(head, field) \
- do { \
- (head)->slh_first = (head)->slh_first->field.sle_next; \
- } while (0)
-
-#define SLIST_REMOVE(head, elm, type, field) \
- do { \
- if ((head)->slh_first == (elm)) { \
- SLIST_REMOVE_HEAD((head), field); \
- } else { \
- struct type *curelm = (head)->slh_first; \
- \
- while (curelm->field.sle_next != (elm)) \
- curelm = curelm->field.sle_next; \
- curelm->field.sle_next = curelm->field.sle_next->field.sle_next; \
- _Q_INVALIDATE((elm)->field.sle_next); \
- } \
- } while (0)
-
-/*
- * List definitions.
- */
-#define LIST_HEAD(name, type) \
- struct name { \
- struct type *lh_first; /* first element */ \
- }
-
-#define LIST_HEAD_INITIALIZER(head) \
- { NULL }
-
-#define LIST_ENTRY(type) \
- struct { \
- struct type *le_next; /* next element */ \
- struct type **le_prev; /* address of previous next element */ \
- }
-
-/*
- * List access methods
- */
-#define LIST_FIRST(head) ((head)->lh_first)
-#define LIST_END(head) NULL
-#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head))
-#define LIST_NEXT(elm, field) ((elm)->field.le_next)
-
-#define LIST_FOREACH(var, head, field) \
- for ((var) = LIST_FIRST(head); \
- (var) != LIST_END(head); \
- (var) = LIST_NEXT(var, field))
-
-/*
- * List functions.
- */
-#define LIST_INIT(head) \
- do { \
- LIST_FIRST(head) = LIST_END(head); \
- } while (0)
-
-#define LIST_INSERT_AFTER(listelm, elm, field) \
- do { \
- if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \
- (listelm)->field.le_next->field.le_prev = &(elm)->field.le_next; \
- (listelm)->field.le_next = (elm); \
- (elm)->field.le_prev = &(listelm)->field.le_next; \
- } while (0)
-
-#define LIST_INSERT_BEFORE(listelm, elm, field) \
- do { \
- (elm)->field.le_prev = (listelm)->field.le_prev; \
- (elm)->field.le_next = (listelm); \
- *(listelm)->field.le_prev = (elm); \
- (listelm)->field.le_prev = &(elm)->field.le_next; \
- } while (0)
-
-#define LIST_INSERT_HEAD(head, elm, field) \
- do { \
- if (((elm)->field.le_next = (head)->lh_first) != NULL) \
- (head)->lh_first->field.le_prev = &(elm)->field.le_next; \
- (head)->lh_first = (elm); \
- (elm)->field.le_prev = &(head)->lh_first; \
- } while (0)
-
-#define LIST_REMOVE(elm, field) \
- do { \
- if ((elm)->field.le_next != NULL) \
- (elm)->field.le_next->field.le_prev = (elm)->field.le_prev; \
- *(elm)->field.le_prev = (elm)->field.le_next; \
- _Q_INVALIDATE((elm)->field.le_prev); \
- _Q_INVALIDATE((elm)->field.le_next); \
- } while (0)
-
-#define LIST_REPLACE(elm, elm2, field) \
- do { \
- if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \
- (elm2)->field.le_next->field.le_prev = &(elm2)->field.le_next; \
- (elm2)->field.le_prev = (elm)->field.le_prev; \
- *(elm2)->field.le_prev = (elm2); \
- _Q_INVALIDATE((elm)->field.le_prev); \
- _Q_INVALIDATE((elm)->field.le_next); \
- } while (0)
-
-/*
- * Simple queue definitions.
- */
-#define SIMPLEQ_HEAD(name, type) \
- struct name { \
- struct type *sqh_first; /* first element */ \
- struct type **sqh_last; /* addr of last next element */ \
- }
-
-#define SIMPLEQ_HEAD_INITIALIZER(head) \
- { NULL, &(head).sqh_first }
-
-#define SIMPLEQ_ENTRY(type) \
- struct { \
- struct type *sqe_next; /* next element */ \
- }
-
-/*
- * Simple queue access methods.
- */
-#define SIMPLEQ_FIRST(head) ((head)->sqh_first)
-#define SIMPLEQ_END(head) NULL
-#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head))
-#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next)
-
-#define SIMPLEQ_FOREACH(var, head, field) \
- for ((var) = SIMPLEQ_FIRST(head); \
- (var) != SIMPLEQ_END(head); \
- (var) = SIMPLEQ_NEXT(var, field))
-
-/*
- * Simple queue functions.
- */
-#define SIMPLEQ_INIT(head) \
- do { \
- (head)->sqh_first = NULL; \
- (head)->sqh_last = &(head)->sqh_first; \
- } while (0)
-
-#define SIMPLEQ_INSERT_HEAD(head, elm, field) \
- do { \
- if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \
- (head)->sqh_last = &(elm)->field.sqe_next; \
- (head)->sqh_first = (elm); \
- } while (0)
-
-#define SIMPLEQ_INSERT_TAIL(head, elm, field) \
- do { \
- (elm)->field.sqe_next = NULL; \
- *(head)->sqh_last = (elm); \
- (head)->sqh_last = &(elm)->field.sqe_next; \
- } while (0)
-
-#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) \
- do { \
- if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL) \
- (head)->sqh_last = &(elm)->field.sqe_next; \
- (listelm)->field.sqe_next = (elm); \
- } while (0)
-
-#define SIMPLEQ_REMOVE_HEAD(head, field) \
- do { \
- if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
- (head)->sqh_last = &(head)->sqh_first; \
- } while (0)
-
-/*
- * Tail queue definitions.
- */
-#define TAILQ_HEAD(name, type) \
- struct name { \
- struct type *tqh_first; /* first element */ \
- struct type **tqh_last; /* addr of last next element */ \
- }
-
-#define TAILQ_HEAD_INITIALIZER(head) \
- { NULL, &(head).tqh_first }
-
-#define TAILQ_ENTRY(type) \
- struct { \
- struct type *tqe_next; /* next element */ \
- struct type **tqe_prev; /* address of previous next element */ \
- }
-
-/*
- * tail queue access methods
- */
-#define TAILQ_FIRST(head) ((head)->tqh_first)
-#define TAILQ_END(head) NULL
-#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
-#define TAILQ_LAST(head, headname) \
- (*(((struct headname *)((head)->tqh_last))->tqh_last))
-/* XXX */
-#define TAILQ_PREV(elm, headname, field) \
- (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
-#define TAILQ_EMPTY(head) \
- (TAILQ_FIRST(head) == TAILQ_END(head))
-
-#define TAILQ_FOREACH(var, head, field) \
- for ((var) = TAILQ_FIRST(head); \
- (var) != TAILQ_END(head); \
- (var) = TAILQ_NEXT(var, field))
-
-#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
- for ((var) = TAILQ_LAST(head, headname); \
- (var) != TAILQ_END(head); \
- (var) = TAILQ_PREV(var, headname, field))
-
-/*
- * Tail queue functions.
- */
-#define TAILQ_INIT(head) \
- do { \
- (head)->tqh_first = NULL; \
- (head)->tqh_last = &(head)->tqh_first; \
- } while (0)
-
-#define TAILQ_INSERT_HEAD(head, elm, field) \
- do { \
- if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \
- (head)->tqh_first->field.tqe_prev = &(elm)->field.tqe_next; \
- else \
- (head)->tqh_last = &(elm)->field.tqe_next; \
- (head)->tqh_first = (elm); \
- (elm)->field.tqe_prev = &(head)->tqh_first; \
- } while (0)
-
-#define TAILQ_INSERT_TAIL(head, elm, field) \
- do { \
- (elm)->field.tqe_next = NULL; \
- (elm)->field.tqe_prev = (head)->tqh_last; \
- *(head)->tqh_last = (elm); \
- (head)->tqh_last = &(elm)->field.tqe_next; \
- } while (0)
-
-#define TAILQ_INSERT_AFTER(head, listelm, elm, field) \
- do { \
- if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL) \
- (elm)->field.tqe_next->field.tqe_prev = &(elm)->field.tqe_next; \
- else \
- (head)->tqh_last = &(elm)->field.tqe_next; \
- (listelm)->field.tqe_next = (elm); \
- (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \
- } while (0)
-
-#define TAILQ_INSERT_BEFORE(listelm, elm, field) \
- do { \
- (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
- (elm)->field.tqe_next = (listelm); \
- *(listelm)->field.tqe_prev = (elm); \
- (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
- } while (0)
-
-#define TAILQ_REMOVE(head, elm, field) \
- do { \
- if (((elm)->field.tqe_next) != NULL) \
- (elm)->field.tqe_next->field.tqe_prev = (elm)->field.tqe_prev; \
- else \
- (head)->tqh_last = (elm)->field.tqe_prev; \
- *(elm)->field.tqe_prev = (elm)->field.tqe_next; \
- _Q_INVALIDATE((elm)->field.tqe_prev); \
- _Q_INVALIDATE((elm)->field.tqe_next); \
- } while (0)
-
-#define TAILQ_REPLACE(head, elm, elm2, field) \
- do { \
- if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \
- (elm2)->field.tqe_next->field.tqe_prev = &(elm2)->field.tqe_next; \
- else \
- (head)->tqh_last = &(elm2)->field.tqe_next; \
- (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \
- *(elm2)->field.tqe_prev = (elm2); \
- _Q_INVALIDATE((elm)->field.tqe_prev); \
- _Q_INVALIDATE((elm)->field.tqe_next); \
- } while (0)
-
-/*
- * Circular queue definitions.
- */
-#define CIRCLEQ_HEAD(name, type) \
- struct name { \
- struct type *cqh_first; /* first element */ \
- struct type *cqh_last; /* last element */ \
- }
-
-#define CIRCLEQ_HEAD_INITIALIZER(head) \
- { CIRCLEQ_END(&head), CIRCLEQ_END(&head) }
-
-#define CIRCLEQ_ENTRY(type) \
- struct { \
- struct type *cqe_next; /* next element */ \
- struct type *cqe_prev; /* previous element */ \
- }
-
-/*
- * Circular queue access methods
- */
-#define CIRCLEQ_FIRST(head) ((head)->cqh_first)
-#define CIRCLEQ_LAST(head) ((head)->cqh_last)
-#define CIRCLEQ_END(head) ((void *)(head))
-#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next)
-#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev)
-#define CIRCLEQ_EMPTY(head) \
- (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head))
-
-#define CIRCLEQ_FOREACH(var, head, field) \
- for ((var) = CIRCLEQ_FIRST(head); \
- (var) != CIRCLEQ_END(head); \
- (var) = CIRCLEQ_NEXT(var, field))
-
-#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \
- for ((var) = CIRCLEQ_LAST(head); \
- (var) != CIRCLEQ_END(head); \
- (var) = CIRCLEQ_PREV(var, field))
-
-/*
- * Circular queue functions.
- */
-#define CIRCLEQ_INIT(head) \
- do { \
- (head)->cqh_first = CIRCLEQ_END(head); \
- (head)->cqh_last = CIRCLEQ_END(head); \
- } while (0)
-
-#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) \
- do { \
- (elm)->field.cqe_next = (listelm)->field.cqe_next; \
- (elm)->field.cqe_prev = (listelm); \
- if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \
- (head)->cqh_last = (elm); \
- else \
- (listelm)->field.cqe_next->field.cqe_prev = (elm); \
- (listelm)->field.cqe_next = (elm); \
- } while (0)
-
-#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) \
- do { \
- (elm)->field.cqe_next = (listelm); \
- (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \
- if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \
- (head)->cqh_first = (elm); \
- else \
- (listelm)->field.cqe_prev->field.cqe_next = (elm); \
- (listelm)->field.cqe_prev = (elm); \
- } while (0)
-
-#define CIRCLEQ_INSERT_HEAD(head, elm, field) \
- do { \
- (elm)->field.cqe_next = (head)->cqh_first; \
- (elm)->field.cqe_prev = CIRCLEQ_END(head); \
- if ((head)->cqh_last == CIRCLEQ_END(head)) \
- (head)->cqh_last = (elm); \
- else \
- (head)->cqh_first->field.cqe_prev = (elm); \
- (head)->cqh_first = (elm); \
- } while (0)
-
-#define CIRCLEQ_INSERT_TAIL(head, elm, field) \
- do { \
- (elm)->field.cqe_next = CIRCLEQ_END(head); \
- (elm)->field.cqe_prev = (head)->cqh_last; \
- if ((head)->cqh_first == CIRCLEQ_END(head)) \
- (head)->cqh_first = (elm); \
- else \
- (head)->cqh_last->field.cqe_next = (elm); \
- (head)->cqh_last = (elm); \
- } while (0)
-
-#define CIRCLEQ_REMOVE(head, elm, field) \
- do { \
- if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \
- (head)->cqh_last = (elm)->field.cqe_prev; \
- else \
- (elm)->field.cqe_next->field.cqe_prev = (elm)->field.cqe_prev; \
- if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \
- (head)->cqh_first = (elm)->field.cqe_next; \
- else \
- (elm)->field.cqe_prev->field.cqe_next = (elm)->field.cqe_next; \
- _Q_INVALIDATE((elm)->field.cqe_prev); \
- _Q_INVALIDATE((elm)->field.cqe_next); \
- } while (0)
-
-#define CIRCLEQ_REPLACE(head, elm, elm2, field) \
- do { \
- if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == CIRCLEQ_END(head)) \
- (head)->cqh_last = (elm2); \
- else \
- (elm2)->field.cqe_next->field.cqe_prev = (elm2); \
- if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == CIRCLEQ_END(head)) \
- (head)->cqh_first = (elm2); \
- else \
- (elm2)->field.cqe_prev->field.cqe_next = (elm2); \
- _Q_INVALIDATE((elm)->field.cqe_prev); \
- _Q_INVALIDATE((elm)->field.cqe_next); \
- } while (0)
+++ /dev/null
-/*
- * vim:ts=4:sw=4:expandtab
- */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <stdbool.h>
-#include <stdint.h>
-
-#include "queue.h"
-
-struct obj {
- int abc;
- TAILQ_ENTRY(obj) entry;
-};
-
-TAILQ_HEAD(objhead, obj) head;
-
-void dump() {
- struct obj *e;
- printf("dump:\n");
- e = TAILQ_FIRST(&head);
- printf("first: %d\n", e->abc);
- e = TAILQ_LAST(&head, objhead);
- printf("last: %d\n", e->abc);
- TAILQ_FOREACH(e, &head, entry) {
- printf(" %d\n", e->abc);
- }
- printf("again, but reverse:\n");
- TAILQ_FOREACH_REVERSE(e, &head, objhead, entry) {
- printf(" %d\n", e->abc);
- }
- printf("done\n\n");
-}
-
-#define TAILQ_SWAP(first, second, head, field) \
- do { \
- *((first)->field.tqe_prev) = (second); \
- (second)->field.tqe_prev = (first)->field.tqe_prev; \
- (first)->field.tqe_prev = &((second)->field.tqe_next); \
- (first)->field.tqe_next = (second)->field.tqe_next; \
- if ((second)->field.tqe_next) \
- (second)->field.tqe_next->field.tqe_prev = &((first)->field.tqe_next); \
- (second)->field.tqe_next = first; \
- if ((head)->tqh_last == &((second)->field.tqe_next)) \
- (head)->tqh_last = &((first)->field.tqe_next); \
- } while (0)
-
-void _TAILQ_SWAP(struct obj *first, struct obj *second, struct objhead *head) {
- struct obj **tqe_prev = first->entry.tqe_prev;
- *tqe_prev = second;
-
- second->entry.tqe_prev = first->entry.tqe_prev;
-
- first->entry.tqe_prev = &(second->entry.tqe_next);
-
- first->entry.tqe_next = second->entry.tqe_next;
-
- if (second->entry.tqe_next) {
- struct obj *tqe_next = second->entry.tqe_next;
- tqe_next->entry.tqe_prev = &(first->entry.tqe_next);
- }
-
- second->entry.tqe_next = first;
-
- if (head->tqh_last == &(second->entry.tqe_next))
- head->tqh_last = &(first->entry.tqe_next);
-}
-
-int main() {
- printf("hello\n");
-
- TAILQ_INIT(&head);
-
- struct obj first;
- first.abc = 123;
-
- struct obj second;
- second.abc = 456;
-
- struct obj third;
- third.abc = 789;
-
- struct obj fourth;
- fourth.abc = 999;
-
- struct obj fifth;
- fifth.abc = 5555;
-
- /*
- * ************************************************
- */
- printf("swapping first two elements:\n");
-
- TAILQ_INSERT_TAIL(&head, &first, entry);
- TAILQ_INSERT_TAIL(&head, &second, entry);
- TAILQ_INSERT_TAIL(&head, &third, entry);
-
- dump();
-
- TAILQ_SWAP(&first, &second, &head, entry);
-
- dump();
-
- /*
- * ************************************************
- */
- printf("swapping last two elements:\n");
-
- TAILQ_INIT(&head);
-
- TAILQ_INSERT_TAIL(&head, &first, entry);
- TAILQ_INSERT_TAIL(&head, &second, entry);
- TAILQ_INSERT_TAIL(&head, &third, entry);
-
- dump();
-
- TAILQ_SWAP(&second, &third, &head, entry);
-
- dump();
-
- /*
- * ************************************************
- */
- printf("longer list:\n");
-
- TAILQ_INIT(&head);
-
- TAILQ_INSERT_TAIL(&head, &first, entry);
- TAILQ_INSERT_TAIL(&head, &second, entry);
- TAILQ_INSERT_TAIL(&head, &third, entry);
- TAILQ_INSERT_TAIL(&head, &fourth, entry);
-
- dump();
-
- TAILQ_SWAP(&first, &second, &head, entry);
-
- dump();
-
- /*
- * ************************************************
- */
- printf("longer list 2:\n");
-
- TAILQ_INIT(&head);
-
- TAILQ_INSERT_TAIL(&head, &first, entry);
- TAILQ_INSERT_TAIL(&head, &second, entry);
- TAILQ_INSERT_TAIL(&head, &third, entry);
- TAILQ_INSERT_TAIL(&head, &fourth, entry);
-
- dump();
-
- TAILQ_SWAP(&second, &third, &head, entry);
-
- dump();
-
- /*
- * ************************************************
- */
- printf("longer list, swap, then insert:\n");
-
- TAILQ_INIT(&head);
-
- TAILQ_INSERT_TAIL(&head, &first, entry);
- TAILQ_INSERT_TAIL(&head, &second, entry);
- TAILQ_INSERT_TAIL(&head, &third, entry);
- TAILQ_INSERT_TAIL(&head, &fourth, entry);
-
- dump();
-
- TAILQ_SWAP(&second, &third, &head, entry);
-
- dump();
-
- TAILQ_INSERT_AFTER(&head, &third, &fifth, entry);
-
- dump();
-
- /*
- * ************************************************
- */
- printf("longer list, swap, then append:\n");
-
- TAILQ_INIT(&head);
-
- TAILQ_INSERT_TAIL(&head, &first, entry);
- TAILQ_INSERT_TAIL(&head, &second, entry);
- TAILQ_INSERT_TAIL(&head, &third, entry);
- TAILQ_INSERT_TAIL(&head, &fourth, entry);
-
- dump();
-
- TAILQ_SWAP(&second, &third, &head, entry);
-
- dump();
-
- TAILQ_INSERT_TAIL(&head, &fifth, entry);
-
- dump();
-
- /*
- * ************************************************
- */
- printf("longer list, swap, then remove:\n");
-
- TAILQ_INIT(&head);
-
- TAILQ_INSERT_TAIL(&head, &first, entry);
- TAILQ_INSERT_TAIL(&head, &second, entry);
- TAILQ_INSERT_TAIL(&head, &third, entry);
- TAILQ_INSERT_TAIL(&head, &fourth, entry);
-
- dump();
-
- TAILQ_SWAP(&second, &third, &head, entry);
-
- dump();
-
- TAILQ_REMOVE(&head, &second, entry);
-
- dump();
-}
+++ /dev/null
-#include <stdio.h>
-#include "table.h"
-
-void print_table() {
- int r, c;
- printf("printing table...\n");
- for (c = 0; c < table_dims.x; c++)
- for (r = 0; r < table_dims.y; r++)
- printf("table[%d][%d] = %p\n", c, r, table[c][r]);
- printf("done\n");
-}
-
-int main() {
- printf("table.c tests\n");
- printf("table_dimensions = %d, %d\n", table_dims.x, table_dims.y);
- init_table();
- printf("table_dimensions = %d, %d\n", table_dims.x, table_dims.y);
- print_table();
-
- printf("expand_table_cols()\n");
- expand_table_cols();
- printf("table_dimensions = %d, %d\n", table_dims.x, table_dims.y);
- print_table();
-
- printf("expand_table_rows()\n");
- expand_table_rows();
- printf("table_dimensions = %d, %d\n", table_dims.x, table_dims.y);
- print_table();
-}