From dc702a023a30dd93625f688d90d482226cca7a42 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 6 Apr 2010 20:52:07 +0200 Subject: [PATCH 001/867] Bugfix: correctly initialize workspaces if RandR is not available (Thanks stesie) --- src/randr.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/randr.c b/src/randr.c index e61fd9b2..7ce856d2 100644 --- a/src/randr.c +++ b/src/randr.c @@ -364,6 +364,22 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, new->changed = true; } +static void init_workspaces() { + Output *output; + Workspace *ws; + + /* Just go through each active output and associate one workspace */ + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active || output->current_workspace != NULL) + continue; + ws = get_first_workspace_for_output(output); + initialize_output(global_conn, output, ws); + } + + /* render_layout flushes */ + render_layout(global_conn); +} + /* * (Re-)queries the outputs via RandR and stores them in the list of outputs. * @@ -387,6 +403,7 @@ void randr_query_outputs(xcb_connection_t *conn) { rcookie = xcb_randr_get_screen_resources_current(conn, root); if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) { disable_randr(conn); + init_workspaces(); return; } cts = res->config_timestamp; @@ -494,16 +511,7 @@ void randr_query_outputs(xcb_connection_t *conn) { ewmh_update_workarea(); - /* Just go through each active output and associate one workspace */ - TAILQ_FOREACH(output, &outputs, outputs) { - if (!output->active || output->current_workspace != NULL) - continue; - ws = get_first_workspace_for_output(output); - initialize_output(conn, output, ws); - } - - /* render_layout flushes */ - render_layout(conn); + init_workspaces(); } /* From e4632c3a7fad783771c681cd0ae58c729aca8410 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Sun, 11 Apr 2010 21:00:57 +0200 Subject: [PATCH 002/867] add socket path parameter to i3-wsbar script fixes http://i3.zekjur.net/bugs/ticket/210 --- i3-wsbar | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/i3-wsbar b/i3-wsbar index b1a50c40..2f62edb4 100755 --- a/i3-wsbar +++ b/i3-wsbar @@ -12,7 +12,7 @@ use AnyEvent; use v5.10; my $stdin; -my $i3 = i3; +my $socket_path = undef; my ($workspaces, $outputs) = ([], {}); my $last_line = ""; @@ -23,6 +23,7 @@ my $show_all = 0; my $result = GetOptions( 'command=s' => \$command, + 'socket=s' => \$socket_path, 'input-on=s' => \$input_on, 'output-on=s' => \$output_on, 'show-all' => \$show_all, @@ -35,6 +36,8 @@ if ($command eq '') { exit 1; } +my $i3 = i3($socket_path); + my @input_on = split(/,/, $input_on); my @output_on = split(/,/, $output_on); From c145f7e5297ef06aaf84689762a736d5bc8cbb83 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 15:25:51 +0100 Subject: [PATCH 003/867] first step of the big refactoring ("tree" branch). From here on, we can track changes. It made no sense to put the development up to this point into git. --- Makefile | 11 +- README.tree | 49 +++++ common.mk | 16 -- i3-msg/main.c | 5 +- include/all.h | 52 +++++ include/commands.h | 4 +- include/con.h | 20 ++ include/config.h | 3 + include/data.h | 384 ++++++++------------------------- include/floating.h | 21 +- include/handlers.h | 2 + include/i3.h | 2 +- include/i3/ipc.h | 15 +- include/ipc.h | 8 + include/load_layout.h | 6 + include/manage.h | 10 +- include/randr.h | 6 +- include/render.h | 10 + include/table.h | 72 ------- include/tree.h | 28 +++ include/util.h | 11 + include/workspace.h | 10 +- include/x.h | 15 ++ include/xcb.h | 4 +- include/xinerama.h | 2 +- src/cfgparse.l | 10 +- src/cfgparse.y | 25 +-- src/click.c | 69 +++--- src/con.c | 248 +++++++++++++++++++++ src/config.c | 55 +++-- src/floating.c | 121 ++++++++--- src/handlers.c | 85 +++----- src/ipc.c | 108 ++++++++-- src/load_layout.c | 160 ++++++++++++++ src/log.c | 2 +- src/manage.c | 136 ++++++++---- src/nc.c | 414 ++++++++++++++++++++++++++++++++++++ src/randr.c | 47 ++-- src/render.c | 137 ++++++++++++ src/table.c | 406 ----------------------------------- src/tree.c | 335 +++++++++++++++++++++++++++++ src/util.c | 86 ++++++-- src/workspace.c | 92 ++++---- src/x.c | 299 ++++++++++++++++++++++++++ src/xcb.c | 16 +- src/xinerama.c | 150 +++++++------ testcases/Makefile | 2 +- testcases/t/16-nestedcons.t | 84 ++++++++ 48 files changed, 2583 insertions(+), 1270 deletions(-) create mode 100644 README.tree create mode 100644 include/all.h create mode 100644 include/con.h create mode 100644 include/load_layout.h create mode 100644 include/render.h delete mode 100644 include/table.h create mode 100644 include/tree.h create mode 100644 include/x.h create mode 100644 src/con.c create mode 100644 src/load_layout.c create mode 100644 src/nc.c create mode 100644 src/render.c delete mode 100644 src/table.c create mode 100644 src/tree.c create mode 100644 src/x.c create mode 100644 testcases/t/16-nestedcons.t diff --git a/Makefile b/Makefile index c73723ad..28a78064 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c -FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c)) +FILES:=src/ipc.c src/nc.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) @@ -13,7 +13,7 @@ HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) # updated if necessary, but we also want to save rebuilds of the object # files, so we cannot let the object files depend on loglevels.h. ifeq ($(MAKECMDGOALS),loglevels.h) -UNUSED:=$(warning Generating loglevels.h) +#UNUSED:=$(warning Generating loglevels.h) else UNUSED:=$(shell $(MAKE) loglevels.h) endif @@ -25,12 +25,7 @@ src/%.o: src/%.c ${HEADERS} all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES} echo "LINK i3" - $(CC) -o i3 ${FILES} src/cfgparse.y.o src/cfgparse.yy.o $(LDFLAGS) - echo "" - echo "SUBDIR i3-msg" - $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg - echo "SUBDIR i3-input" - $(MAKE) TOPDIR=$(TOPDIR) -C i3-input + $(CC) -o i3 $^ $(LDFLAGS) loglevels.h: echo "LOGLEVELS" diff --git a/README.tree b/README.tree new file mode 100644 index 00000000..446b8481 --- /dev/null +++ b/README.tree @@ -0,0 +1,49 @@ +README file for the "tree" branch of i3 +======================================= + +This is a *massive* refactoring of i3. It was driven by thinking about whether +a different data structure could make things easier (for users and developers). +The old data structure of a table provided relatively flexible layouts but was +*very* hard to implement. + +The new data structure is a tree. You can have horizontally and vertically split +containers. Each container can contain either nothing yet (waiting for a window +or for the user to put more containers in it), one or more containers or exactly +one window. RandR Outputs and workspaces are not treated specially, but they +are just containers inside the tree. + +This structure allows for easy serialization, meaning multiple things: +- we can store and reload the layout for inplace restarts (this is already working) +- we can store parts of the layout and load them at any position in our tree + (partly working) + - we can load a layout specifying the physical positions of RandR outputs + for pathologic screen setups + - we can load a default layout for each workspace to specify the position + of dock clients +- we can use test-driven development to a much higher degree because we have + access to the whole tree + +Ripping out the core data structures of i3 and replacing them of course has +some side-effects, which I will describe here (the list may not be complete, +new side-effects may not be known yet): +- Empty containers are allowed. They can swallow windows based on certain + criteria. We can implement session-saving this way. +- Assignments (put windows on certain workspaces, put workspaces on certain + outputs) are just special cases of the point above. +- Window decorations are now always rendered on the parent window. This means + we don’t have to carry around ugly Stack_Windows any more. +- Operations always (?) operate on containers, so you can make a container + (containing multiple windows) fullscreen or floating (for example) and no + longer just single windows. +- All X11 requests are now pushed to X11 in a separate step (rendering is one + step, updating X11 another). This makes talking to X11 a lot less error-prone + and allows for simpler code. + +====================== +SOME WORDS OF WARNING: +====================== + +The current state of the branch is not nearly the quality you know of i3. It +is in flux, changes and crashes are to be expected. Many features do not work +yet. It is only suitable if you want to help developing or have a look at what +is coming. Do *NOT* use it for production! You have been warned. diff --git a/common.mk b/common.mk index 0334ac61..f55264a1 100644 --- a/common.mk +++ b/common.mk @@ -20,22 +20,6 @@ CFLAGS += -Iinclude CFLAGS += -I/usr/local/include CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" -# Check if pkg-config is installed, because without pkg-config, the following -# check for the version of libxcb cannot be done. -ifeq ($(shell which pkg-config 2>/dev/null 1>/dev/null || echo 1),1) -$(error "pkg-config was not found") -endif - -ifeq ($(shell pkg-config --exists xcb-keysyms || echo 1),1) -$(error "pkg-config could not find xcb-keysyms.pc") -endif - -ifeq ($(shell pkg-config --exact-version=0.3.3 xcb-keysyms && echo 1),1) -# xcb-keysyms fixed API from 0.3.3 to 0.3.4, so for some months, we will -# have this here. Distributions should upgrade their libxcb in the meantime. -CFLAGS += -DOLD_XCB_KEYSYMS_API -endif - LDFLAGS += -lm LDFLAGS += -lxcb-event LDFLAGS += -lxcb-property diff --git a/i3-msg/main.c b/i3-msg/main.c index a1bdd72e..0dc1165a 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -127,12 +127,11 @@ int main(int argc, char *argv[]) { while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { if (o == 's') { socket_path = strdup(optarg); - break; } else if (o == 't') { if (strcasecmp(optarg, "command") == 0) message_type = I3_IPC_MESSAGE_TYPE_COMMAND; - else if (strcasecmp(optarg, "get_workspaces") == 0) - message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES; + else if (strcasecmp(optarg, "tree") == 0) + message_type = I3_IPC_MESSAGE_TYPE_GET_TREE; else { printf("Unknown message type\n"); printf("Known types: command, get_workspaces\n"); diff --git a/include/all.h b/include/all.h new file mode 100644 index 00000000..a86bd5d2 --- /dev/null +++ b/include/all.h @@ -0,0 +1,52 @@ +/* + * This header file includes all relevant files of i3 and the most often used + * system header files. This reduces boilerplate (the amount of code duplicated + * at the beginning of each source file) and is not significantly slower at + * compile-time. + * + */ +#ifndef _ALL_H +#define _ALL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "util.h" +#include "commands.h" +#include "ipc.h" +#include "tree.h" +#include "log.h" +#include "xcb.h" +#include "manage.h" +#include "workspace.h" +#include "i3.h" +#include "x.h" +#include "click.h" +#include "floating.h" +#include "config.h" +#include "handlers.h" +#include "randr.h" +#include "xinerama.h" +#include "con.h" +#include "load_layout.h" +#include "render.h" + +#endif diff --git a/include/commands.h b/include/commands.h index fbad973b..c6b45d1f 100644 --- a/include/commands.h +++ b/include/commands.h @@ -13,10 +13,12 @@ #include +#if 0 bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction); +#endif /** Parses a command, see file CMDMODE for more information */ -void parse_command(xcb_connection_t *conn, const char *command); +void parse_command(const char *command); #endif diff --git a/include/con.h b/include/con.h new file mode 100644 index 00000000..9b14d897 --- /dev/null +++ b/include/con.h @@ -0,0 +1,20 @@ +#ifndef _CON_H +#define _CON_H + +Con *con_new(Con *parent); +bool con_is_leaf(Con *con); +bool con_accepts_window(Con *con); +Con *con_get_output(Con *con); +Con *con_get_workspace(Con *con); +Con *con_get_fullscreen_con(Con *con); +bool con_is_floating(Con *con); +Con *con_by_window_id(xcb_window_t window); +Con *con_by_frame_id(xcb_window_t frame); +Con *con_for_window(i3Window *window, Match **store_match); +void con_attach(Con *con, Con *parent); +void con_detach(Con *con); + +enum { WINDOW_ADD = 0, WINDOW_REMOVE = 1 }; +void con_fix_percent(Con *con, int action); + +#endif diff --git a/include/config.h b/include/config.h index 0b671b7a..36011f45 100644 --- a/include/config.h +++ b/include/config.h @@ -124,6 +124,9 @@ struct Config { } bar; }; +char *glob_path(const char *path); +bool path_exists(const char *path); + /** * Reads the configuration from ~/.i3/config or /etc/i3/config if not found. * diff --git a/include/data.h b/include/data.h index 3618dfb8..c034d115 100644 --- a/include/data.h +++ b/include/data.h @@ -26,42 +26,25 @@ * * Let’s start from the biggest to the smallest: * - * - An Output is a physical output on your graphics driver. Outputs which - * are currently in use have (output->active == true). Each output has a - * position and a mode. An output usually corresponds to one connected - * screen (except if you are running multiple screens in clone mode). - * - * - Each Output contains Workspaces. The concept is known from various - * other window managers. Basically, a workspace is a specific set of - * windows, usually grouped thematically (irc, www, work, …). You can switch - * between these. - * - * - Each Workspace has a table, which is our layout abstraction. You manage - * your windows by moving them around in your table. It grows as necessary. - * - * - Each cell of the table has a container, which can be in default or - * stacking mode. In default mode, each client is given equally much space - * in the container. In stacking mode, only one client is shown at a time, - * but all the titlebars are rendered at the top. - * - * - Inside the container are clients, which is X11-speak for a window. + * TODO * */ /* Forward definitions */ -typedef struct Cell Cell; typedef struct Font i3Font; -typedef struct Container Container; -typedef struct Client Client; typedef struct Binding Binding; -typedef struct Workspace Workspace; typedef struct Rect Rect; typedef struct xoutput Output; +typedef struct Con Con; +typedef struct Match Match; +typedef struct Window i3Window; + /****************************************************************************** * Helper types *****************************************************************************/ typedef enum { D_LEFT, D_RIGHT, D_UP, D_DOWN } direction_t; +typedef enum { HORIZ, VERT, NO_ORIENTATION } orientation_t; enum { BIND_NONE = 0, @@ -94,15 +77,6 @@ struct Rect { uint32_t height; } __attribute__((packed)); -/** - * Defines a position in the table - * - */ -struct Cell { - int row; - int column; -}; - /** * Used for the cache of colorpixels. * @@ -129,22 +103,6 @@ struct Cached_Pixmap { xcb_drawable_t referred_drawable; }; -/** - * Contains data for the windows needed to draw the titlebars on in stacking - * mode - * - */ -struct Stack_Window { - xcb_window_t window; - struct Cached_Pixmap pixmap; - Rect rect; - - /** Backpointer to the container this stack window is in */ - Container *container; - - SLIST_ENTRY(Stack_Window) stack_windows; -}; - struct Ignore_Event { int sequence; time_t added; @@ -167,86 +125,6 @@ struct keyvalue_element { * Major types *****************************************************************************/ -/** - * The concept of Workspaces is known from various other window - * managers. Basically, a workspace is a specific set of windows, usually - * grouped thematically (irc, www, work, …). You can switch between these. - * - */ -struct Workspace { - /** Number of this workspace, starting from 0 */ - int num; - - /** Name of the workspace (in UTF-8) */ - char *utf8_name; - - /** Name of the workspace (in UCS-2) */ - char *name; - - /** Length of the workspace’s name (in glyphs) */ - int name_len; - - /** Width of the workspace’s name (in pixels) rendered in config.font */ - int text_width; - - /** x, y, width, height */ - Rect rect; - - /** table dimensions */ - int cols; - /** table dimensions */ - int rows; - - /** These are stored here only while this workspace is _not_ shown - * (see show_workspace()) */ - int current_row; - /** These are stored here only while this workspace is _not_ shown - * (see show_workspace()) */ - int current_col; - - /** Should clients on this workspace be automatically floating? */ - bool auto_float; - /** Are the floating clients on this workspace currently hidden? */ - bool floating_hidden; - - /** The name of the RandR output this screen should be on */ - char *preferred_output; - - /** True if any client on this workspace has its urgent flag set */ - bool urgent; - - /** the client who is started in fullscreen mode on this workspace, - * NULL if there is none */ - Client *fullscreen_client; - - /** The focus stack contains the clients in the correct order of focus - so that the focus can be reverted correctly when a client is - closed */ - SLIST_HEAD(focus_stack_head, Client) focus_stack; - - /** This tail queue contains the floating clients in order of when - * they were first set to floating (new floating clients are just - * appended) */ - TAILQ_HEAD(floating_clients_head, Client) floating_clients; - - /** Backpointer to the output this workspace is on */ - Output *output; - - /** This is a two-dimensional dynamic array of - * Container-pointers. I’ve always wanted to be a three-star - * programmer :) */ - Container ***table; - - /** width_factor and height_factor contain the amount of space - * (percentage) a column/row has of all the space which is available - * for resized windows. This ensures that non-resized windows (newly - * opened, for example) have the same size as always */ - float *width_factor; - float *height_factor; - - TAILQ_ENTRY(Workspace) workspaces; -}; - /** * Holds a keybinding, consisting of a keycode combined with modifiers and the * command which is executed as soon as the key is pressed (see src/command.c) @@ -329,172 +207,6 @@ struct Font { TAILQ_ENTRY(Font) fonts; }; -/** - * A client is X11-speak for a window. - * - */ -struct Client { - /** initialized will be set to true if the client was fully - * initialized by manage_window() and all functions can be used - * normally */ - bool initialized; - - /** if you set a client to floating and set it back to managed, it - * does remember its old position and *tries* to get back there */ - Cell old_position; - - /** Backpointer. A client is inside a container */ - Container *container; - /** Because dock clients don’t have a container, we have this - * workspace-backpointer */ - Workspace *workspace; - - /** x, y, width, height of the frame */ - Rect rect; - /** Position in floating mode and in tiling mode are saved - * separately */ - Rect floating_rect; - /** x, y, width, height of the child (relative to its frame) */ - Rect child_rect; - - /** contains the size calculated from the hints set by the window or 0 - * if the client did not send any hints */ - int proportional_height; - int proportional_width; - - int base_height; - int base_width; - - /** The amount of pixels which X will draw around the client. */ - int border_width; - - /** contains the minimum increment size as specified for the window - * (in pixels). */ - int width_increment; - int height_increment; - - /** Height which was determined by reading the _NET_WM_STRUT_PARTIAL - * top/bottom of the screen reservation */ - int desired_height; - - /** Name (= window title) */ - char *name; - /** name_len stores the real string length (glyphs) of the window - * title if the client uses _NET_WM_NAME. Otherwise, it is set to -1 - * to indicate that name should be just passed to X as 8-bit string - * and therefore will not be rendered correctly. This behaviour is to - * support legacy applications which do not set _NET_WM_NAME */ - int name_len; - /** This will be set to true as soon as the first _NET_WM_NAME comes - * in. If set to true, legacy window names are ignored. */ - bool uses_net_wm_name; - - /** Holds the WM_CLASS (which consists of two strings, the instance - * and the class), useful for matching the client in commands */ - char *window_class_instance; - char *window_class_class; - - /** Holds the client’s mark, for vim-like jumping */ - char *mark; - - /** Holds the xcb_window_t (just an ID) for the leader window (logical - * parent for toolwindows and similar floating windows) */ - xcb_window_t leader; - - /** fullscreen is pretty obvious */ - bool fullscreen; - - /** floating? (= not in tiling layout) This cannot be simply a bool - * because we want to keep track of whether the status was set by the - * application (by setting WM_CLASS to tools for example) or by the - * user. The user’s choice overwrites automatic mode, of course. The - * order of the values is important because we check with >= - * FLOATING_AUTO_ON if a client is floating. */ - enum { FLOATING_AUTO_OFF = 0, FLOATING_USER_OFF = 1, FLOATING_AUTO_ON = 2, FLOATING_USER_ON = 3 } floating; - - /** Ensure TITLEBAR_TOP maps to 0 because we use calloc for - * initialization later */ - enum { TITLEBAR_TOP = 0, TITLEBAR_LEFT, TITLEBAR_RIGHT, TITLEBAR_BOTTOM, TITLEBAR_OFF } titlebar_position; - - /** Contains a bool specifying whether this window should not be drawn - * with the usual decorations */ - bool borderless; - - /** If a client is set as a dock, it is placed at the very bottom of - * the screen and its requested size is used */ - bool dock; - - /** True if the client set the urgency flag in its WM_HINTS property */ - bool urgent; - - /* After leaving fullscreen mode, a client needs to be reconfigured - * (configuration = setting X, Y, width and height). By setting the - * force_reconfigure flag, render_layout() will reconfigure the - * client. */ - bool force_reconfigure; - - /* When reparenting a window, an unmap-notify is sent. As we delete - * windows when they’re unmapped, we need to ignore that - * one. Therefore, this flag is set when reparenting. */ - bool awaiting_useless_unmap; - - /* XCB contexts */ - xcb_window_t frame; /**< Our window: The frame around the - * client */ - xcb_gcontext_t titlegc; /**< The titlebar’s graphic context - * inside the frame */ - xcb_window_t child; /**< The client’s window */ - - /** The following entry provides the necessary list pointers to use - * Client with LIST_* macros */ - CIRCLEQ_ENTRY(Client) clients; - SLIST_ENTRY(Client) dock_clients; - SLIST_ENTRY(Client) focus_clients; - TAILQ_ENTRY(Client) floating_clients; -}; - -/** - * A container is either in default, stacking or tabbed mode. There is one for - * each cell of the table. - * - */ -struct Container { - /* Those are speaking for themselves: */ - Client *currently_focused; - int colspan; - int rowspan; - - /* Position of the container inside our table */ - int row; - int col; - /* Xinerama: X/Y of the container */ - int x; - int y; - /* Width/Height of the container. Changeable by the user */ - int width; - int height; - - /* When in stacking mode, we draw the titlebars of each client onto a - * separate window */ - struct Stack_Window stack_win; - - /* Backpointer to the workspace this container is in */ - Workspace *workspace; - - /* Ensure MODE_DEFAULT maps to 0 because we use calloc for - * initialization later */ - enum { MODE_DEFAULT = 0, MODE_STACK, MODE_TABBED } mode; - - /* When in stacking, one can either have unlimited windows inside the - * container or set a limit for the rows or columns the stack window - * should display to use the screen more efficiently. */ - enum { STACK_LIMIT_NONE = 0, STACK_LIMIT_COLS, STACK_LIMIT_ROWS } stack_limit; - - /* The number of columns or rows to limit to, see stack_limit */ - int stack_limit_value; - - CIRCLEQ_HEAD(client_head, Client) clients; -}; /** * An Output is a physical output on your graphics driver. Outputs which @@ -518,9 +230,6 @@ struct xoutput { bool changed; bool to_be_disabled; - /** Current workspace selected on this virtual screen */ - Workspace *current_workspace; - /** x, y, width, height */ Rect rect; @@ -535,4 +244,85 @@ struct xoutput { TAILQ_ENTRY(xoutput) outputs; }; +struct Window { + xcb_window_t id; + + const char *class; +}; + +struct Match { + enum { M_WINDOW, M_CON } what; + + char *title; + int title_len; + char *application; + char *class; + char *instance; + xcb_window_t id; + bool floating; + + enum { M_GLOBAL, M_OUTPUT, M_WORKSPACE } levels; + + enum { M_USER, M_RESTART } source; + + /* wo das fenster eingefügt werden soll. bei here wird es direkt + * diesem Con zugewiesen, also layout saving. bei active ist es + * ein assignment, welches an der momentan fokussierten stelle einfügt */ + enum { M_HERE, M_ACTIVE } insert_where; + + TAILQ_ENTRY(Match) matches; +}; + +struct Con { + bool mapped; + enum { CT_ROOT = 0, CT_OUTPUT = 1, CT_CON = 2, CT_FLOATING_CON = 3 } type; + orientation_t orientation; + struct Con *parent; + /* parent before setting it to floating */ + struct Con *old_parent; + + struct Rect rect; + struct Rect window_rect; + struct Rect deco_rect; + + char *name; + + double percent; + + struct Window *window; + + /* ids/gc for the frame window */ + xcb_window_t frame; + xcb_gcontext_t gc; + + /* Only workspace-containers can have floating clients */ + TAILQ_HEAD(floating_head, Con) floating_head; + + TAILQ_HEAD(nodes_head, Con) nodes_head; + TAILQ_HEAD(focus_head, Con) focus_head; + + TAILQ_HEAD(swallow_head, Match) swallow_head; + + enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode; + enum { L_DEFAULT = 0, L_STACKED = 1, L_TABBED = 2 } layout; + /** floating? (= not in tiling layout) This cannot be simply a bool + * because we want to keep track of whether the status was set by the + * application (by setting _NET_WM_WINDOW_TYPE appropriately) or by the + * user. The user’s choice overwrites automatic mode, of course. The + * order of the values is important because we check with >= + * FLOATING_AUTO_ON if a client is floating. */ + enum { + FLOATING_AUTO_OFF = 0, + FLOATING_USER_OFF = 1, + FLOATING_AUTO_ON = 2, + FLOATING_USER_ON = 3 + } floating; + + + TAILQ_ENTRY(Con) nodes; + TAILQ_ENTRY(Con) focused; + TAILQ_ENTRY(Con) all_cons; + TAILQ_ENTRY(Con) floating_windows; +}; + #endif diff --git a/include/floating.h b/include/floating.h index aa9d9d5f..4f7cf422 100644 --- a/include/floating.h +++ b/include/floating.h @@ -11,14 +11,15 @@ #ifndef _FLOATING_H #define _FLOATING_H +#include "tree.h" + /** Callback for dragging */ -typedef void(*callback_t)(xcb_connection_t*, Client*, Rect*, uint32_t, uint32_t, void*); +typedef void(*callback_t)(Con*, Rect*, uint32_t, uint32_t, void*); /** Macro to create a callback function for dragging */ #define DRAGGING_CB(name) \ - static void name(xcb_connection_t *conn, Client *client, \ - Rect *old_rect, uint32_t new_x, uint32_t new_y, \ - void *extra) + static void name(Con *con, Rect *old_rect, uint32_t new_x, \ + uint32_t new_y, void *extra) /** On which border was the dragging initiated? */ typedef enum { BORDER_LEFT = (1 << 0), @@ -36,9 +37,9 @@ typedef enum { BORDER_LEFT = (1 << 0), * the user. * */ -void toggle_floating_mode(xcb_connection_t *conn, Client *client, - bool automatic); +void toggle_floating_mode(Con *con, bool automatic); +#if 0 /** * Removes the floating client from its workspace and attaches it to the new * workspace. This is centralized here because it may happen if you move it @@ -56,13 +57,14 @@ void floating_assign_to_workspace(Client *client, Workspace *new_workspace); int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event); +#endif /** * Called when the user clicked on the titlebar of a floating window. * Calls the drag_pointer function with the drag_window callback * */ -void floating_drag_window(xcb_connection_t *conn, Client *client, - xcb_button_press_event_t *event); +void floating_drag_window(Con *con, xcb_button_press_event_t *event); +#if 0 /** * Called when the user clicked on a floating window while holding the @@ -97,6 +99,7 @@ void floating_move(xcb_connection_t *conn, Client *currently_focused, */ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace); +#endif /** * This function grabs your pointer and lets you drag stuff around (borders). * Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received @@ -105,7 +108,7 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace); * the event and the new coordinates (x, y). * */ -void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, +void drag_pointer(Con *con, xcb_button_press_event_t *event, xcb_window_t confine_to, border_t border, callback_t callback, void *extra); diff --git a/include/handlers.h b/include/handlers.h index c7cbb322..b92b59a4 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -21,6 +21,7 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event); +#if 0 /** * When the user moves the mouse pointer onto a window, this callback gets * called. @@ -200,5 +201,6 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *prop); +#endif #endif diff --git a/include/i3.h b/include/i3.h index bf9d4b81..562c557d 100644 --- a/include/i3.h +++ b/include/i3.h @@ -23,7 +23,7 @@ #define NUM_ATOMS 21 -extern xcb_connection_t *global_conn; +extern xcb_connection_t *conn; extern xcb_key_symbols_t *keysyms; extern char **start_argv; extern Display *xkbdpy; diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 1ea39182..0046b637 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -27,13 +27,8 @@ #define I3_IPC_MESSAGE_TYPE_COMMAND 0 /** Requests the current workspaces from i3 */ -#define I3_IPC_MESSAGE_TYPE_GET_WORKSPACES 1 +#define I3_IPC_MESSAGE_TYPE_GET_TREE 1 -/** Subscribe to the specified events */ -#define I3_IPC_MESSAGE_TYPE_SUBSCRIBE 2 - -/** Requests the current outputs from i3 */ -#define I3_IPC_MESSAGE_TYPE_GET_OUTPUTS 3 /* * Messages from i3 to clients @@ -44,13 +39,7 @@ #define I3_IPC_REPLY_TYPE_COMMAND 0 /** Workspaces reply type */ -#define I3_IPC_REPLY_TYPE_WORKSPACES 1 - -/** Subscription reply type */ -#define I3_IPC_REPLY_TYPE_SUBSCRIBE 2 - -/** Outputs reply type */ -#define I3_IPC_REPLY_TYPE_OUTPUTS 3 +#define I3_IPC_REPLY_TYPE_TREE 1 /* * Events from i3 to clients. Events have the first bit set high. diff --git a/include/ipc.h b/include/ipc.h index 63d59141..7f92ee61 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -13,6 +13,12 @@ #define _IPC_H #include +#include +#include +#include + +#include "data.h" +#include "tree.h" #include "i3/ipc.h" @@ -74,4 +80,6 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa */ void ipc_shutdown(); +void dump_node(yajl_gen gen, Con *con, bool inplace_restart); + #endif diff --git a/include/load_layout.h b/include/load_layout.h new file mode 100644 index 00000000..f3a60a09 --- /dev/null +++ b/include/load_layout.h @@ -0,0 +1,6 @@ +#ifndef _LOAD_LAYOUT_H +#define _LOAD_LAYOUT_H + +void tree_append_json(const char *filename); + +#endif diff --git a/include/manage.h b/include/manage.h index 9c87a08e..555cefbe 100644 --- a/include/manage.h +++ b/include/manage.h @@ -20,8 +20,7 @@ * manage them * */ -void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t - *prophs, xcb_window_t root); +void manage_existing_windows(xcb_window_t root); /** * Restores the geometry of each window by reparenting it to the root window @@ -31,17 +30,17 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t * side-effects which are to be expected when continuing to run i3. * */ -void restore_geometry(xcb_connection_t *conn); +void restore_geometry(); /** * Do some sanity checks and then reparent the window. * */ -void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, - xcb_window_t window, +void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cookie, bool needs_to_be_mapped); +#if 0 /** * reparent_window() gets called when a new window was opened and becomes a * child of the root window, or it gets called by us when we manage the @@ -56,3 +55,4 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, uint32_t border_width); #endif +#endif diff --git a/include/randr.h b/include/randr.h index 4832efe5..7a501b8e 100644 --- a/include/randr.h +++ b/include/randr.h @@ -22,7 +22,7 @@ extern struct outputs_head outputs; * XRandR information to setup workspaces for each screen. * */ -void initialize_randr(xcb_connection_t *conn, int *event_base); +void randr_init(int *event_base); /** * Disables RandR support by creating exactly one output with the size of the @@ -35,13 +35,13 @@ void disable_randr(xcb_connection_t *conn); * Initializes the specified output, assigning the specified workspace to it. * */ -void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace); +//void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace); /** * (Re-)queries the outputs via RandR and stores them in the list of outputs. * */ -void randr_query_outputs(xcb_connection_t *conn); +void randr_query_outputs(); /** * Returns the first output which is active. diff --git a/include/render.h b/include/render.h new file mode 100644 index 00000000..26ae8d51 --- /dev/null +++ b/include/render.h @@ -0,0 +1,10 @@ +/* + * vim:ts=4:sw=4:expandtab + */ + +#ifndef _RENDER_H +#define _RENDER_H + +void render_con(Con *con); + +#endif diff --git a/include/table.h b/include/table.h deleted file mode 100644 index 6236bef5..00000000 --- a/include/table.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * vim:ts=8:expandtab - * - * i3 - an improved dynamic tiling window manager - * - * (c) 2009 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - */ -#include - -#include - -#include "data.h" - -#ifndef _TABLE_H -#define _TABLE_H - -#define CUR_TABLE (c_ws->table) -#define CUR_CELL (CUR_TABLE[current_col][current_row]) - -extern Workspace *c_ws; -extern TAILQ_HEAD(workspaces_head, Workspace) *workspaces; -//extern int num_workspaces; -extern int current_col; -extern int current_row; - -/** Initialize table */ -void init_table(); - -/** Add one row to the table */ -void expand_table_rows(Workspace *workspace); - -/** Adds one row at the head of the table */ -void expand_table_rows_at_head(Workspace *workspace); - -/** Add one column to the table */ -void expand_table_cols(Workspace *workspace); - -/** - * Inserts one column at the table’s head - * - */ -void expand_table_cols_at_head(Workspace *workspace); - -/** - * Performs simple bounds checking for the given column/row - * - */ -bool cell_exists(Workspace *ws, int col, int row); - -/** - * Shrinks the table by "compacting" it, that is, removing completely empty - * rows/columns - * - */ -void cleanup_table(xcb_connection_t *conn, Workspace *workspace); - -/** - * Fixes col/rowspan (makes sure there are no overlapping windows) - * - */ -void fix_colrowspan(xcb_connection_t *conn, Workspace *workspace); - -/** - * Prints the table’s contents in human-readable form for debugging - * - */ -void dump_table(xcb_connection_t *conn, Workspace *workspace); - -#endif diff --git a/include/tree.h b/include/tree.h new file mode 100644 index 00000000..21c02967 --- /dev/null +++ b/include/tree.h @@ -0,0 +1,28 @@ +/* + * vim:ts=4:sw=4:expandtab + */ + +#ifndef _TREE_H +#define _TREE_H + +extern Con *croot; +/* TODO: i am not sure yet how much access to the focused container should + * be permitted to source files */ +extern Con *focused; +TAILQ_HEAD(all_cons_head, Con); +extern struct all_cons_head all_cons; + +void tree_init(); +Con *tree_open_con(Con *con); +void tree_split(Con *con, orientation_t orientation); +void con_focus(Con *con); +void level_up(); +void level_down(); +void tree_render(); +void tree_close_con(); +void tree_next(char way, orientation_t orientation); +void tree_move(char way, orientation_t orientation); +void tree_close(Con *con); +bool tree_restore(); + +#endif diff --git a/include/util.h b/include/util.h index d1384962..937e654b 100644 --- a/include/util.h +++ b/include/util.h @@ -62,6 +62,13 @@ void *smalloc(size_t size); */ void *scalloc(size_t size); +/** + * Safe-wrapper around realloc which exits if realloc returns NULL (meaning + * that there is no more memory available). + * + */ +void *srealloc(void *ptr, size_t size); + /** * Safe-wrapper around strdup which exits if malloc returns NULL (meaning that * there is no more memory available) @@ -118,6 +125,7 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, */ char *convert_utf8_to_ucs2(char *input, int *real_strlen); +#if 0 /** * Returns the client which comes next in focus stack (= was selected before) for * the given container, optionally excluding the given client. @@ -125,7 +133,9 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen); */ Client *get_last_focused_client(xcb_connection_t *conn, Container *container, Client *exclude); +#endif +#if 0 /** * Sets the given client as focused by updating the data structures correctly, * updating the X input focus and finally re-decorating both windows (to @@ -156,6 +166,7 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode); */ Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle, Client *specific); +#endif /* * Restart i3 in-place diff --git a/include/workspace.h b/include/workspace.h index dae245ce..7a61daa8 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -11,6 +11,7 @@ #include #include "data.h" +#include "tree.h" #include "randr.h" #ifndef _WORKSPACE_H @@ -22,8 +23,9 @@ * memory and initializing the data structures correctly). * */ -Workspace *workspace_get(int number); +Con *workspace_get(const char *num); +#if 0 /** * Sets the name (or just its number) for the given workspace. This has to * be called for every workspace as the rendering function @@ -41,9 +43,11 @@ void workspace_set_name(Workspace *ws, const char *name); */ bool workspace_is_visible(Workspace *ws); +#endif /** Switches to the given workspace */ -void workspace_show(xcb_connection_t *conn, int workspace); +void workspace_show(const char *num); +#if 0 /** * Assigns the given workspace to the given screen by correctly updating its * state and reconfiguring all the clients on this workspace. @@ -106,5 +110,5 @@ int workspace_width(Workspace *ws); * */ int workspace_height(Workspace *ws); - +#endif #endif diff --git a/include/x.h b/include/x.h new file mode 100644 index 00000000..85dfc3ce --- /dev/null +++ b/include/x.h @@ -0,0 +1,15 @@ +/* + * vim:ts=4:sw=4:expandtab + */ + +#ifndef _X_H +#define _X_H + +void x_con_init(Con *con); +void x_con_kill(Con *con); +void x_window_kill(xcb_window_t window); +void x_draw_decoration(Con *con); +void x_push_changes(Con *con); +void x_raise_con(Con *con); + +#endif diff --git a/include/xcb.h b/include/xcb.h index 78e1373a..004a64de 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -85,7 +85,7 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern); * validity. This has to be done by the caller. * */ -uint32_t get_colorpixel(xcb_connection_t *conn, char *hex); +uint32_t get_colorpixel(char *hex); /** * Convenience wrapper around xcb_create_window which takes care of depth, @@ -127,12 +127,14 @@ void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, */ void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window); +#if 0 /** * Generates a configure_notify_event with absolute coordinates (relative to * the X root window, not to the client’s frame) for the given client. * */ void fake_absolute_configure_notify(xcb_connection_t *conn, Client *client); +#endif /** * Finds out which modifier mask is the one for numlock, as the user may diff --git a/include/xinerama.h b/include/xinerama.h index f1182349..600b77f3 100644 --- a/include/xinerama.h +++ b/include/xinerama.h @@ -18,6 +18,6 @@ * Xinerama information to setup workspaces for each screen. * */ -void initialize_xinerama(xcb_connection_t *conn); +void xinerama_init(); #endif diff --git a/src/cfgparse.l b/src/cfgparse.l index 10a13076..fedf286e 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -94,12 +94,12 @@ new_container { return TOKNEWCONTAINER; } new_window { return TOKNEWWINDOW; } focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; } workspace_bar { return TOKWORKSPACEBAR; } -default { yylval.number = MODE_DEFAULT; return TOKCONTAINERMODE; } -stacking { yylval.number = MODE_STACK; return TOKCONTAINERMODE; } -tabbed { yylval.number = MODE_TABBED; return TOKCONTAINERMODE; } +default { /* yylval.number = MODE_DEFAULT; */return TOKCONTAINERMODE; } +stacking { /* yylval.number = MODE_STACK; */return TOKCONTAINERMODE; } +tabbed { /* yylval.number = MODE_TABBED; */return TOKCONTAINERMODE; } stack-limit { return TOKSTACKLIMIT; } -cols { yylval.number = STACK_LIMIT_COLS; return TOKSTACKLIMIT; } -rows { yylval.number = STACK_LIMIT_ROWS; return TOKSTACKLIMIT; } +cols { /* yylval.number = STACK_LIMIT_COLS; */return TOKSTACKLIMIT; } +rows { /* yylval.number = STACK_LIMIT_ROWS; */return TOKSTACKLIMIT; } exec { BEGIN(BIND_AWS_COND); return TOKEXEC; } client.focused { BEGIN(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; } client.focused_inactive { BEGIN(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; } diff --git a/src/cfgparse.y b/src/cfgparse.y index 2774f05c..19bfaec1 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -3,25 +3,12 @@ * vim:ts=8:expandtab * */ -#include -#include -#include #include #include #include #include -#include -#include -#include "data.h" -#include "config.h" -#include "i3.h" -#include "util.h" -#include "queue.h" -#include "table.h" -#include "workspace.h" -#include "xcb.h" -#include "log.h" +#include "all.h" typedef struct yy_buffer_state *YY_BUFFER_STATE; extern int yylex(struct context *context); @@ -372,6 +359,7 @@ new_container: DLOG("new containers will be in mode %d\n", $3); config.container_mode = $3; +#if 0 /* We also need to change the layout of the already existing * workspaces here. Workspaces may exist at this point because * of the other directives which are modifying workspaces @@ -388,6 +376,7 @@ new_container: ws->table[0][0], config.container_mode); } +#endif } | TOKNEWCONTAINER WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER { @@ -395,6 +384,7 @@ new_container: config.container_stack_limit = $5; config.container_stack_limit_value = $7; +#if 0 /* See the comment above */ Workspace *ws; TAILQ_FOREACH(ws, workspaces, workspaces) { @@ -404,6 +394,7 @@ new_container: con->stack_limit = config.container_stack_limit; con->stack_limit_value = config.container_stack_limit_value; } +#endif } ; @@ -454,12 +445,14 @@ workspace: if (ws_num < 1) { DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { +#if 0 Workspace *ws = workspace_get(ws_num - 1); ws->preferred_output = $7; if ($8 != NULL) { workspace_set_name(ws, $8); free($8); } +#endif } } | TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name @@ -469,10 +462,12 @@ workspace: DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { DLOG("workspace name to: %s\n", $5); +#if 0 if ($5 != NULL) { workspace_set_name(workspace_get(ws_num - 1), $5); free($5); } +#endif } } ; @@ -584,7 +579,7 @@ colorpixel: char *hex; if (asprintf(&hex, "#%s", $2) == -1) die("asprintf()"); - $$ = get_colorpixel(global_conn, hex); + $$ = get_colorpixel(hex); free(hex); } ; diff --git a/src/click.c b/src/click.c index c8b9d23b..61328e06 100644 --- a/src/click.c +++ b/src/click.c @@ -11,34 +11,17 @@ * because they are quite large. * */ -#include -#include -#include -#include #include -#include #include -#include #include #include #include -#include "i3.h" -#include "queue.h" -#include "table.h" -#include "config.h" -#include "util.h" -#include "xcb.h" -#include "client.h" -#include "workspace.h" -#include "commands.h" -#include "floating.h" -#include "resize.h" -#include "log.h" -#include "randr.h" +#include "all.h" +#if 0 static struct Stack_Window *get_stack_window(xcb_window_t window_id) { struct Stack_Window *current; @@ -251,50 +234,63 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client, return resize_graphical_handler(conn, ws, first, second, orientation, event); } +#endif int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) { - DLOG("Button %d pressed\n", event->state); - /* This was either a focus for a client’s parent (= titlebar)… */ - Client *client = table_get(&by_child, event->event); + Con *con; + LOG("Button %d pressed\n", event->state); + + con = con_by_window_id(event->event); bool border_click = false; - if (client == NULL) { - client = table_get(&by_parent, event->event); + if (con == NULL) { + con = con_by_frame_id(event->event); border_click = true; } + //if (con && con->type == CT_FLOATING_CON) + //con = TAILQ_FIRST(&(con->nodes_head)); + /* See if this was a click with the configured modifier. If so, we need * to move around the client if it was floating. if not, we just process * as usual. */ - if (config.floating_modifier != 0 && - (event->state & config.floating_modifier) == config.floating_modifier) { - if (client == NULL) { - DLOG("Not handling, floating_modifier was pressed and no client found\n"); + //if (config.floating_modifier != 0 && + //(event->state & config.floating_modifier) == config.floating_modifier) { + if (con == NULL) { + LOG("Not handling, floating_modifier was pressed and no client found\n"); return 1; } - if (client->fullscreen) { - DLOG("Not handling, client is in fullscreen mode\n"); +#if 0 + if (con->fullscreen) { + LOG("Not handling, client is in fullscreen mode\n"); return 1; } - if (client_is_floating(client)) { - DLOG("button %d pressed\n", event->detail); +#endif + if (con->type == CT_FLOATING_CON) { + LOG("button %d pressed\n", event->detail); if (event->detail == 1) { - DLOG("left mouse button, dragging\n"); - floating_drag_window(conn, client, event); - } else if (event->detail == 3) { + LOG("left mouse button, dragging\n"); + floating_drag_window(con, event); + } +#if 0 + else if (event->detail == 3) { bool proportional = (event->state & BIND_SHIFT); DLOG("right mouse button\n"); floating_resize_window(conn, client, proportional, event); } +#endif return 1; } +#if 0 if (!floating_mod_on_tiled_client(conn, client, event)) { xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); xcb_flush(conn); } +#endif return 1; - } + //} +#if 0 if (client == NULL) { /* The client was neither on a client’s titlebar nor on a client itself, maybe on a stack_window? */ if (button_press_stackwin(conn, event)) @@ -405,4 +401,5 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ } return resize_graphical_handler(conn, ws, first, second, orientation, event); +#endif } diff --git a/src/con.c b/src/con.c new file mode 100644 index 00000000..00001d4c --- /dev/null +++ b/src/con.c @@ -0,0 +1,248 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * + * con.c contains all functions which deal with containers directly (creating + * containers, searching containers, getting specific properties from + * containers, …). + * + */ +#include "all.h" + +char *colors[] = { + "#ff0000", + "#00FF00", + "#0000FF", + "#ff00ff", + "#00ffff", + "#ffff00", + "#aa0000", + "#00aa00", + "#0000aa", + "#aa00aa" +}; + + +Con *con_new(Con *parent) { + Con *new = scalloc(sizeof(Con)); + TAILQ_INSERT_TAIL(&all_cons, new, all_cons); + new->type = CT_CON; + new->name = strdup(""); + static int cnt = 0; + LOG("opening window %d\n", cnt); + + /* TODO: remove window coloring after test-phase */ + LOG("color %s\n", colors[cnt]); + new->name = strdup(colors[cnt]); + uint32_t cp = get_colorpixel(colors[cnt]); + cnt++; + if ((cnt % (sizeof(colors) / sizeof(char*))) == 0) + cnt = 0; + + x_con_init(new); + + xcb_change_window_attributes(conn, new->frame, XCB_CW_BACK_PIXEL, &cp); + + TAILQ_INIT(&(new->floating_head)); + TAILQ_INIT(&(new->nodes_head)); + TAILQ_INIT(&(new->focus_head)); + TAILQ_INIT(&(new->swallow_head)); + + if (parent != NULL) + con_attach(new, parent); + + return new; +} + +void con_attach(Con *con, Con *parent) { + con->parent = parent; + TAILQ_INSERT_TAIL(&(parent->nodes_head), con, nodes); + /* We insert to the TAIL because con_focus() will correct this. + * This way, we have the option to insert Cons without having + * to focus them. */ + TAILQ_INSERT_TAIL(&(parent->focus_head), con, focused); +} + +void con_detach(Con *con) { + if (con->type == CT_FLOATING_CON) { + /* TODO: remove */ + } else { + TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); + TAILQ_REMOVE(&(con->parent->focus_head), con, focused); + } +} + +/* + * Returns true when this node is a leaf node (has no children) + * + */ +bool con_is_leaf(Con *con) { + return TAILQ_EMPTY(&(con->nodes_head)); +} + +/* + * Returns true if this node accepts a window (if the node swallows windows, + * it might already have swallowed enough and cannot hold any more). + * + */ +bool con_accepts_window(Con *con) { + /* 1: workspaces never accept direct windows */ + if (con->parent->type == CT_OUTPUT) + return false; + + /* TODO: if this is a swallowing container, we need to check its max_clients */ + return (con->window == NULL); +} + +/* + * Gets the output container (first container with CT_OUTPUT in hierarchy) this + * node is on. + * + */ +Con *con_get_output(Con *con) { + Con *result = con; + while (result != NULL && result->type != CT_OUTPUT) + result = result->parent; + /* We must be able to get an output because focus can never be set higher + * in the tree (root node cannot be focused). */ + assert(result != NULL); + return result; +} + +/* + * Gets the workspace container this node is on. + * + */ +Con *con_get_workspace(Con *con) { + Con *result = con; + while (result != NULL && result->parent->type != CT_OUTPUT) + result = result->parent; + assert(result != NULL); + return result; +} + +/* + * Returns the first fullscreen node below this node. + * + */ +Con *con_get_fullscreen_con(Con *con) { + Con *current; + + LOG("looking for fullscreen node\n"); + /* TODO: is breadth-first-search really appropriate? (check as soon as + * fullscreen levels and fullscreen for containers is implemented) */ + Con **queue = NULL; + int queue_len = 0; + TAILQ_FOREACH(current, &(con->nodes_head), nodes) { + queue_len++; + queue = srealloc(queue, queue_len * sizeof(Con*)); + queue[queue_len-1] = current; + } + + while (queue_len > 0) { + current = queue[queue_len-1]; + LOG("checking %p\n", current); + if (current->fullscreen_mode != CF_NONE) { + free(queue); + return current; + } + LOG("deleting from queue\n"); + queue_len--; + queue = realloc(queue, queue_len * sizeof(Con*)); + } + + return NULL; +} + +/* + * Returns true if the node is floating. + * + */ +bool con_is_floating(Con *con) { + assert(con != NULL); + LOG("checking if con %p is floating\n", con); + return (con->floating >= FLOATING_AUTO_ON); +} + +Con *con_by_window_id(xcb_window_t window) { + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) + if (con->window != NULL && con->window->id == window) + return con; + return NULL; +} + +Con *con_by_frame_id(xcb_window_t frame) { + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) + if (con->frame == frame) + return con; + return NULL; +} + +static bool match_matches_window(Match *match, i3Window *window) { + /* TODO: pcre, full matching, … */ + if (match->class != NULL && strcasecmp(match->class, window->class) == 0) { + LOG("match made by window class (%s)\n", window->class); + return true; + } + + if (match->id != XCB_NONE && window->id == match->id) { + LOG("match made by window id (%d)\n", window->id); + return true; + } + + LOG("window %d (%s) could not be matched\n", window->id, window->class); + + return false; +} + +/* + * Returns the first container which wants to swallow this window + * TODO: priority + * + */ +Con *con_for_window(i3Window *window, Match **store_match) { + Con *con; + Match *match; + LOG("searching con for window %p\n", window); + LOG("class == %s\n", window->class); + + TAILQ_FOREACH(con, &all_cons, all_cons) + TAILQ_FOREACH(match, &(con->swallow_head), matches) { + if (!match_matches_window(match, window)) + continue; + if (store_match != NULL) + *store_match = match; + return con; + } + + return NULL; +} + +/* + * Updates the percent attribute of the children of the given container. This + * function needs to be called when a window is added or removed from a + * container. + * + */ +void con_fix_percent(Con *con, int action) { + Con *child; + int children = 0; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) + children++; + /* TODO: better document why this math works */ + double fix; + if (action == WINDOW_ADD) + fix = (1.0 - (1.0 / (children+1))); + else + fix = 1.0 / (1.0 - (1.0 / (children+1))); + + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + if (child->percent <= 0.0) + continue; + child->percent *= fix; + } +} diff --git a/src/config.c b/src/config.c index c7ef0af9..60c11fe7 100644 --- a/src/config.c +++ b/src/config.c @@ -12,26 +12,12 @@ * mode). * */ -#include -#include -#include -#include -#include -#include -#include /* We need Xlib for XStringToKeysym */ #include +#include -#include - -#include "i3.h" -#include "util.h" -#include "config.h" -#include "xcb.h" -#include "table.h" -#include "workspace.h" -#include "log.h" +#include "all.h" Config config; struct modes_head modes; @@ -40,20 +26,34 @@ struct modes_head modes; * This function resolves ~ in pathnames. * */ -static char *glob_path(const char *path) { +char *glob_path(const char *path) { static glob_t globbuf; if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) die("glob() failed"); char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path); globfree(&globbuf); + + /* If the file does not exist yet, we still may need to resolve tilde, + * so call wordexp */ + if (strcmp(result, path) == 0) { + wordexp_t we; + wordexp(path, &we, WRDE_NOCMD); + if (we.we_wordc > 0) { + free(result); + result = sstrdup(we.we_wordv[0]); + } + wordfree(&we); + } + return result; } + /* * Checks if the given path exists by calling stat(). * */ -static bool path_exists(const char *path) { +bool path_exists(const char *path) { struct stat buf; return (stat(path, &buf) == 0); } @@ -134,14 +134,6 @@ void translate_keysyms() { continue; } -#ifdef OLD_XCB_KEYSYMS_API - bind->number_keycodes = 1; - xcb_keycode_t code = xcb_key_symbols_get_keycode(keysyms, keysym); - DLOG("Translated symbol \"%s\" to 1 keycode (%d)\n", bind->symbol, code); - grab_keycode_for_binding(global_conn, bind, code); - bind->translated_to = smalloc(sizeof(xcb_keycode_t)); - memcpy(bind->translated_to, &code, sizeof(xcb_keycode_t)); -#else uint32_t last_keycode = 0; xcb_keycode_t *keycodes = xcb_key_symbols_get_keycode(keysyms, keysym); if (keycodes == NULL) { @@ -163,7 +155,6 @@ void translate_keysyms() { bind->translated_to = smalloc(bind->number_keycodes * sizeof(xcb_keycode_t)); memcpy(bind->translated_to, keycodes, bind->number_keycodes * sizeof(xcb_keycode_t)); free(keycodes); -#endif } } @@ -323,9 +314,11 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, } /* Clear workspace names */ +#if 0 Workspace *ws; TAILQ_FOREACH(ws, workspaces, workspaces) workspace_set_name(ws, NULL); +#endif } SLIST_INIT(&modes); @@ -348,9 +341,9 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, /* Initialize default colors */ #define INIT_COLOR(x, cborder, cbackground, ctext) \ do { \ - x.border = get_colorpixel(conn, cborder); \ - x.background = get_colorpixel(conn, cbackground); \ - x.text = get_colorpixel(conn, ctext); \ + x.border = get_colorpixel(cborder); \ + x.background = get_colorpixel(cbackground); \ + x.text = get_colorpixel(ctext); \ } while (0) INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff"); @@ -370,6 +363,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, REQUIRED_OPTION(font); +#if 0 /* Set an empty name for every workspace which got no name */ Workspace *ws; TAILQ_FOREACH(ws, workspaces, workspaces) { @@ -384,4 +378,5 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, workspace_set_name(ws, NULL); } +#endif } diff --git a/src/floating.c b/src/floating.c index 61e95599..cd387ba8 100644 --- a/src/floating.c +++ b/src/floating.c @@ -10,38 +10,81 @@ * src/floating.c: contains all functions for handling floating clients * */ -#include -#include -#include -#include -#include -#include "i3.h" -#include "config.h" -#include "data.h" -#include "util.h" -#include "xcb.h" -#include "debug.h" -#include "layout.h" -#include "client.h" -#include "floating.h" -#include "workspace.h" -#include "log.h" +#include "all.h" + +extern xcb_connection_t *conn; /* - * Toggles floating mode for the given client. - * Correctly takes care of the position/size (separately stored for tiling/floating mode) - * and repositions/resizes/redecorates the client. + * Toggles floating mode for the given container. * * If the automatic flag is set to true, this was an automatic update by a change of the * window class from the application which can be overwritten by the user. * */ -void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic) { - Container *con = client->container; - i3Font *font = load_font(conn, config.font); +void toggle_floating_mode(Con *con, bool automatic) { + //i3Font *font = load_font(conn, config.font); + /* see if the client is already floating */ + if (con_is_floating(con)) { + LOG("already floating, re-setting to tiling\n"); + assert(con->old_parent != NULL); + + /* 1: detach from parent container */ + TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); + TAILQ_REMOVE(&(con->parent->focus_head), con, focused); + + /* 2: kill parent container */ + TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows); + tree_close(con->parent); + + /* 3: re-attach to previous parent */ + con->parent = con->old_parent; + TAILQ_INSERT_TAIL(&(con->parent->nodes_head), con, nodes); + TAILQ_INSERT_TAIL(&(con->parent->focus_head), con, focused); + + return; + } + + /* 1: detach the container from its parent */ + /* TODO: refactor this with tree_close() */ + TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); + TAILQ_REMOVE(&(con->parent->focus_head), con, focused); + + Con *child; + int children = 0; + TAILQ_FOREACH(child, &(con->parent->nodes_head), nodes) + children++; + /* TODO: better document why this math works */ + double fix = 1.0 / (1.0 - (1.0 / (children+1))); + TAILQ_FOREACH(child, &(con->parent->nodes_head), nodes) { + if (child->percent <= 0.0) + continue; + child->percent *= fix; + } + + /* 2: create a new container to render the decoration on, add + * it as a floating window to the workspace */ + Con *nc = con_new(NULL); + nc->parent = con_get_workspace(con); + nc->rect = con->rect; + nc->orientation = NO_ORIENTATION; + nc->type = CT_FLOATING_CON; + TAILQ_INSERT_TAIL(&(nc->parent->floating_head), nc, floating_windows); + + /* 3: attach the child to the new parent container */ + con->old_parent = con->parent; + con->parent = nc; + con->floating = FLOATING_USER_ON; + nc->rect.x = 400; + nc->rect.y = 400; + TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes); + TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused); + + + +#if 0 if (client->dock) { DLOG("Not putting dock client into floating mode\n"); return; @@ -138,8 +181,10 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic /* Re-render the tiling layout of this container */ render_container(conn, con); xcb_flush(conn); +#endif } +#if 0 /* * Removes the floating client from its workspace and attaches it to the new workspace. * This is centralized here because it may happen if you move it via keyboard and @@ -258,16 +303,17 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre return 1; } +#endif DRAGGING_CB(drag_window_callback) { struct xcb_button_press_event_t *event = extra; /* Reposition the client correctly while moving */ - client->rect.x = old_rect->x + (new_x - event->root_x); - client->rect.y = old_rect->y + (new_y - event->root_y); - reposition_client(conn, client); + con->rect.x = old_rect->x + (new_x - event->root_x); + con->rect.y = old_rect->y + (new_y - event->root_y); + //reposition_client(conn, con); /* Because reposition_client does not send a faked configure event (only resize does), * we need to initiate that on our own */ - fake_absolute_configure_notify(conn, client); + //fake_absolute_configure_notify(conn, client); /* fake_absolute_configure_notify flushes */ } @@ -276,12 +322,14 @@ DRAGGING_CB(drag_window_callback) { * Calls the drag_pointer function with the drag_window callback * */ -void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { +void floating_drag_window(Con *con, xcb_button_press_event_t *event) { DLOG("floating_drag_window\n"); - drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event); + drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event); + tree_render(); } +#if 0 /* * This is an ugly data structure which we need because there is no standard * way of having nested functions (only available as a gcc extension at the @@ -368,7 +416,7 @@ void floating_resize_window(xcb_connection_t *conn, Client *client, drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, ¶ms); } - +#endif /* * This function grabs your pointer and lets you drag stuff around (borders). * Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received @@ -377,13 +425,14 @@ void floating_resize_window(xcb_connection_t *conn, Client *client, * the event and the new coordinates (x, y). * */ -void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, - xcb_window_t confine_to, border_t border, callback_t callback, void *extra) { +void drag_pointer(Con *con, xcb_button_press_event_t *event, xcb_window_t + confine_to, border_t border, callback_t callback, void *extra) +{ xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; uint32_t new_x, new_y; Rect old_rect; - if (client != NULL) - memcpy(&old_rect, &(client->rect), sizeof(Rect)); + if (con != NULL) + memcpy(&old_rect, &(con->rect), sizeof(Rect)); /* Grab the pointer */ /* TODO: returncode */ @@ -409,7 +458,7 @@ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event int nr = inside_event->response_type; if (nr == 0) { /* An error occured */ - handle_event(NULL, conn, inside_event); + //handle_event(NULL, conn, inside_event); free(inside_event); continue; } @@ -448,7 +497,7 @@ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x; new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y; - callback(conn, client, &old_rect, new_x, new_y, extra); + callback(con, &old_rect, new_x, new_y, extra); FREE(last_motion_notify); } done: @@ -456,6 +505,7 @@ done: xcb_flush(conn); } +#if 0 /* * Changes focus in the given direction for floating clients. * @@ -555,3 +605,4 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) { xcb_flush(conn); } +#endif diff --git a/src/handlers.c b/src/handlers.c index 624c3430..c679da81 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * @@ -8,38 +8,14 @@ * See file LICENSE for license information. * */ -#include -#include -#include -#include #include -#include #include -#include #include #include -#include "i3.h" -#include "debug.h" -#include "table.h" -#include "layout.h" -#include "commands.h" -#include "data.h" -#include "xcb.h" -#include "util.h" -#include "randr.h" -#include "config.h" -#include "queue.h" -#include "resize.h" -#include "client.h" -#include "manage.h" -#include "floating.h" -#include "workspace.h" -#include "log.h" -#include "container.h" -#include "ipc.h" +#include "all.h" /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it, since it’d trigger an infinite loop of switching between the different windows when @@ -88,42 +64,44 @@ static bool event_is_ignored(const int sequence) { * */ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) { - DLOG("Keypress %d, state raw = %d\n", event->detail, event->state); + DLOG("Keypress %d, state raw = %d\n", event->detail, event->state); - /* Remove the numlock bit, all other bits are modifiers we can bind to */ - uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK); - DLOG("(removed numlock, state = %d)\n", state_filtered); - /* Only use the lower 8 bits of the state (modifier masks) so that mouse - * button masks are filtered out */ - state_filtered &= 0xFF; - DLOG("(removed upper 8 bits, state = %d)\n", state_filtered); + /* Remove the numlock bit, all other bits are modifiers we can bind to */ + uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK); + DLOG("(removed numlock, state = %d)\n", state_filtered); + /* Only use the lower 8 bits of the state (modifier masks) so that mouse + * button masks are filtered out */ + state_filtered &= 0xFF; + DLOG("(removed upper 8 bits, state = %d)\n", state_filtered); - if (xkb_current_group == XkbGroup2Index) - state_filtered |= BIND_MODE_SWITCH; + if (xkb_current_group == XkbGroup2Index) + state_filtered |= BIND_MODE_SWITCH; - DLOG("(checked mode_switch, state %d)\n", state_filtered); + DLOG("(checked mode_switch, state %d)\n", state_filtered); - /* Find the binding */ - Binding *bind = get_binding(state_filtered, event->detail); + /* Find the binding */ + Binding *bind = get_binding(state_filtered, event->detail); - /* No match? Then the user has Mode_switch enabled but does not have a - * specific keybinding. Fall back to the default keybindings (without - * Mode_switch). Makes it much more convenient for users of a hybrid - * layout (like us, ru). */ - if (bind == NULL) { - state_filtered &= ~(BIND_MODE_SWITCH); - DLOG("no match, new state_filtered = %d\n", state_filtered); - if ((bind = get_binding(state_filtered, event->detail)) == NULL) { - ELOG("Could not lookup key binding (modifiers %d, keycode %d)\n", - state_filtered, event->detail); - return 1; - } + /* No match? Then the user has Mode_switch enabled but does not have a + * specific keybinding. Fall back to the default keybindings (without + * Mode_switch). Makes it much more convenient for users of a hybrid + * layout (like us, ru). */ + if (bind == NULL) { + state_filtered &= ~(BIND_MODE_SWITCH); + DLOG("no match, new state_filtered = %d\n", state_filtered); + if ((bind = get_binding(state_filtered, event->detail)) == NULL) { + ELOG("Could not lookup key binding (modifiers %d, keycode %d)\n", + state_filtered, event->detail); + return 1; } + } - parse_command(conn, bind->command); - return 1; + parse_command(bind->command); + return 1; } +#if 0 + /* * Called with coordinates of an enter_notify event or motion_notify event * to check if the user crossed virtual screen boundaries and adjust the @@ -1076,3 +1054,4 @@ int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state return 1; } +#endif diff --git a/src/ipc.c b/src/ipc.c index 8ed455dd..0412bdae 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -10,29 +10,14 @@ * ipc.c: Everything about the UNIX domain sockets for IPC * */ -#include #include #include #include -#include -#include -#include -#include -#include -#include -#include #include #include #include -#include "queue.h" -#include "ipc.h" -#include "i3.h" -#include "util.h" -#include "commands.h" -#include "log.h" -#include "table.h" -#include "randr.h" +#include "all.h" /* Shorter names for all those yajl_gen_* functions */ #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) @@ -129,7 +114,7 @@ IPC_HANDLER(command) { * message_size bytes out of the buffer */ char *command = scalloc(message_size); strncpy(command, (const char*)message, message_size); - parse_command(global_conn, (const char*)command); + parse_command((const char*)command); free(command); /* For now, every command gets a positive acknowledge @@ -139,6 +124,88 @@ IPC_HANDLER(command) { I3_IPC_REPLY_TYPE_COMMAND, strlen(reply)); } +void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { + y(map_open); + ystr("id"); + y(integer, (long int)con); + + ystr("type"); + y(integer, con->type); + + ystr("orientation"); + y(integer, con->orientation); + + ystr("layout"); + y(integer, con->layout); + + ystr("rect"); + y(map_open); + ystr("x"); + y(integer, con->rect.x); + ystr("y"); + y(integer, con->rect.y); + ystr("width"); + y(integer, con->rect.width); + ystr("height"); + y(integer, con->rect.height); + y(map_close); + + ystr("name"); + ystr(con->name); + + ystr("window"); + if (con->window) + y(integer, con->window->id); + else y(null); + + ystr("nodes"); + y(array_open); + Con *leaf; + TAILQ_FOREACH(leaf, &(con->nodes_head), nodes) { + dump_node(gen, leaf, inplace_restart); + } + y(array_close); + + ystr("focus"); + y(array_open); + TAILQ_FOREACH(leaf, &(con->nodes_head), nodes) { + y(integer, (long int)leaf); + } + y(array_close); + + ystr("fullscreen_mode"); + y(integer, con->fullscreen_mode); + + if (inplace_restart) { + if (con->window != NULL) { + ystr("swallows"); + y(array_open); + y(map_open); + ystr("id"); + y(integer, con->window->id); + y(map_close); + y(array_close); + } + } + + y(map_close); +} + +IPC_HANDLER(tree) { + printf("tree\n"); + yajl_gen gen = yajl_gen_alloc(NULL, NULL); + dump_node(gen, croot, false); + + const unsigned char *payload; + unsigned int length; + y(get_buf, &payload, &length); + + ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_TREE, length); + y(free); + +} + +#if 0 /* * Formats the reply message for a GET_WORKSPACES request and sends it to the * client @@ -327,14 +394,13 @@ IPC_HANDLER(subscribe) { ipc_send_message(fd, (const unsigned char*)reply, I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply)); } +#endif /* The index of each callback function corresponds to the numeric * value of the message type (see include/i3/ipc.h) */ -handler_t handlers[4] = { +handler_t handlers[2] = { handle_command, - handle_get_workspaces, - handle_subscribe, - handle_get_outputs + handle_tree }; /* diff --git a/src/load_layout.c b/src/load_layout.c new file mode 100644 index 00000000..c7e77eef --- /dev/null +++ b/src/load_layout.c @@ -0,0 +1,160 @@ +#include +#include +#include + +#include "all.h" + +/* TODO: refactor the whole parsing thing */ + +static char *last_key; +static Con *json_node; +static bool parsing_swallows; +static bool parsing_rect; +struct Match *current_swallow; + +static int json_start_map(void *ctx) { + LOG("start of map\n"); + if (parsing_swallows) { + LOG("TODO: create new swallow\n"); + current_swallow = scalloc(sizeof(Match)); + TAILQ_INSERT_TAIL(&(json_node->swallow_head), current_swallow, matches); + } else { + if (!parsing_rect) + json_node = con_new(json_node); + } + return 1; +} + +static int json_end_map(void *ctx) { + LOG("end of map\n"); + if (!parsing_swallows && !parsing_rect) + json_node = json_node->parent; + if (parsing_rect) + parsing_rect = false; + return 1; +} + +static int json_end_array(void *ctx) { + LOG("end of array\n"); + parsing_swallows = false; + return 1; +} + +static int json_key(void *ctx, const unsigned char *val, unsigned int len) { + LOG("key: %.*s\n", len, val); + FREE(last_key); + last_key = scalloc((len+1) * sizeof(char)); + memcpy(last_key, val, len); + if (strcasecmp(last_key, "swallows") == 0) { + parsing_swallows = true; + } + if (strcasecmp(last_key, "rect") == 0) + parsing_rect = true; + return 1; +} + +static int json_string(void *ctx, const unsigned char *val, unsigned int len) { + LOG("string: %.*s for key %s\n", len, val, last_key); + if (parsing_swallows) { + /* TODO: the other swallowing keys */ + if (strcasecmp(last_key, "class") == 0) { + current_swallow->class = scalloc((len+1) * sizeof(char)); + memcpy(current_swallow->class, val, len); + } + LOG("unhandled yet: swallow\n"); + } else { + if (strcasecmp(last_key, "name") == 0) { + json_node->name = scalloc((len+1) * sizeof(char)); + memcpy(json_node->name, val, len); + } + } + return 1; +} + +static int json_int(void *ctx, long val) { + LOG("int %d for key %s\n", val, last_key); + if (strcasecmp(last_key, "orientation") == 0) { + json_node->orientation = val; + } + if (strcasecmp(last_key, "layout") == 0) { + json_node->layout = val; + } + if (strcasecmp(last_key, "type") == 0) { + json_node->type = val; + } + if (strcasecmp(last_key, "fullscreen_mode") == 0) { + json_node->fullscreen_mode = val; + } + + if (parsing_rect) { + if (strcasecmp(last_key, "x") == 0) + json_node->rect.x = val; + else if (strcasecmp(last_key, "y") == 0) + json_node->rect.y = val; + else if (strcasecmp(last_key, "width") == 0) + json_node->rect.width = val; + else if (strcasecmp(last_key, "height") == 0) + json_node->rect.height = val; + else printf("WARNING: unknown key %s in rect\n", last_key); + printf("rect now: (%d, %d, %d, %d)\n", + json_node->rect.x, json_node->rect.y, + json_node->rect.width, json_node->rect.height); + } + if (parsing_swallows) { + if (strcasecmp(last_key, "id") == 0) { + current_swallow->id = val; + } + } + + return 1; +} + +static int json_double(void *ctx, double val) { + LOG("double %f for key %s\n", val, last_key); + if (strcasecmp(last_key, "percent") == 0) { + json_node->percent = val; + } + return 1; +} + +void tree_append_json(const char *filename) { + /* TODO: percent of other windows are not correctly fixed at the moment */ + FILE *f; + if ((f = fopen(filename, "r")) == NULL) { + LOG("Cannot open file\n"); + return; + } + char *buf = malloc(65535); /* TODO */ + int n = fread(buf, 1, 65535, f); + LOG("read %d bytes\n", n); + yajl_gen g; + yajl_handle hand; + yajl_callbacks callbacks; + memset(&callbacks, '\0', sizeof(yajl_callbacks)); + callbacks.yajl_start_map = json_start_map; + callbacks.yajl_end_map = json_end_map; + callbacks.yajl_end_array = json_end_array; + callbacks.yajl_string = json_string; + callbacks.yajl_map_key = json_key; + callbacks.yajl_integer = json_int; + callbacks.yajl_double = json_double; + g = yajl_gen_alloc(NULL, NULL); + hand = yajl_alloc(&callbacks, NULL, NULL, (void*)g); + yajl_status stat; + json_node = focused; + setlocale(LC_NUMERIC, "C"); + stat = yajl_parse(hand, (const unsigned char*)buf, n); + if (stat != yajl_status_ok && + stat != yajl_status_insufficient_data) + { + unsigned char * str = yajl_get_error(hand, 1, (const unsigned char*)buf, n); + fprintf(stderr, (const char *) str); + yajl_free_error(hand, str); + } + + setlocale(LC_NUMERIC, ""); + yajl_parse_complete(hand); + + fclose(f); + //con_focus(json_node); +} diff --git a/src/log.c b/src/log.c index 1fcf70cb..28b51423 100644 --- a/src/log.c +++ b/src/log.c @@ -22,7 +22,7 @@ #include "loglevels.h" static uint32_t loglevel = 0; -static bool verbose = false; +static bool verbose = true; /** * Set verbosity of i3. If verbose is set to true, informative messages will diff --git a/src/manage.c b/src/manage.c index 10cf74c7..b56da976 100644 --- a/src/manage.c +++ b/src/manage.c @@ -11,33 +11,17 @@ * (or existing ones on restart). * */ -#include -#include -#include -#include -#include +#include "all.h" + +extern struct Con *focused; -#include "xcb.h" -#include "data.h" -#include "util.h" -#include "i3.h" -#include "table.h" -#include "config.h" -#include "handlers.h" -#include "layout.h" -#include "manage.h" -#include "floating.h" -#include "client.h" -#include "workspace.h" -#include "log.h" -#include "ewmh.h" /* * Go through all existing windows (if the window manager is restarted) and manage them * */ -void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) { +void manage_existing_windows(xcb_window_t root) { xcb_query_tree_reply_t *reply; int i, len; xcb_window_t *children; @@ -57,7 +41,8 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *pr /* Call manage_window with the attributes for every window */ for (i = 0; i < len; ++i) - manage_window(prophs, conn, children[i], cookies[i], true); + manage_window(children[i], cookies[i], true); + free(reply); free(cookies); @@ -71,15 +56,16 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *pr * side-effects which are to be expected when continuing to run i3. * */ -void restore_geometry(xcb_connection_t *conn) { - Workspace *ws; - Client *client; - DLOG("Restoring geometry\n"); +void restore_geometry() { + LOG("Restoring geometry\n"); - TAILQ_FOREACH(ws, workspaces, workspaces) - SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) - xcb_reparent_window(conn, client->child, root, - client->rect.x, client->rect.y); + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) + if (con->window) { + printf("placing window at %d %d\n", con->rect.x, con->rect.y); + xcb_reparent_window(conn, con->window->id, root, + con->rect.x, con->rect.y); + } /* Make sure our changes reach the X server, we restart/exit now */ xcb_flush(conn); @@ -89,58 +75,123 @@ void restore_geometry(xcb_connection_t *conn) { * Do some sanity checks and then reparent the window. * */ -void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, - xcb_window_t window, xcb_get_window_attributes_cookie_t cookie, +void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cookie, bool needs_to_be_mapped) { xcb_drawable_t d = { window }; xcb_get_geometry_cookie_t geomc; xcb_get_geometry_reply_t *geom; xcb_get_window_attributes_reply_t *attr = 0; + printf("---> looking at window 0x%08x\n", window); + + xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie, + utf8_title_cookie, title_cookie, + class_cookie, leader_cookie; + + wm_type_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX); + strut_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX); + state_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STATE], UINT32_MAX); + utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_NAME], 128); + leader_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[WM_CLIENT_LEADER], UINT32_MAX); + title_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_NAME, 128); + class_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_CLASS, 128); + + geomc = xcb_get_geometry(conn, d); /* Check if the window is mapped (it could be not mapped when intializing and calling manage_window() for every window) */ if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) { - ELOG("Could not get attributes\n"); + LOG("Could not get attributes\n"); return; } - if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) + if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) { + LOG("map_state unviewable\n"); goto out; + } /* Don’t manage clients with the override_redirect flag */ + LOG("override_redirect is %d\n", attr->override_redirect); if (attr->override_redirect) goto out; /* Check if the window is already managed */ - if (table_get(&by_child, window)) + if (con_by_window_id(window) != NULL) goto out; /* Get the initial geometry (position, size, …) */ if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL) goto out; + LOG("reparenting!\n"); + + i3Window *cwindow = scalloc(sizeof(i3Window)); + cwindow->id = window; + + class_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_CLASS, 128); + xcb_get_property_reply_t *preply; + preply = xcb_get_property_reply(conn, class_cookie, NULL); + if (preply == NULL || xcb_get_property_value_length(preply) == 0) { + LOG("cannot get wm_class\n"); + } else cwindow->class = strdup(xcb_get_property_value(preply)); + + Con *nc; + Match *match; + + /* TODO: assignments */ + /* TODO: two matches for one container */ + /* See if any container swallows this new window */ + nc = con_for_window(cwindow, &match); + if (nc == NULL) { + if (focused->type == CT_CON && con_accepts_window(focused)) { + LOG("using current container, focused = %p, focused->name = %s\n", + focused, focused->name); + nc = focused; + } else nc = tree_open_con(NULL); + } else { + if (match != NULL && match->insert_where == M_ACTIVE) { + /* We need to go down the focus stack starting from nc */ + while (TAILQ_FIRST(&(nc->focus_head)) != TAILQ_END(&(nc->focus_head))) { + printf("walking down one step...\n"); + nc = TAILQ_FIRST(&(nc->focus_head)); + } + /* We need to open a new con */ + /* TODO: make a difference between match-once containers (directly assign + * cwindow) and match-multiple (tree_open_con first) */ + nc = tree_open_con(nc->parent); + + } + + } + nc->window = cwindow; + + xcb_void_cookie_t rcookie = xcb_reparent_window_checked(conn, window, nc->frame, 0, 0); + if (xcb_request_check(conn, rcookie) != NULL) { + LOG("Could not reparent the window, aborting\n"); + goto out; + //xcb_destroy_window(conn, nc->frame); + } + + xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window); + + tree_render(); + +#if 0 /* Reparent the window and add it to our list of managed windows */ reparent_window(conn, window, attr->visual, geom->root, geom->depth, geom->x, geom->y, geom->width, geom->height, geom->border_width); +#endif /* Generate callback events for every property we watch */ - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_HINTS); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[WM_CLIENT_LEADER]); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]); - free(geom); out: free(attr); return; } +#if 0 /* * reparent_window() gets called when a new window was opened and becomes a child of the root * window, or it gets called by us when we manage the already existing windows at startup. @@ -517,3 +568,4 @@ map: xcb_flush(conn); } +#endif diff --git a/src/nc.c b/src/nc.c new file mode 100644 index 00000000..709cb541 --- /dev/null +++ b/src/nc.c @@ -0,0 +1,414 @@ +/* + * vim:ts=4:sw=4:expandtab + */ +#include +#include "all.h" + +static int xkb_event_base; + +int xkb_current_group; + +extern Con *focused; + +char **start_argv; + +xcb_connection_t *conn; +xcb_event_handlers_t evenths; +xcb_atom_t atoms[NUM_ATOMS]; + +xcb_window_t root; +uint8_t root_depth; + +xcb_key_symbols_t *keysyms; + +/* The list of key bindings */ +struct bindings_head *bindings; + +/* The list of exec-lines */ +struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts); + +/* The list of assignments */ +struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments); + +/* + * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb. + * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop + * + */ +static void xcb_got_event(EV_P_ struct ev_io *w, int revents) { + /* empty, because xcb_prepare_cb and xcb_check_cb are used */ +} + +/* + * Flush before blocking (and waiting for new events) + * + */ +static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) { + xcb_flush(conn); +} + +/* + * Instead of polling the X connection socket we leave this to + * xcb_poll_for_event() which knows better than we can ever know. + * + */ +static void xcb_check_cb(EV_P_ ev_check *w, int revents) { + xcb_generic_event_t *event; + + while ((event = xcb_poll_for_event(conn)) != NULL) { + xcb_event_handle(&evenths, event); + free(event); + } +} + +int handle_map_request(void *unused, xcb_connection_t *conn, xcb_map_request_event_t *event) { + xcb_get_window_attributes_cookie_t cookie; + + cookie = xcb_get_window_attributes_unchecked(conn, event->window); + + LOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence); + //add_ignore_event(event->sequence); + + manage_window(event->window, cookie, false); + return 1; +} + + +int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event) { + LOG("unmap event for 0x%08x\n", event->window); + Con *con = con_by_window_id(event->window); + if (con == NULL) { + LOG("Not a managed window, ignoring\n"); + return 1; + } + + tree_close(con); + tree_render(); + return 1; +} + +int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) { + Con *parent, *con; + + /* event->count is the number of minimum remaining expose events for this window, so we + skip all events but the last one */ + if (event->count != 0) + return 1; + LOG("expose-event, window = %08x\n", event->window); + + if ((parent = con_by_frame_id(event->window)) == NULL) { + LOG("expose event for unknown window, ignoring\n"); + return 1; + } + + TAILQ_FOREACH(con, &(parent->nodes_head), nodes) { + LOG("expose for con %p / %s\n", con, con->name); + if (con->window) + x_draw_decoration(con); + } + xcb_flush(conn); + + return 1; +} + + + +void parse_command(const char *command) { + printf("received command: %s\n", command); + + if (strcasecmp(command, "open") == 0) + tree_open_con(NULL); + else if (strcasecmp(command, "close") == 0) + tree_close_con(); + else if (strcasecmp(command, "split h") == 0) + tree_split(focused, HORIZ); + else if (strcasecmp(command, "split v") == 0) + tree_split(focused, VERT); + else if (strcasecmp(command, "level up") == 0) + level_up(); + else if (strcasecmp(command, "level down") == 0) + level_down(); + else if (strcasecmp(command, "prev h") == 0) + tree_next('p', HORIZ); + else if (strcasecmp(command, "prev v") == 0) + tree_next('p', VERT); + else if (strcasecmp(command, "next h") == 0) + tree_next('n', HORIZ); + else if (strcasecmp(command, "next v") == 0) + tree_next('n', VERT); + else if (strncasecmp(command, "workspace ", strlen("workspace ")) == 0) + workspace_show(command + strlen("workspace ")); + + else if (strcasecmp(command, "move before h") == 0) + tree_move('p', HORIZ); + else if (strcasecmp(command, "move before v") == 0) + tree_move('p', VERT); + else if (strcasecmp(command, "move after h") == 0) + tree_move('n', HORIZ); + else if (strcasecmp(command, "move after v") == 0) + tree_move('n', VERT); + else if (strncasecmp(command, "restore", strlen("restore")) == 0) + tree_append_json(command + strlen("restore ")); + else if (strncasecmp(command, "exec", strlen("exec")) == 0) + start_application(command + strlen("exec ")); + else if (strcasecmp(command, "restart") == 0) + i3_restart(); + else if (strcasecmp(command, "floating") == 0) + toggle_floating_mode(focused, false); + + tree_render(); + +#if 0 + if (strcasecmp(command, "prev") == 0) + tree_prev(O_CURRENT); +#endif +} + +int main(int argc, char *argv[]) { + int screens; + char *override_configpath = NULL; + bool autostart = true; + bool only_check_config = false; + bool force_xinerama = false; + xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS]; + static struct option long_options[] = { + {"no-autostart", no_argument, 0, 'a'}, + {"config", required_argument, 0, 'c'}, + {"version", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {"force-xinerama", no_argument, 0, 0}, + {0, 0, 0, 0} + }; + int option_index = 0, opt; + + setlocale(LC_ALL, ""); + + /* Disable output buffering to make redirects in .xsession actually useful for debugging */ + if (!isatty(fileno(stdout))) + setbuf(stdout, NULL); + + start_argv = argv; + + while ((opt = getopt_long(argc, argv, "c:Cvahld:V", long_options, &option_index)) != -1) { + switch (opt) { + case 'a': + LOG("Autostart disabled using -a\n"); + autostart = false; + break; + case 'c': + override_configpath = sstrdup(optarg); + break; + case 'C': + LOG("Checking configuration file only (-C)\n"); + only_check_config = true; + break; + case 'v': + printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n"); + exit(EXIT_SUCCESS); + case 'V': + set_verbosity(true); + break; + case 'd': + LOG("Enabling debug loglevel %s\n", optarg); + add_loglevel(optarg); + break; + case 'l': + /* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */ + break; + case 0: + if (strcmp(long_options[option_index].name, "force-xinerama") == 0) { + force_xinerama = true; + ELOG("Using Xinerama instead of RandR. This option should be " + "avoided at all cost because it does not refresh the list " + "of screens, so you cannot configure displays at runtime. " + "Please check if your driver really does not support RandR " + "and disable this option as soon as you can.\n"); + break; + } + /* fall-through */ + default: + fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]); + fprintf(stderr, "\n"); + fprintf(stderr, "-a: disable autostart\n"); + fprintf(stderr, "-v: display version and exit\n"); + fprintf(stderr, "-V: enable verbose mode\n"); + fprintf(stderr, "-d : enable debug loglevel \n"); + fprintf(stderr, "-c : use the provided configfile instead\n"); + fprintf(stderr, "-C: check configuration file and exit\n"); + fprintf(stderr, "--force-xinerama: Use Xinerama instead of RandR. This " + "option should only be used if you are stuck with the " + "nvidia closed source driver which does not support RandR.\n"); + exit(EXIT_FAILURE); + } + } + + LOG("i3 (tree) version " I3_VERSION " starting\n"); + + conn = xcb_connect(NULL, &screens); + if (xcb_connection_has_error(conn)) + errx(EXIT_FAILURE, "Cannot open display\n"); + + load_configuration(conn, override_configpath, false); + if (only_check_config) { + LOG("Done checking configuration file. Exiting.\n"); + exit(0); + } + + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); + root = root_screen->root; + root_depth = root_screen->root_depth; + + uint32_t mask = XCB_CW_EVENT_MASK; + uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | + XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video + projector), the root window gets a + ConfigureNotify */ + XCB_EVENT_MASK_POINTER_MOTION | + XCB_EVENT_MASK_PROPERTY_CHANGE | + XCB_EVENT_MASK_ENTER_WINDOW }; + xcb_void_cookie_t cookie; + cookie = xcb_change_window_attributes_checked(conn, root, mask, values); + check_error(conn, cookie, "Another window manager seems to be running"); + + /* Place requests for the atoms we need as soon as possible */ + #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name); + + REQUEST_ATOM(_NET_SUPPORTED); + REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN); + REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK); + REQUEST_ATOM(_NET_WM_NAME); + REQUEST_ATOM(_NET_WM_STATE); + REQUEST_ATOM(_NET_WM_WINDOW_TYPE); + REQUEST_ATOM(_NET_WM_DESKTOP); + REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK); + REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DIALOG); + REQUEST_ATOM(_NET_WM_WINDOW_TYPE_UTILITY); + REQUEST_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR); + REQUEST_ATOM(_NET_WM_WINDOW_TYPE_SPLASH); + REQUEST_ATOM(_NET_WM_STRUT_PARTIAL); + REQUEST_ATOM(WM_PROTOCOLS); + REQUEST_ATOM(WM_DELETE_WINDOW); + REQUEST_ATOM(UTF8_STRING); + REQUEST_ATOM(WM_STATE); + REQUEST_ATOM(WM_CLIENT_LEADER); + REQUEST_ATOM(_NET_CURRENT_DESKTOP); + REQUEST_ATOM(_NET_ACTIVE_WINDOW); + REQUEST_ATOM(_NET_WORKAREA); + + + xcb_event_handlers_init(conn, &evenths); + xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL); + + xcb_event_set_button_press_handler(&evenths, handle_button_press, NULL); + + xcb_event_set_map_request_handler(&evenths, handle_map_request, NULL); + + xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL); + //xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL); + + xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL); + + /* Setup NetWM atoms */ + #define GET_ATOM(name) \ + do { \ + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \ + if (!reply) { \ + ELOG("Could not get atom " #name "\n"); \ + exit(-1); \ + } \ + atoms[name] = reply->atom; \ + free(reply); \ + } while (0) + + GET_ATOM(_NET_SUPPORTED); + GET_ATOM(_NET_WM_STATE_FULLSCREEN); + GET_ATOM(_NET_SUPPORTING_WM_CHECK); + GET_ATOM(_NET_WM_NAME); + GET_ATOM(_NET_WM_STATE); + GET_ATOM(_NET_WM_WINDOW_TYPE); + GET_ATOM(_NET_WM_DESKTOP); + GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK); + GET_ATOM(_NET_WM_WINDOW_TYPE_DIALOG); + GET_ATOM(_NET_WM_WINDOW_TYPE_UTILITY); + GET_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR); + GET_ATOM(_NET_WM_WINDOW_TYPE_SPLASH); + GET_ATOM(_NET_WM_STRUT_PARTIAL); + GET_ATOM(WM_PROTOCOLS); + GET_ATOM(WM_DELETE_WINDOW); + GET_ATOM(UTF8_STRING); + GET_ATOM(WM_STATE); + GET_ATOM(WM_CLIENT_LEADER); + GET_ATOM(_NET_CURRENT_DESKTOP); + GET_ATOM(_NET_ACTIVE_WINDOW); + GET_ATOM(_NET_WORKAREA); + + keysyms = xcb_key_symbols_alloc(conn); + + xcb_get_numlock_mask(conn); + + translate_keysyms(); + grab_all_keys(conn, false); + + int randr_base; + if (force_xinerama) { + xinerama_init(); + } else { + DLOG("Checking for XRandR...\n"); + randr_init(&randr_base); + +#if 0 + xcb_event_set_handler(&evenths, + randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY, + handle_screen_change, + NULL); +#endif + } + + if (!tree_restore()) + tree_init(); + tree_render(); + + /* proof-of-concept for assignments */ + Con *ws = workspace_get("3"); + + Match *current_swallow = scalloc(sizeof(Match)); + TAILQ_INSERT_TAIL(&(ws->swallow_head), current_swallow, matches); + + current_swallow->insert_where = M_ACTIVE; + current_swallow->class = strdup("xterm"); + + struct ev_loop *loop = ev_loop_new(0); + if (loop == NULL) + die("Could not initialize libev. Bad LIBEV_FLAGS?\n"); + + /* Create the UNIX domain socket for IPC */ + if (config.ipc_socket_path != NULL) { + int ipc_socket = ipc_create_socket(config.ipc_socket_path); + if (ipc_socket == -1) { + ELOG("Could not create the IPC socket, IPC disabled\n"); + } else { + struct ev_io *ipc_io = scalloc(sizeof(struct ev_io)); + ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ); + ev_io_start(loop, ipc_io); + } + } + + struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io)); + struct ev_check *xcb_check = scalloc(sizeof(struct ev_check)); + struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare)); + + ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); + ev_io_start(loop, xcb_watcher); + + ev_check_init(xcb_check, xcb_check_cb); + ev_check_start(loop, xcb_check); + + ev_prepare_init(xcb_prepare, xcb_prepare_cb); + ev_prepare_start(loop, xcb_prepare); + + xcb_flush(conn); + + manage_existing_windows(root); + + ev_loop(loop, 0); +} diff --git a/src/randr.c b/src/randr.c index e61fd9b2..6c63069f 100644 --- a/src/randr.c +++ b/src/randr.c @@ -12,30 +12,11 @@ * (take your time to read it completely, it answers all questions). * */ -#include -#include -#include -#include -#include #include -#include -#include #include -#include "queue.h" -#include "i3.h" -#include "data.h" -#include "table.h" -#include "util.h" -#include "layout.h" -#include "xcb.h" -#include "config.h" -#include "workspace.h" -#include "log.h" -#include "ewmh.h" -#include "ipc.h" -#include "client.h" +#include "all.h" /* While a clean namespace is usually a pretty good thing, we really need * to use shorter names than the whole xcb_randr_* default names. */ @@ -159,6 +140,7 @@ Output *get_output_most(direction_t direction, Output *current) { return candidate; } +#if 0 /* * Initializes the specified output, assigning the specified workspace to it. * @@ -207,6 +189,7 @@ void initialize_output(xcb_connection_t *conn, Output *output, Workspace *worksp workspace_assign_to(ws, output, true); } } +#endif /* * Disables RandR support by creating exactly one output with the size of the @@ -245,8 +228,6 @@ void disable_randr(xcb_connection_t *conn) { */ static void output_change_mode(xcb_connection_t *conn, Output *output) { i3Font *font = load_font(conn, config.font); - Workspace *ws; - Client *client; DLOG("Output mode changed, reconfiguring bar, updating workspaces\n"); Rect bar_rect = {output->rect.x, @@ -256,6 +237,7 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { xcb_set_window_rect(conn, output->bar, bar_rect); +#if 0 /* go through all workspaces and set force_reconfigure */ TAILQ_FOREACH(ws, workspaces, workspaces) { if (ws->output != output) @@ -290,6 +272,7 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { xcb_set_window_rect(conn, client->child, r); } } +#endif } /* @@ -368,8 +351,7 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, * (Re-)queries the outputs via RandR and stores them in the list of outputs. * */ -void randr_query_outputs(xcb_connection_t *conn) { - Workspace *ws; +void randr_query_outputs() { Output *output, *other, *first; xcb_randr_get_screen_resources_current_cookie_t rcookie; resources_reply *res; @@ -460,8 +442,9 @@ void randr_query_outputs(xcb_connection_t *conn) { if ((first = get_first_output()) == NULL) die("No usable outputs available\n"); - bool needs_init = (first->current_workspace == NULL); + //bool needs_init = (first->current_workspace == NULL); +#if 0 TAILQ_FOREACH(ws, workspaces, workspaces) { if (ws->output != output) continue; @@ -469,7 +452,7 @@ void randr_query_outputs(xcb_connection_t *conn) { workspace_assign_to(ws, first, true); if (!needs_init) continue; - initialize_output(conn, first, ws); + //initialize_output(conn, first, ws); needs_init = false; } @@ -479,7 +462,9 @@ void randr_query_outputs(xcb_connection_t *conn) { SLIST_REMOVE_HEAD(&(output->dock_clients), dock_clients); SLIST_INSERT_HEAD(&(first->dock_clients), dock, dock_clients); } - output->current_workspace = NULL; + +#endif + //output->current_workspace = NULL; output->to_be_disabled = false; } else if (output->changed) { output_change_mode(conn, output); @@ -492,8 +477,9 @@ void randr_query_outputs(xcb_connection_t *conn) { disable_randr(conn); } - ewmh_update_workarea(); + //ewmh_update_workarea(); +#if 0 /* Just go through each active output and associate one workspace */ TAILQ_FOREACH(output, &outputs, outputs) { if (!output->active || output->current_workspace != NULL) @@ -501,9 +487,10 @@ void randr_query_outputs(xcb_connection_t *conn) { ws = get_first_workspace_for_output(output); initialize_output(conn, output, ws); } +#endif /* render_layout flushes */ - render_layout(conn); + tree_render(); } /* @@ -511,7 +498,7 @@ void randr_query_outputs(xcb_connection_t *conn) { * XRandR information to setup workspaces for each screen. * */ -void initialize_randr(xcb_connection_t *conn, int *event_base) { +void randr_init(int *event_base) { const xcb_query_extension_reply_t *extreply; extreply = xcb_get_extension_data(conn, &xcb_randr_id); diff --git a/src/render.c b/src/render.c new file mode 100644 index 00000000..b2932f50 --- /dev/null +++ b/src/render.c @@ -0,0 +1,137 @@ +/* + * vim:ts=4:sw=4:expandtab + */ + +#include "all.h" + +/* + * "Renders" the given container (and its children), meaning that all rects are + * updated correctly. Note that this function does not call any xcb_* + * functions, so the changes are completely done in memory only (and + * side-effect free). As soon as you call x_push_changes(), the changes will be + * updated in X11. + * + */ +void render_con(Con *con) { + printf("currently rendering node %p / %s / layout %d\n", + con, con->name, con->layout); + int children = 0; + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) + children++; + printf("children: %d, orientation = %d\n", children, con->orientation); + + /* Copy container rect, subtract container border */ + /* This is the actually usable space inside this container for clients */ + Rect rect = con->rect; + rect.x += 2; + rect.y += 2; + rect.width -= 2 * 2; + rect.height -= 2 * 2; + + int x = rect.x; + int y = rect.y; + + int i = 0; + + printf("mapped = true\n"); + con->mapped = true; + + /* Check for fullscreen nodes */ + Con *fullscreen = con_get_fullscreen_con(con); + if (fullscreen) { + LOG("got fs node: %p\n", fullscreen); + fullscreen->rect = rect; + render_con(fullscreen); + return; + } + + + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + + /* default layout */ + if (con->layout == L_DEFAULT) { + double percentage = 1.0 / children; + if (child->percent > 0.0) + percentage = child->percent; + printf("child %p / %s requests percentage %f\n", + child, child->name, percentage); + + if (con->orientation == HORIZ) { + child->rect.x = x; + child->rect.y = y; + child->rect.width = percentage * rect.width; + child->rect.height = rect.height; + x += child->rect.width; + } else { + child->rect.x = x; + child->rect.y = y; + child->rect.width = rect.width; + child->rect.height = percentage * rect.height; + y += child->rect.height; + } + + /* first we have the decoration, if this is a leaf node */ + if (con_is_leaf(child)) { + printf("that child is a leaf node, subtracting deco\n"); + /* TODO: make a function for relative coords? */ + child->deco_rect.x = child->rect.x - con->rect.x; + child->deco_rect.y = child->rect.y - con->rect.y; + + child->rect.y += 17; + child->rect.height -= 17; + + child->deco_rect.width = child->rect.width; + child->deco_rect.height = 17; + } + } + + if (con->layout == L_STACKED) { + printf("stacked con\n"); + child->rect.x = x; + child->rect.y = y; + child->rect.width = rect.width; + child->rect.height = rect.height; + + child->rect.y += (17 * children); + child->rect.height -= (17 * children); + + child->deco_rect.x = x - con->rect.x; + child->deco_rect.y = y - con->rect.y + (i * 17); + child->deco_rect.width = child->rect.width; + child->deco_rect.height = 17; + } + + printf("child at (%d, %d) with (%d x %d)\n", + child->rect.x, child->rect.y, child->rect.width, child->rect.height); + printf("x now %d, y now %d\n", x, y); + if (child->window) { + /* depending on the border style, the rect of the child window + * needs to be smaller */ + Rect *inset = &(child->window_rect); + *inset = (Rect){0, 0, child->rect.width, child->rect.height}; + /* TODO: different border styles */ + inset->x += 2; + inset->width -= 2 * 2; + inset->height -= 2; + } + x_raise_con(child); + render_con(child); + i++; + } + + /* in a stacking container, we ensure the focused client is raised */ + if (con->layout == L_STACKED) { + Con *foc = TAILQ_FIRST(&(con->focus_head)); + x_raise_con(foc); + } + + TAILQ_FOREACH(child, &(con->floating_head), floating_windows) { + LOG("render floating:\n"); + LOG("floating child at (%d,%d) with %d x %d\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height); + x_raise_con(child); + render_con(child); + } + + printf("-- level up\n"); +} diff --git a/src/table.c b/src/table.c deleted file mode 100644 index 71081013..00000000 --- a/src/table.c +++ /dev/null @@ -1,406 +0,0 @@ -/* - * vim:ts=8:expandtab - * - * i3 - an improved dynamic tiling window manager - * - * © 2009 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - * table.c: Functions/macros for easy modifying/accessing of _the_ table (defining our - * layout). - * - */ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "data.h" -#include "table.h" -#include "util.h" -#include "i3.h" -#include "layout.h" -#include "config.h" -#include "workspace.h" -#include "log.h" - -int current_workspace = 0; -int num_workspaces = 1; -struct workspaces_head *workspaces; -/* Convenience pointer to the current workspace */ -Workspace *c_ws; -int current_col = 0; -int current_row = 0; - -/* - * Initialize table - * - */ -void init_table() { - workspaces = scalloc(sizeof(struct workspaces_head)); - TAILQ_INIT(workspaces); - - c_ws = scalloc(sizeof(Workspace)); - workspace_set_name(c_ws, NULL); - TAILQ_INIT(&(c_ws->floating_clients)); - TAILQ_INSERT_TAIL(workspaces, c_ws, workspaces); -} - -static void new_container(Workspace *workspace, Container **container, int col, int row, bool skip_layout_switch) { - Container *new; - new = *container = scalloc(sizeof(Container)); - CIRCLEQ_INIT(&(new->clients)); - new->colspan = 1; - new->rowspan = 1; - new->col = col; - new->row = row; - new->workspace = workspace; - if (!skip_layout_switch) - switch_layout_mode(global_conn, new, config.container_mode); - new->stack_limit = config.container_stack_limit; - new->stack_limit_value = config.container_stack_limit_value; -} - -/* - * Add one row to the table - * - */ -void expand_table_rows(Workspace *workspace) { - workspace->rows++; - - workspace->height_factor = realloc(workspace->height_factor, sizeof(float) * workspace->rows); - workspace->height_factor[workspace->rows-1] = 0; - - for (int c = 0; c < workspace->cols; c++) { - workspace->table[c] = realloc(workspace->table[c], sizeof(Container*) * workspace->rows); - new_container(workspace, &(workspace->table[c][workspace->rows-1]), c, workspace->rows-1, true); - } - - /* We need to switch the layout in a separate step because it could - * happen that render_layout() (being called by switch_layout_mode()) - * would access containers which were not yet initialized. */ - for (int c = 0; c < workspace->cols; c++) - switch_layout_mode(global_conn, workspace->table[c][workspace->rows-1], config.container_mode); -} - -/* - * Adds one row at the head of the table - * - */ -void expand_table_rows_at_head(Workspace *workspace) { - workspace->rows++; - - workspace->height_factor = realloc(workspace->height_factor, sizeof(float) * workspace->rows); - - DLOG("rows = %d\n", workspace->rows); - for (int rows = (workspace->rows - 1); rows >= 1; rows--) { - DLOG("Moving height_factor %d (%f) to %d\n", rows-1, workspace->height_factor[rows-1], rows); - workspace->height_factor[rows] = workspace->height_factor[rows-1]; - } - - workspace->height_factor[0] = 0; - - for (int cols = 0; cols < workspace->cols; cols++) - workspace->table[cols] = realloc(workspace->table[cols], sizeof(Container*) * workspace->rows); - - /* Move the other rows */ - for (int cols = 0; cols < workspace->cols; cols++) - for (int rows = workspace->rows - 1; rows > 0; rows--) { - DLOG("Moving row %d to %d\n", rows-1, rows); - workspace->table[cols][rows] = workspace->table[cols][rows-1]; - workspace->table[cols][rows]->row = rows; - } - - for (int cols = 0; cols < workspace->cols; cols++) - new_container(workspace, &(workspace->table[cols][0]), cols, 0, false); -} - -/* - * Add one column to the table - * - */ -void expand_table_cols(Workspace *workspace) { - workspace->cols++; - - workspace->width_factor = realloc(workspace->width_factor, sizeof(float) * workspace->cols); - workspace->width_factor[workspace->cols-1] = 0; - - workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols); - workspace->table[workspace->cols-1] = scalloc(sizeof(Container*) * workspace->rows); - - for (int c = 0; c < workspace->rows; c++) - new_container(workspace, &(workspace->table[workspace->cols-1][c]), workspace->cols-1, c, true); - - for (int c = 0; c < workspace->rows; c++) - switch_layout_mode(global_conn, workspace->table[workspace->cols-1][c], config.container_mode); -} - -/* - * Inserts one column at the table’s head - * - */ -void expand_table_cols_at_head(Workspace *workspace) { - workspace->cols++; - - workspace->width_factor = realloc(workspace->width_factor, sizeof(float) * workspace->cols); - - DLOG("cols = %d\n", workspace->cols); - for (int cols = (workspace->cols - 1); cols >= 1; cols--) { - DLOG("Moving width_factor %d (%f) to %d\n", cols-1, workspace->width_factor[cols-1], cols); - workspace->width_factor[cols] = workspace->width_factor[cols-1]; - } - - workspace->width_factor[0] = 0; - - workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols); - workspace->table[workspace->cols-1] = scalloc(sizeof(Container*) * workspace->rows); - - /* Move the other columns */ - for (int rows = 0; rows < workspace->rows; rows++) - for (int cols = workspace->cols - 1; cols > 0; cols--) { - DLOG("Moving col %d to %d\n", cols-1, cols); - workspace->table[cols][rows] = workspace->table[cols-1][rows]; - workspace->table[cols][rows]->col = cols; - } - - for (int rows = 0; rows < workspace->rows; rows++) - new_container(workspace, &(workspace->table[0][rows]), 0, rows, false); -} - -/* - * Shrinks the table by one column. - * - * The containers themselves are freed in move_columns_from() or move_rows_from(). Therefore, this - * function may only be called from move_*() or after making sure that the containers are freed - * properly. - * - */ -static void shrink_table_cols(Workspace *workspace) { - float free_space = workspace->width_factor[workspace->cols-1]; - - workspace->cols--; - - /* Shrink the width_factor array */ - workspace->width_factor = realloc(workspace->width_factor, sizeof(float) * workspace->cols); - - /* Free the container-pointers */ - free(workspace->table[workspace->cols]); - - /* Re-allocate the table */ - workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols); - - /* Distribute the free space */ - if (free_space == 0) - return; - - for (int cols = (workspace->cols-1); cols >= 0; cols--) { - if (workspace->width_factor[cols] == 0) - continue; - - DLOG("Added free space (%f) to %d (had %f)\n", free_space, cols, - workspace->width_factor[cols]); - workspace->width_factor[cols] += free_space; - break; - } -} - -/* - * See shrink_table_cols() - * - */ -static void shrink_table_rows(Workspace *workspace) { - float free_space = workspace->height_factor[workspace->rows-1]; - - workspace->rows--; - for (int cols = 0; cols < workspace->cols; cols++) - workspace->table[cols] = realloc(workspace->table[cols], sizeof(Container*) * workspace->rows); - - /* Shrink the height_factor array */ - workspace->height_factor = realloc(workspace->height_factor, sizeof(float) * workspace->rows); - - /* Distribute the free space */ - if (free_space == 0) - return; - - for (int rows = (workspace->rows-1); rows >= 0; rows--) { - if (workspace->height_factor[rows] == 0) - continue; - - DLOG("Added free space (%f) to %d (had %f)\n", free_space, rows, - workspace->height_factor[rows]); - workspace->height_factor[rows] += free_space; - break; - } -} - -/* - * Performs simple bounds checking for the given column/row - * - */ -bool cell_exists(Workspace *ws, int col, int row) { - return (col >= 0 && col < ws->cols) && - (row >= 0 && row < ws->rows); -} - -static void free_container(xcb_connection_t *conn, Workspace *workspace, int col, int row) { - Container *old_container = workspace->table[col][row]; - - if (old_container->mode == MODE_STACK || old_container->mode == MODE_TABBED) - leave_stack_mode(conn, old_container); - - free(old_container); -} - -static void move_columns_from(xcb_connection_t *conn, Workspace *workspace, int cols) { - DLOG("firstly freeing \n"); - - /* Free the columns which are cleaned up */ - for (int rows = 0; rows < workspace->rows; rows++) - free_container(conn, workspace, cols-1, rows); - - for (; cols < workspace->cols; cols++) - for (int rows = 0; rows < workspace->rows; rows++) { - DLOG("at col = %d, row = %d\n", cols, rows); - Container *new_container = workspace->table[cols][rows]; - - DLOG("moving cols = %d to cols -1 = %d\n", cols, cols-1); - workspace->table[cols-1][rows] = new_container; - - new_container->row = rows; - new_container->col = cols-1; - } -} - -static void move_rows_from(xcb_connection_t *conn, Workspace *workspace, int rows) { - for (int cols = 0; cols < workspace->cols; cols++) - free_container(conn, workspace, cols, rows-1); - - for (; rows < workspace->rows; rows++) - for (int cols = 0; cols < workspace->cols; cols++) { - Container *new_container = workspace->table[cols][rows]; - - DLOG("moving rows = %d to rows -1 = %d\n", rows, rows - 1); - workspace->table[cols][rows-1] = new_container; - - new_container->row = rows-1; - new_container->col = cols; - } -} - -/* - * Prints the table’s contents in human-readable form for debugging - * - */ -void dump_table(xcb_connection_t *conn, Workspace *workspace) { - DLOG("dump_table()\n"); - FOR_TABLE(workspace) { - Container *con = workspace->table[cols][rows]; - DLOG("----\n"); - DLOG("at col=%d, row=%d\n", cols, rows); - DLOG("currently_focused = %p\n", con->currently_focused); - Client *loop; - CIRCLEQ_FOREACH(loop, &(con->clients), clients) { - DLOG("got client %08x / %s\n", loop->child, loop->name); - } - DLOG("----\n"); - } - DLOG("done\n"); -} - -/* - * Shrinks the table by "compacting" it, that is, removing completely empty rows/columns - * - */ -void cleanup_table(xcb_connection_t *conn, Workspace *workspace) { - DLOG("cleanup_table()\n"); - - /* Check for empty columns if we got more than one column */ - for (int cols = 0; (workspace->cols > 1) && (cols < workspace->cols);) { - bool completely_empty = true; - for (int rows = 0; rows < workspace->rows; rows++) - if (workspace->table[cols][rows]->currently_focused != NULL) { - completely_empty = false; - break; - } - if (completely_empty) { - DLOG("Removing completely empty column %d\n", cols); - if (cols < (workspace->cols - 1)) - move_columns_from(conn, workspace, cols+1); - else { - for (int rows = 0; rows < workspace->rows; rows++) - free_container(conn, workspace, cols, rows); - } - shrink_table_cols(workspace); - - if (workspace->current_col >= workspace->cols) - workspace->current_col = workspace->cols - 1; - } else cols++; - } - - /* Check for empty rows if we got more than one row */ - for (int rows = 0; (workspace->rows > 1) && (rows < workspace->rows);) { - bool completely_empty = true; - DLOG("Checking row %d\n", rows); - for (int cols = 0; cols < workspace->cols; cols++) - if (workspace->table[cols][rows]->currently_focused != NULL) { - completely_empty = false; - break; - } - if (completely_empty) { - DLOG("Removing completely empty row %d\n", rows); - if (rows < (workspace->rows - 1)) - move_rows_from(conn, workspace, rows+1); - else { - for (int cols = 0; cols < workspace->cols; cols++) - free_container(conn, workspace, cols, rows); - } - shrink_table_rows(workspace); - - if (workspace->current_row >= workspace->rows) - workspace->current_row = workspace->rows - 1; - } else rows++; - } - - /* Boundary checking for current_col and current_row */ - if (current_col >= c_ws->cols) - current_col = c_ws->cols-1; - - if (current_row >= c_ws->rows) - current_row = c_ws->rows-1; - - if (CUR_CELL->currently_focused != NULL) - set_focus(conn, CUR_CELL->currently_focused, true); -} - -/* - * Fixes col/rowspan (makes sure there are no overlapping windows, obeys borders). - * - */ -void fix_colrowspan(xcb_connection_t *conn, Workspace *workspace) { - DLOG("Fixing col/rowspan\n"); - - FOR_TABLE(workspace) { - Container *con = workspace->table[cols][rows]; - if (con->colspan > 1) { - DLOG("gots one with colspan %d (at %d c, %d r)\n", con->colspan, cols, rows); - while (con->colspan > 1 && - (!cell_exists(workspace, cols + (con->colspan-1), rows) && - workspace->table[cols + (con->colspan - 1)][rows]->currently_focused != NULL)) - con->colspan--; - DLOG("fixed it to %d\n", con->colspan); - } - if (con->rowspan > 1) { - DLOG("gots one with rowspan %d (at %d c, %d r)\n", con->rowspan, cols, rows); - while (con->rowspan > 1 && - (!cell_exists(workspace, cols, rows + (con->rowspan - 1)) && - workspace->table[cols][rows + (con->rowspan - 1)]->currently_focused != NULL)) - con->rowspan--; - DLOG("fixed it to %d\n", con->rowspan); - } - } -} diff --git a/src/tree.c b/src/tree.c new file mode 100644 index 00000000..1d5405fd --- /dev/null +++ b/src/tree.c @@ -0,0 +1,335 @@ +/* + * vim:ts=4:sw=4:expandtab + */ + +#include "all.h" + +struct Con *croot; +struct Con *focused; + +struct all_cons_head all_cons = TAILQ_HEAD_INITIALIZER(all_cons); + +/* + * Sets input focus to the given container. Will be updated in X11 in the next + * run of x_push_changes(). + * + */ +void con_focus(Con *con) { + assert(con != NULL); + + /* 1: set focused-pointer to the new con */ + /* 2: exchange the position of the container in focus stack of the parent all the way up */ + TAILQ_REMOVE(&(con->parent->focus_head), con, focused); + TAILQ_INSERT_HEAD(&(con->parent->focus_head), con, focused); + if (con->parent->parent != NULL) + con_focus(con->parent); + + focused = con; +} + +/* + * Loads tree from ~/.i3/_restart.json + * + */ +bool tree_restore() { + char *globbed = glob_path("~/.i3/_restart.json"); + + if (!path_exists(globbed)) { + LOG("%s does not exist, not restoring tree\n", globbed); + free(globbed); + return false; + } + + /* TODO: refactor the following */ + croot = con_new(NULL); + focused = croot; + + tree_append_json(globbed); + char *old_restart = glob_path("~/.i3/_restart.json.old"); + unlink(old_restart); + rename(globbed, old_restart); + free(globbed); + free(old_restart); + + printf("appended tree, using new root\n"); + croot = TAILQ_FIRST(&(croot->nodes_head)); + printf("new root = %p\n", croot); + Con *out = TAILQ_FIRST(&(croot->nodes_head)); + printf("out = %p\n", out); + Con *ws = TAILQ_FIRST(&(out->nodes_head)); + printf("ws = %p\n", ws); + con_focus(ws); + + return true; +} + +/* + * Initializes the tree by creating the root node, adding all RandR outputs + * to the tree (that means randr_init() has to be called before) and + * assigning a workspace to each RandR output. + * + */ +void tree_init() { + Output *output; + + croot = con_new(NULL); + croot->name = "root"; + croot->type = CT_ROOT; + + Con *ws; + /* add the outputs */ + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active) + continue; + + Con *oc = con_new(croot); + oc->name = strdup(output->name); + oc->type = CT_OUTPUT; + oc->rect = output->rect; + + /* add a workspace to this output */ + ws = con_new(oc); + ws->name = strdup("1"); + ws->fullscreen_mode = CF_OUTPUT; + } + + con_focus(ws); +} + +/* + * Opens an empty container in the current container + * + */ +Con *tree_open_con(Con *con) { + if (con == NULL) { + /* every focusable Con has a parent (outputs have parent root) */ + con = focused->parent; + /* If the parent is an output, we are on a workspace. In this case, + * the new container needs to be opened as a leaf of the workspace. */ + if (con->type == CT_OUTPUT) + con = focused; + } + + assert(con != NULL); + + /* 3: re-calculate child->percent for each child */ + con_fix_percent(con, WINDOW_ADD); + + /* 4: add a new container leaf to this con */ + Con *new = con_new(con); + con_focus(new); + + return new; +} + +/* + * Closes the given container including all children + * + */ +void tree_close(Con *con) { + /* TODO: check floating clients and adjust old_parent if necessary */ + + /* Get the container which is next focused */ + Con *next; + if (con->type == CT_FLOATING_CON) { + next = TAILQ_NEXT(con, floating_windows); + if (next == TAILQ_END(&(con->parent->floating_head))) + next = con->parent; + } else { + next = TAILQ_NEXT(con, focused); + if (next == TAILQ_END(&(con->parent->nodes_head))) + next = con->parent; + } + + LOG("closing %p\n", con); + Con *child; + /* We cannot use TAILQ_FOREACH because the children get deleted + * in their parent’s nodes_head */ + while (!TAILQ_EMPTY(&(con->nodes_head))) { + child = TAILQ_FIRST(&(con->nodes_head)); + tree_close(child); + } + + /* kill the X11 part of this container */ + x_con_kill(con); + + con_detach(con); + con_fix_percent(con->parent, WINDOW_REMOVE); + + if (con->window != NULL) { + x_window_kill(con->window->id); + free(con->window); + } + free(con->name); + TAILQ_REMOVE(&all_cons, con, all_cons); + free(con); + + /* TODO: check if the container (or one of its children) was focused */ + con_focus(next); +} + +void tree_close_con() { + assert(focused != NULL); + if (focused->parent->type == CT_OUTPUT) { + LOG("Cannot close workspace\n"); + return; + } + + /* Kill con */ + tree_close(focused); +} + +/* + * Splits (horizontally or vertically) the given container by creating a new + * container which contains the old one and the future ones. + * + */ +void tree_split(Con *con, orientation_t orientation) { + /* 2: replace it with a new Con */ + Con *new = con_new(NULL); + Con *parent = con->parent; + TAILQ_REPLACE(&(parent->nodes_head), con, new, nodes); + TAILQ_REPLACE(&(parent->focus_head), con, new, focused); + new->parent = parent; + new->orientation = orientation; + + /* 3: add it as a child to the new Con */ + con_attach(con, new); +} + +void level_up() { + /* We can focus up to the workspace, but not any higher in the tree */ + if (focused->parent->type != CT_CON) { + printf("cannot go up\n"); + return; + } + con_focus(focused->parent); +} + +void level_down() { + /* Go down the focus stack of the current node */ + Con *next = TAILQ_FIRST(&(focused->focus_head)); + if (next == TAILQ_END(&(focused->focus_head))) { + printf("cannot go down\n"); + return; + } + con_focus(next); +} + +static void mark_unmapped(Con *con) { + Con *current; + + con->mapped = false; + TAILQ_FOREACH(current, &(con->nodes_head), nodes) + mark_unmapped(current); +} + +void tree_render() { + if (croot == NULL) + return; + + printf("-- BEGIN RENDERING --\n"); + /* Reset map state for all nodes in tree */ + /* TODO: a nicer method to walk all nodes would be good, maybe? */ + mark_unmapped(croot); + croot->mapped = true; + + /* We start rendering at an output */ + Con *output; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + printf("output %p / %s\n", output, output->name); + render_con(output); + } + x_push_changes(croot); + printf("-- END RENDERING --\n"); +} + +void tree_next(char way, orientation_t orientation) { + /* 1: get the first parent with the same orientation */ + Con *parent = focused->parent; + while (parent->orientation != orientation) { + LOG("need to go one level further up\n"); + /* if the current parent is an output, we are at a workspace + * and the orientation still does not match */ + if (parent->parent->type == CT_OUTPUT) + return; + parent = parent->parent; + } + Con *current = TAILQ_FIRST(&(parent->focus_head)); + assert(current != TAILQ_END(&(parent->focus_head))); + + /* 2: chose next (or previous) */ + Con *next; + if (way == 'n') { + next = TAILQ_NEXT(current, nodes); + /* if we are at the end of the list, we need to wrap */ + if (next == TAILQ_END(&(parent->nodes_head))) + next = TAILQ_FIRST(&(parent->nodes_head)); + } else { + next = TAILQ_PREV(current, nodes_head, nodes); + /* if we are at the end of the list, we need to wrap */ + if (next == TAILQ_END(&(parent->nodes_head))) + next = TAILQ_LAST(&(parent->nodes_head), nodes_head); + } + + /* 3: focus choice comes in here. at the moment we will go down + * until we find a window */ + /* TODO: check for window, atm we only go down as far as possible */ + while (TAILQ_FIRST(&(next->focus_head)) != TAILQ_END(&(next->focus_head))) + next = TAILQ_FIRST(&(next->focus_head)); + + con_focus(next); +} + +void tree_move(char way, orientation_t orientation) { + /* 1: get the first parent with the same orientation */ + Con *parent = focused->parent; + bool level_changed = false; + while (parent->orientation != orientation) { + LOG("need to go one level further up\n"); + /* if the current parent is an output, we are at a workspace + * and the orientation still does not match */ + if (parent->parent->type == CT_OUTPUT) + return; + parent = parent->parent; + level_changed = true; + } + Con *current = TAILQ_FIRST(&(parent->focus_head)); + assert(current != TAILQ_END(&(parent->focus_head))); + + /* 2: chose next (or previous) */ + Con *next = current; + if (way == 'n') { + LOG("i would insert it after %p / %s\n", next, next->name); + if (!level_changed) { + next = TAILQ_NEXT(next, nodes); + if (next == TAILQ_END(&(next->parent->nodes_head))) { + LOG("cannot move further to the right\n"); + return; + } + } + + con_detach(focused); + focused->parent = next->parent; + + TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, focused, nodes); + TAILQ_INSERT_HEAD(&(next->parent->focus_head), focused, focused); + /* TODO: don’t influence focus handling? */ + } else { + LOG("i would insert it before %p / %s\n", current, current->name); + if (!level_changed) { + next = TAILQ_PREV(next, nodes_head, nodes); + if (next == TAILQ_END(&(next->parent->nodes_head))) { + LOG("cannot move further\n"); + return; + } + } + + con_detach(focused); + focused->parent = next->parent; + + TAILQ_INSERT_BEFORE(next, focused, nodes); + TAILQ_INSERT_HEAD(&(next->parent->focus_head), focused, focused); + /* TODO: don’t influence focus handling? */ + } +} diff --git a/src/util.c b/src/util.c index cb37d30a..e381aa52 100644 --- a/src/util.c +++ b/src/util.c @@ -10,34 +10,18 @@ * util.c: Utility functions, which can be useful everywhere. * */ -#include -#include -#include -#include #include #include -#include #include #if defined(__OpenBSD__) #include #endif -#include +#include -#include "i3.h" -#include "data.h" -#include "table.h" -#include "layout.h" -#include "util.h" -#include "xcb.h" -#include "client.h" -#include "log.h" -#include "ewmh.h" -#include "manage.h" -#include "workspace.h" -#include "ipc.h" +#include "all.h" -static iconv_t conversion_descriptor = 0; +//static iconv_t conversion_descriptor = 0; struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent); struct keyvalue_table_head by_child = TAILQ_HEAD_INITIALIZER(by_child); @@ -77,12 +61,20 @@ void *scalloc(size_t size) { return result; } +void *srealloc(void *ptr, size_t size) { + void *result = realloc(ptr, size); + exit_if_null(result, "Error: out memory (realloc(%zd))\n", size); + return result; +} + char *sstrdup(const char *str) { char *result = strdup(str); exit_if_null(result, "Error: out of memory (strdup())\n"); return result; } +#if 0 + /* * The table_* functions emulate the behaviour of libxcb-wm, which in libxcb 0.3.4 suddenly * vanished. Great. @@ -120,7 +112,7 @@ void *table_get(struct keyvalue_table_head *head, uint32_t key) { return NULL; } - +#endif /* * Starts the given application by passing it through a shell. We use double fork * to avoid zombie processes. As the started application’s parent exits (immediately), @@ -132,6 +124,7 @@ void *table_get(struct keyvalue_table_head *head, uint32_t key) { * */ void start_application(const char *command) { + LOG("executing: %s\n", command); if (fork() == 0) { /* Child process */ if (fork() == 0) { @@ -165,6 +158,7 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes } } +#if 0 /* * Converts the given string to UCS-2 big endian for use with * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen, @@ -482,6 +476,7 @@ done: FREE(to_title_ucs); return matching; } +#endif /* * Goes through the list of arguments (for exec()) and checks if the given argument @@ -506,15 +501,58 @@ static char **append_argument(char **original, char *argument) { return result; } +#define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) +#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str)) + +void store_restart_layout() { + yajl_gen gen = yajl_gen_alloc(NULL, NULL); + + dump_node(gen, croot, true); + + const unsigned char *payload; + unsigned int length; + y(get_buf, &payload, &length); + + char *globbed = glob_path("~/.i3/_restart.json"); + int fd = open(globbed, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + free(globbed); + if (fd == -1) { + perror("open()"); + return; + } + + int written = 0; + while (written < length) { + int n = write(fd, payload + written, length - written); + /* TODO: correct error-handling */ + if (n == -1) { + perror("write()"); + return; + } + if (n == 0) { + printf("write == 0?\n"); + return; + } + written += n; + printf("written: %d of %d\n", written, length); + } + close(fd); + + printf("layout: %.*s\n", length, payload); + + y(free); +} + /* * Restart i3 in-place * appends -a to argument list to disable autostart * */ void i3_restart() { - restore_geometry(global_conn); + store_restart_layout(); + restore_geometry(); - ipc_shutdown(); + //ipc_shutdown(); LOG("restarting \"%s\"...\n", start_argv[0]); /* make sure -a is in the argument list or append it */ @@ -524,6 +562,8 @@ void i3_restart() { /* not reached */ } +#if 0 + #if defined(__OpenBSD__) /* @@ -559,4 +599,4 @@ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) { } #endif - +#endif diff --git a/src/workspace.c b/src/workspace.c index c950df8f..d65a5198 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -10,25 +10,11 @@ * workspace.c: Functions for modifying workspaces * */ -#include -#include -#include #include -#include -#include "util.h" -#include "data.h" -#include "i3.h" -#include "config.h" -#include "xcb.h" -#include "table.h" -#include "randr.h" -#include "layout.h" -#include "workspace.h" -#include "client.h" -#include "log.h" -#include "ewmh.h" -#include "ipc.h" +#include "all.h" + +extern Con *focused; /* * Returns a pointer to the workspace with the given number (starting at 0), @@ -36,38 +22,40 @@ * memory and initializing the data structures correctly). * */ -Workspace *workspace_get(int number) { - Workspace *ws = NULL; - TAILQ_FOREACH(ws, workspaces, workspaces) - if (ws->num == number) - return ws; +Con *workspace_get(const char *num) { + Con *output, *workspace = NULL, *current; - /* If we are still there, we could not find the requested workspace. */ - int last_ws = TAILQ_LAST(workspaces, workspaces_head)->num; + /* TODO: could that look like this in the future? + GET_MATCHING_NODE(workspace, croot, strcasecmp(current->name, num) != 0); + */ + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + TAILQ_FOREACH(current, &(output->nodes_head), nodes) { + if (strcasecmp(current->name, num) != 0) + continue; - DLOG("We need to initialize that one, last ws = %d\n", last_ws); + workspace = current; + break; + } + } - for (int c = last_ws; c < number; c++) { - DLOG("Creating new ws\n"); - - ws = scalloc(sizeof(Workspace)); - ws->num = c+1; - TAILQ_INIT(&(ws->floating_clients)); - expand_table_cols(ws); - expand_table_rows(ws); - workspace_set_name(ws, NULL); - - TAILQ_INSERT_TAIL(workspaces, ws, workspaces); + LOG("should switch to ws %s\n", num); + if (workspace == NULL) { + LOG("need to create this one\n"); + output = con_get_output(focused); + LOG("got output %p\n", output); + workspace = con_new(output); + workspace->name = strdup(num); ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); } - DLOG("done\n"); - ewmh_update_workarea(); + //ewmh_update_workarea(); - return ws; + return workspace; } +#if 0 + /* * Sets the name (or just its number) for the given workspace. This has to * be called for every workspace as the rendering function @@ -105,22 +93,27 @@ void workspace_set_name(Workspace *ws, const char *name) { bool workspace_is_visible(Workspace *ws) { return (ws->output != NULL && ws->output->current_workspace == ws); } +#endif + /* * Switches to the given workspace * */ -void workspace_show(xcb_connection_t *conn, int workspace) { - bool need_warp = false; - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; - /* t_ws (to workspace) is just a convenience pointer to the workspace we’re switching to */ - Workspace *t_ws = workspace_get(workspace-1); +void workspace_show(const char *num) { + Con *workspace, *current; - DLOG("show_workspace(%d)\n", workspace); + workspace = workspace_get(num); + workspace->fullscreen_mode = CF_OUTPUT; + /* disable fullscreen */ + TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) + current->fullscreen_mode = CF_NONE; - /* Store current_row/current_col */ - c_ws->current_row = current_row; - c_ws->current_col = current_col; + LOG("switching to %p\n", workspace); + con_focus(workspace); + workspace->fullscreen_mode = CF_OUTPUT; + LOG("focused now = %p / %s\n", focused, focused->name); +#if 0 /* Check if the workspace has not been used yet */ workspace_initialize(t_ws, c_ws->output, false); @@ -209,8 +202,10 @@ void workspace_show(xcb_connection_t *conn, int workspace) { client_warp_pointer_into(conn, last_focused); xcb_flush(conn); } +#endif } +#if 0 /* * Assigns the given workspace to the given output by correctly updating its * state and reconfiguring all the clients on this workspace. @@ -475,3 +470,4 @@ int workspace_height(Workspace *ws) { return height; } +#endif diff --git a/src/x.c b/src/x.c new file mode 100644 index 00000000..2ed8fd11 --- /dev/null +++ b/src/x.c @@ -0,0 +1,299 @@ +/* + * vim:ts=4:sw=4:expandtab + */ + +#include "all.h" + +/* Stores the X11 window ID of the currently focused window */ +static xcb_window_t focused_id = XCB_NONE; + +/* + * Describes the X11 state we may modify (map state, position, window stack). + * There is one entry per container. The state represents the current situation + * as X11 sees it (with the exception of the order in the state_head CIRCLEQ, + * which represents the order that will be pushed to X11, while old_state_head + * represents the current order). It will be updated in x_push_changes(). + * + */ +typedef struct con_state { + xcb_window_t id; + bool mapped; + Rect rect; + Rect window_rect; + + bool initial; + + CIRCLEQ_ENTRY(con_state) state; + CIRCLEQ_ENTRY(con_state) old_state; +} con_state; + +CIRCLEQ_HEAD(state_head, con_state) state_head = + CIRCLEQ_HEAD_INITIALIZER(state_head); + +CIRCLEQ_HEAD(old_state_head, con_state) old_state_head = + CIRCLEQ_HEAD_INITIALIZER(old_state_head); + +/* + * Returns the container state for the given frame. This function always + * returns a container state (otherwise, there is a bug in the code and the + * container state of a container for which x_con_init() was not called was + * requested). + * + */ +static con_state *state_for_frame(xcb_window_t window) { + con_state *state; + CIRCLEQ_FOREACH(state, &state_head, state) + if (state->id == window) + return state; + + /* TODO: better error handling? */ + ELOG("No state found\n"); + assert(true); + return NULL; +} + +/* + * Initializes the X11 part for the given container. Called exactly once for + * every container from con_new(). + * + */ +void x_con_init(Con *con) { + /* TODO: maybe create the window when rendering first? we could then even + * get the initial geometry right */ + + uint32_t mask = 0; + uint32_t values[2]; + + /* our own frames should not be managed */ + mask |= XCB_CW_OVERRIDE_REDIRECT; + values[0] = 1; + + /* We want to know when… */ + mask |= XCB_CW_EVENT_MASK; + values[1] = FRAME_EVENT_MASK; + + Rect dims = { -15, -15, 10, 10 }; + con->frame = create_window(conn, dims, XCB_WINDOW_CLASS_INPUT_OUTPUT, -1, false, mask, values); + con->gc = xcb_generate_id(conn); + xcb_create_gc(conn, con->gc, con->frame, 0, 0); + + struct con_state *state = scalloc(sizeof(struct con_state)); + state->id = con->frame; + state->mapped = false; + state->initial = true; + CIRCLEQ_INSERT_HEAD(&state_head, state, state); + CIRCLEQ_INSERT_HEAD(&old_state_head, state, old_state); + LOG("adding new state for window id 0x%08x\n", state->id); +} + +void x_con_kill(Con *con) { + con_state *state; + + xcb_destroy_window(conn, con->frame); + state = state_for_frame(con->frame); + CIRCLEQ_REMOVE(&state_head, state, state); + CIRCLEQ_REMOVE(&old_state_head, state, old_state); +} + +/* + * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW) + * + */ +static bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom) { + xcb_get_property_cookie_t cookie; + xcb_get_wm_protocols_reply_t protocols; + bool result = false; + + cookie = xcb_get_wm_protocols_unchecked(conn, window, atoms[WM_PROTOCOLS]); + if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1) + return false; + + /* Check if the client’s protocols have the requested atom set */ + for (uint32_t i = 0; i < protocols.atoms_len; i++) + if (protocols.atoms[i] == atom) + result = true; + + xcb_get_wm_protocols_reply_wipe(&protocols); + + return result; +} + +void x_window_kill(xcb_window_t window) { + /* if this window does not support WM_DELETE_WINDOW, we kill it the hard way */ + if (!window_supports_protocol(window, atoms[WM_DELETE_WINDOW])) { + LOG("Killing window the hard way\n"); + xcb_kill_client(conn, window); + return; + } + + xcb_client_message_event_t ev; + + memset(&ev, 0, sizeof(xcb_client_message_event_t)); + + ev.response_type = XCB_CLIENT_MESSAGE; + ev.window = window; + ev.type = atoms[WM_PROTOCOLS]; + ev.format = 32; + ev.data.data32[0] = atoms[WM_DELETE_WINDOW]; + ev.data.data32[1] = XCB_CURRENT_TIME; + + LOG("Sending WM_DELETE to the client\n"); + xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char*)&ev); + xcb_flush(conn); +} + +void x_draw_decoration(Con *con) { + Con *parent; + + parent = con->parent; + + if (con == focused) + xcb_change_gc_single(conn, parent->gc, XCB_GC_FOREGROUND, get_colorpixel("#FF0000")); + else xcb_change_gc_single(conn, parent->gc, XCB_GC_FOREGROUND, get_colorpixel("#0C0C0C")); + xcb_rectangle_t drect = { con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height }; + xcb_poly_fill_rectangle(conn, parent->frame, parent->gc, 1, &drect); + + if (con->window == NULL) { + return; + } + + if (con->window->class == NULL) { + LOG("not rendering decoration, not yet known\n"); + return; + } + + + LOG("should render text %s onto %p / %s\n", con->window->class, parent, parent->name); + + xcb_change_gc_single(conn, parent->gc, XCB_GC_FOREGROUND, get_colorpixel("#FFFFFF")); + xcb_image_text_8( + conn, + strlen(con->window->class), + parent->frame, + parent->gc, + con->deco_rect.x, + con->deco_rect.y + 14, + con->window->class + ); +} + +/* + * This function pushes the properties of each node of the layout tree to + * X11 if they have changed (like the map state, position of the window, …). + * It recursively traverses all children of the given node. + * + */ +static void x_push_node(Con *con) { + Con *current; + con_state *state; + + LOG("Pushing changes for node %p / %s\n", con, con->name); + state = state_for_frame(con->frame); + + /* map/unmap if map state changed */ + if (state->mapped != con->mapped) { + if (!con->mapped) { + LOG("unmapping container\n"); + xcb_unmap_window(conn, con->frame); + } else { + if (state->initial && con->window != NULL) { + LOG("mapping child window\n"); + xcb_map_window(conn, con->window->id); + } + LOG("mapping container\n"); + xcb_map_window(conn, con->frame); + } + state->mapped = con->mapped; + } + + /* set new position if rect changed */ + if (memcmp(&(state->rect), &(con->rect), sizeof(Rect)) != 0) { + LOG("setting rect (%d, %d, %d, %d)\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height); + xcb_set_window_rect(conn, con->frame, con->rect); + memcpy(&(state->rect), &(con->rect), sizeof(Rect)); + } + + /* dito, but for child windows */ + if (memcmp(&(state->window_rect), &(con->window_rect), sizeof(Rect)) != 0) { + LOG("setting window rect (%d, %d, %d, %d)\n", + con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height); + xcb_set_window_rect(conn, con->window->id, con->window_rect); + memcpy(&(state->rect), &(con->rect), sizeof(Rect)); + } + + /* handle all children and floating windows of this node */ + TAILQ_FOREACH(current, &(con->nodes_head), nodes) + x_push_node(current); + + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) + x_push_node(current); + + if (con->type != CT_ROOT && con->type != CT_OUTPUT) + x_draw_decoration(con); +} + +/* + * Pushes all changes (state of each node, see x_push_node() and the window + * stack) to X11. + * + */ +void x_push_changes(Con *con) { + con_state *state; + + LOG("\n\n PUSHING CHANGES\n\n"); + x_push_node(con); + + LOG("-- PUSHING FOCUS STACK --\n"); + /* X11 correctly represents the stack if we push it from bottom to top */ + CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { + LOG("stack: 0x%08x\n", state->id); + con_state *prev = CIRCLEQ_PREV(state, state); + con_state *old_prev = CIRCLEQ_PREV(state, old_state); + if ((state->initial || prev != old_prev) && prev != CIRCLEQ_END(&state_head)) { + state->initial = false; + LOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id); + uint32_t mask = 0; + mask |= XCB_CONFIG_WINDOW_SIBLING; + mask |= XCB_CONFIG_WINDOW_STACK_MODE; + uint32_t values[] = {state->id, XCB_STACK_MODE_ABOVE}; + + xcb_configure_window(conn, prev->id, mask, values); + } + } + + xcb_window_t to_focus = focused->frame; + if (focused->window != NULL) + to_focus = focused->window->id; + + if (focused_id != to_focus) { + LOG("Updating focus\n"); + xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME); + } + + xcb_flush(conn); + LOG("\n\n ENDING CHANGES\n\n"); + + /* save the current stack as old stack */ + CIRCLEQ_FOREACH(state, &state_head, state) { + CIRCLEQ_REMOVE(&old_state_head, state, old_state); + CIRCLEQ_INSERT_TAIL(&old_state_head, state, old_state); + } + CIRCLEQ_FOREACH(state, &old_state_head, old_state) { + LOG("old stack: 0x%08x\n", state->id); + } +} + +/* + * Raises the specified container in the internal stack of X windows. The + * next call to x_push_changes() will make the change visible in X11. + * + */ +void x_raise_con(Con *con) { + con_state *state; + LOG("raising in new stack: %p / %s\n", con, con->name); + state = state_for_frame(con->frame); + + LOG("found state entry, moving to top\n"); + CIRCLEQ_REMOVE(&state_head, state, state); + CIRCLEQ_INSERT_HEAD(&state_head, state, state); +} diff --git a/src/xcb.c b/src/xcb.c index ee3148ed..1fd677b7 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -10,18 +10,8 @@ * xcb.c: Helper functions for easier usage of XCB * */ -#include -#include -#include -#include -#include -#include - -#include "i3.h" -#include "util.h" -#include "xcb.h" -#include "log.h" +#include "all.h" TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts); unsigned int xcb_numlock_mask; @@ -74,7 +64,7 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern) { * This has to be done by the caller. * */ -uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) { +uint32_t get_colorpixel(char *hex) { char strgroups[3][3] = {{hex[1], hex[2], '\0'}, {hex[3], hex[4], '\0'}, {hex[5], hex[6], '\0'}}; @@ -182,6 +172,7 @@ void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window) xcb_flush(conn); } +#if 0 /* * Generates a configure_notify_event with absolute coordinates (relative to the X root * window, not to the client’s frame) for the given client. @@ -197,6 +188,7 @@ void fake_absolute_configure_notify(xcb_connection_t *conn, Client *client) { fake_configure_notify(conn, absolute, client->child); } +#endif /* * Finds out which modifier mask is the one for numlock, as the user may change this. diff --git a/src/xinerama.c b/src/xinerama.c index d7efff0d..bf92a636 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * @@ -12,20 +12,10 @@ * driver which does not support RandR in 2010 *sigh*. * */ -#include -#include -#include -#include #include -#include "queue.h" -#include "data.h" -#include "util.h" -#include "xinerama.h" -#include "workspace.h" -#include "log.h" -#include "randr.h" +#include "all.h" static int num_screens; @@ -34,12 +24,12 @@ static int num_screens; * */ static Output *get_screen_at(int x, int y) { - Output *output; - TAILQ_FOREACH(output, &outputs, outputs) - if (output->rect.x == x && output->rect.y == y) - return output; + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->rect.x == x && output->rect.y == y) + return output; - return NULL; + return NULL; } /* @@ -48,52 +38,52 @@ static Output *get_screen_at(int x, int y) { * */ static void query_screens(xcb_connection_t *conn) { - xcb_xinerama_query_screens_reply_t *reply; - xcb_xinerama_screen_info_t *screen_info; + xcb_xinerama_query_screens_reply_t *reply; + xcb_xinerama_screen_info_t *screen_info; - reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL); - if (!reply) { - ELOG("Couldn't get Xinerama screens\n"); - return; - } - screen_info = xcb_xinerama_query_screens_screen_info(reply); - int screens = xcb_xinerama_query_screens_screen_info_length(reply); + reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL); + if (!reply) { + ELOG("Couldn't get Xinerama screens\n"); + return; + } + screen_info = xcb_xinerama_query_screens_screen_info(reply); + int screens = xcb_xinerama_query_screens_screen_info_length(reply); - for (int screen = 0; screen < screens; screen++) { - Output *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org); - if (s != NULL) { - DLOG("Re-used old Xinerama screen %p\n", s); - /* This screen already exists. We use the littlest screen so that the user - can always see the complete workspace */ - s->rect.width = min(s->rect.width, screen_info[screen].width); - s->rect.height = min(s->rect.height, screen_info[screen].height); - } else { - s = scalloc(sizeof(Output)); - asprintf(&(s->name), "xinerama-%d", num_screens); - DLOG("Created new Xinerama screen %s (%p)\n", s->name, s); - s->active = true; - s->rect.x = screen_info[screen].x_org; - s->rect.y = screen_info[screen].y_org; - s->rect.width = screen_info[screen].width; - s->rect.height = screen_info[screen].height; - /* We always treat the screen at 0x0 as the primary screen */ - if (s->rect.x == 0 && s->rect.y == 0) - TAILQ_INSERT_HEAD(&outputs, s, outputs); - else TAILQ_INSERT_TAIL(&outputs, s, outputs); - num_screens++; - } - - DLOG("found Xinerama screen: %d x %d at %d x %d\n", - screen_info[screen].width, screen_info[screen].height, - screen_info[screen].x_org, screen_info[screen].y_org); + for (int screen = 0; screen < screens; screen++) { + Output *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org); + if (s != NULL) { + DLOG("Re-used old Xinerama screen %p\n", s); + /* This screen already exists. We use the littlest screen so that the user + can always see the complete workspace */ + s->rect.width = min(s->rect.width, screen_info[screen].width); + s->rect.height = min(s->rect.height, screen_info[screen].height); + } else { + s = scalloc(sizeof(Output)); + asprintf(&(s->name), "xinerama-%d", num_screens); + DLOG("Created new Xinerama screen %s (%p)\n", s->name, s); + s->active = true; + s->rect.x = screen_info[screen].x_org; + s->rect.y = screen_info[screen].y_org; + s->rect.width = screen_info[screen].width; + s->rect.height = screen_info[screen].height; + /* We always treat the screen at 0x0 as the primary screen */ + if (s->rect.x == 0 && s->rect.y == 0) + TAILQ_INSERT_HEAD(&outputs, s, outputs); + else TAILQ_INSERT_TAIL(&outputs, s, outputs); + num_screens++; } - free(reply); + DLOG("found Xinerama screen: %d x %d at %d x %d\n", + screen_info[screen].width, screen_info[screen].height, + screen_info[screen].x_org, screen_info[screen].y_org); + } - if (num_screens == 0) { - ELOG("No screens found. Please fix your setup. i3 will exit now.\n"); - exit(0); - } + free(reply); + + if (num_screens == 0) { + ELOG("No screens found. Please fix your setup. i3 will exit now.\n"); + exit(0); + } } /* @@ -101,28 +91,30 @@ static void query_screens(xcb_connection_t *conn) { * information to setup workspaces for each screen. * */ -void initialize_xinerama(xcb_connection_t *conn) { - if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) { - DLOG("Xinerama extension not found, disabling.\n"); +void xinerama_init() { + if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) { + DLOG("Xinerama extension not found, disabling.\n"); + disable_randr(conn); + } else { + xcb_xinerama_is_active_reply_t *reply; + reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL); + + if (reply == NULL || !reply->state) { + DLOG("Xinerama is not active (in your X-Server), disabling.\n"); disable_randr(conn); - } else { - xcb_xinerama_is_active_reply_t *reply; - reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL); + } else + query_screens(conn); - if (reply == NULL || !reply->state) { - DLOG("Xinerama is not active (in your X-Server), disabling.\n"); - disable_randr(conn); - } else - query_screens(conn); + FREE(reply); + } - FREE(reply); - } - - Output *output; - Workspace *ws; - /* Just go through each active output and associate one workspace */ - TAILQ_FOREACH(output, &outputs, outputs) { - ws = get_first_workspace_for_output(output); - initialize_output(conn, output, ws); - } +#if 0 + Output *output; + Workspace *ws; + /* Just go through each active output and associate one workspace */ + TAILQ_FOREACH(output, &outputs, outputs) { + ws = get_first_workspace_for_output(output); + initialize_output(conn, output, ws); + } +#endif } diff --git a/testcases/Makefile b/testcases/Makefile index 010b595f..1e7a9191 100644 --- a/testcases/Makefile +++ b/testcases/Makefile @@ -1,4 +1,4 @@ test: - PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" t/15*.t + PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" t/16*.t all: test diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t new file mode 100644 index 00000000..2f19b0c2 --- /dev/null +++ b/testcases/t/16-nestedcons.t @@ -0,0 +1,84 @@ +#!perl +# vim:ts=4:sw=4:expandtab + +use Test::More tests => 8; +use Test::Deep; +use List::MoreUtils qw(all none); +use Data::Dumper; +use AnyEvent::I3; + +my $i3 = i3("/tmp/nestedcons"); + +#################### +# Request tree +#################### + +my $tree = $i3->get_workspaces->recv; +# $VAR1 = { +# 'fullscreen_mode' => 0, +# 'nodes' => [ +# { +# 'fullscreen_mode' => 0, +# 'nodes' => [ +# { +# 'fullscreen_mode' => 0, +# 'nodes' => [], +# 'window' => undef, +# 'name' => '1', +# 'orientation' => 0, +# 'type' => 2 +# } +# ], +# 'window' => undef, +# 'name' => 'LVDS1', +# 'orientation' => 0, +# 'type' => 1 +# } +# ], +# 'window' => undef, +# 'name' => 'root', +# 'orientation' => 0, +# 'type' => 0 +# }; + +my $expected = { + fullscreen_mode => 0, + nodes => ignore(), + window => undef, + name => 'root', + orientation => ignore(), + type => 0, + id => ignore(), +}; + +cmp_deeply($tree, $expected, 'root node OK'); + +my @nodes = @{$tree->{nodes}}; + +ok(@nodes > 0, 'root node has at least one leaf'); + +ok((all { $_->{type} == 1 } @nodes), 'all nodes are of type CT_OUTPUT'); +ok((none { defined($_->{window}) } @nodes), 'no CT_OUTPUT contains a window'); +ok((all { @{$_->{nodes}} > 0 } @nodes), 'all nodes have at least one leaf (workspace)'); +my @workspaces; +for my $ws (map { @{$_->{nodes}} } @nodes) { + push @workspaces, $ws; +} + +ok((all { $_->{type} == 2 } @workspaces), 'all workspaces are of type CT_CON'); +ok((all { @{$_->{nodes}} == 0 } @workspaces), 'all workspaces are empty yet'); +ok((none { defined($_->{window}) } @workspaces), 'no CT_OUTPUT contains a window'); + +# TODO: get the focused container + +$i3->command('open')->recv; + +# TODO: get the focused container, check if it changed. +# TODO: get the old focused container, check if there is a new child + +diag(Dumper(\@workspaces)); + +diag(Dumper($tree)); + + +diag( "Testing i3, Perl $], $^X" ); From 6aa6fa0af02575174f8bc7c41a6c0f3422cd3bba Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Apr 2010 16:41:23 +0200 Subject: [PATCH 004/867] correctly focus workspaces --- src/tree.c | 2 +- src/workspace.c | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/tree.c b/src/tree.c index 1d5405fd..22dcf5cc 100644 --- a/src/tree.c +++ b/src/tree.c @@ -275,7 +275,7 @@ void tree_next(char way, orientation_t orientation) { /* 3: focus choice comes in here. at the moment we will go down * until we find a window */ /* TODO: check for window, atm we only go down as far as possible */ - while (TAILQ_FIRST(&(next->focus_head)) != TAILQ_END(&(next->focus_head))) + while (!TAILQ_EMPTY(&(next->focus_head))) next = TAILQ_FIRST(&(next->focus_head)); con_focus(next); diff --git a/src/workspace.c b/src/workspace.c index d65a5198..63e079f9 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -110,7 +110,12 @@ void workspace_show(const char *num) { current->fullscreen_mode = CF_NONE; LOG("switching to %p\n", workspace); - con_focus(workspace); + Con *next = workspace; + + while (!TAILQ_EMPTY(&(next->focus_head))) + next = TAILQ_FIRST(&(next->focus_head)); + + con_focus(next); workspace->fullscreen_mode = CF_OUTPUT; LOG("focused now = %p / %s\n", focused, focused->name); #if 0 From 4206db2839bd98df4f0c13e72efebfd3fde216d9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Apr 2010 16:43:08 +0200 Subject: [PATCH 005/867] workspace.c: update header, reformat --- src/workspace.c | 75 +++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/src/workspace.c b/src/workspace.c index 63e079f9..4d7922a8 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -1,11 +1,8 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * - * © 2009-2010 Michael Stapelberg and contributors - * - * See file LICENSE for license information. + * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) * * workspace.c: Functions for modifying workspaces * @@ -14,8 +11,6 @@ #include "all.h" -extern Con *focused; - /* * Returns a pointer to the workspace with the given number (starting at 0), * creating the workspace if necessary (by allocating the necessary amount of @@ -23,35 +18,35 @@ extern Con *focused; * */ Con *workspace_get(const char *num) { - Con *output, *workspace = NULL, *current; + Con *output, *workspace = NULL, *current; - /* TODO: could that look like this in the future? - GET_MATCHING_NODE(workspace, croot, strcasecmp(current->name, num) != 0); - */ - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - TAILQ_FOREACH(current, &(output->nodes_head), nodes) { - if (strcasecmp(current->name, num) != 0) - continue; + /* TODO: could that look like this in the future? + GET_MATCHING_NODE(workspace, croot, strcasecmp(current->name, num) != 0); + */ + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + TAILQ_FOREACH(current, &(output->nodes_head), nodes) { + if (strcasecmp(current->name, num) != 0) + continue; - workspace = current; - break; - } + workspace = current; + break; } + } - LOG("should switch to ws %s\n", num); - if (workspace == NULL) { - LOG("need to create this one\n"); - output = con_get_output(focused); - LOG("got output %p\n", output); - workspace = con_new(output); - workspace->name = strdup(num); + LOG("should switch to ws %s\n", num); + if (workspace == NULL) { + LOG("need to create this one\n"); + output = con_get_output(focused); + LOG("got output %p\n", output); + workspace = con_new(output); + workspace->name = strdup(num); - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); - } + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); + } - //ewmh_update_workarea(); + //ewmh_update_workarea(); - return workspace; + return workspace; } #if 0 @@ -101,23 +96,23 @@ bool workspace_is_visible(Workspace *ws) { * */ void workspace_show(const char *num) { - Con *workspace, *current; + Con *workspace, *current; - workspace = workspace_get(num); - workspace->fullscreen_mode = CF_OUTPUT; - /* disable fullscreen */ - TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) - current->fullscreen_mode = CF_NONE; + workspace = workspace_get(num); + workspace->fullscreen_mode = CF_OUTPUT; + /* disable fullscreen */ + TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) + current->fullscreen_mode = CF_NONE; - LOG("switching to %p\n", workspace); - Con *next = workspace; + LOG("switching to %p\n", workspace); + Con *next = workspace; while (!TAILQ_EMPTY(&(next->focus_head))) next = TAILQ_FIRST(&(next->focus_head)); - con_focus(next); - workspace->fullscreen_mode = CF_OUTPUT; - LOG("focused now = %p / %s\n", focused, focused->name); + con_focus(next); + workspace->fullscreen_mode = CF_OUTPUT; + LOG("focused now = %p / %s\n", focused, focused->name); #if 0 /* Check if the workspace has not been used yet */ From bcfb0d250514404ca08bc498035fbeca8d18de10 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Apr 2010 16:48:42 +0200 Subject: [PATCH 006/867] more reformatting --- src/manage.c | 262 +++++++++++++++++++++++++-------------------------- src/nc.c | 12 +-- 2 files changed, 133 insertions(+), 141 deletions(-) diff --git a/src/manage.c b/src/manage.c index b56da976..e15e802a 100644 --- a/src/manage.c +++ b/src/manage.c @@ -1,51 +1,45 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager + * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) * - * © 2009-2010 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - * src/manage.c: Contains all functions for initially managing new windows - * (or existing ones on restart). + * manage.c: Contains all functions for initially managing new windows + * (or existing ones on restart). * */ #include "all.h" -extern struct Con *focused; - - /* * Go through all existing windows (if the window manager is restarted) and manage them * */ void manage_existing_windows(xcb_window_t root) { - xcb_query_tree_reply_t *reply; - int i, len; - xcb_window_t *children; - xcb_get_window_attributes_cookie_t *cookies; + xcb_query_tree_reply_t *reply; + int i, len; + xcb_window_t *children; + xcb_get_window_attributes_cookie_t *cookies; - /* Get the tree of windows whose parent is the root window (= all) */ - if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL) - return; + /* Get the tree of windows whose parent is the root window (= all) */ + if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL) + return; - len = xcb_query_tree_children_length(reply); - cookies = smalloc(len * sizeof(*cookies)); + len = xcb_query_tree_children_length(reply); + cookies = smalloc(len * sizeof(*cookies)); - /* Request the window attributes for every window */ - children = xcb_query_tree_children(reply); - for (i = 0; i < len; ++i) - cookies[i] = xcb_get_window_attributes(conn, children[i]); + /* Request the window attributes for every window */ + children = xcb_query_tree_children(reply); + for (i = 0; i < len; ++i) + cookies[i] = xcb_get_window_attributes(conn, children[i]); - /* Call manage_window with the attributes for every window */ - for (i = 0; i < len; ++i) - manage_window(children[i], cookies[i], true); + /* Call manage_window with the attributes for every window */ + for (i = 0; i < len; ++i) + manage_window(children[i], cookies[i], true); - free(reply); - free(cookies); + free(reply); + free(cookies); } /* @@ -57,18 +51,18 @@ void manage_existing_windows(xcb_window_t root) { * */ void restore_geometry() { - LOG("Restoring geometry\n"); + LOG("Restoring geometry\n"); - Con *con; - TAILQ_FOREACH(con, &all_cons, all_cons) - if (con->window) { - printf("placing window at %d %d\n", con->rect.x, con->rect.y); - xcb_reparent_window(conn, con->window->id, root, - con->rect.x, con->rect.y); - } + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) + if (con->window) { + printf("placing window at %d %d\n", con->rect.x, con->rect.y); + xcb_reparent_window(conn, con->window->id, root, + con->rect.x, con->rect.y); + } - /* Make sure our changes reach the X server, we restart/exit now */ - xcb_flush(conn); + /* Make sure our changes reach the X server, we restart/exit now */ + xcb_flush(conn); } /* @@ -77,118 +71,116 @@ void restore_geometry() { */ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cookie, bool needs_to_be_mapped) { - xcb_drawable_t d = { window }; - xcb_get_geometry_cookie_t geomc; - xcb_get_geometry_reply_t *geom; - xcb_get_window_attributes_reply_t *attr = 0; + xcb_drawable_t d = { window }; + xcb_get_geometry_cookie_t geomc; + xcb_get_geometry_reply_t *geom; + xcb_get_window_attributes_reply_t *attr = 0; - printf("---> looking at window 0x%08x\n", window); + printf("---> looking at window 0x%08x\n", window); - xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie, - utf8_title_cookie, title_cookie, - class_cookie, leader_cookie; + xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie, + utf8_title_cookie, title_cookie, + class_cookie, leader_cookie; - wm_type_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX); - strut_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX); - state_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STATE], UINT32_MAX); - utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_NAME], 128); - leader_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[WM_CLIENT_LEADER], UINT32_MAX); - title_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_NAME, 128); - class_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_CLASS, 128); + wm_type_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX); + strut_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX); + state_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STATE], UINT32_MAX); + utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_NAME], 128); + leader_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[WM_CLIENT_LEADER], UINT32_MAX); + title_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_NAME, 128); + class_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_CLASS, 128); - geomc = xcb_get_geometry(conn, d); + geomc = xcb_get_geometry(conn, d); - /* Check if the window is mapped (it could be not mapped when intializing and - calling manage_window() for every window) */ - if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) { - LOG("Could not get attributes\n"); - return; + /* Check if the window is mapped (it could be not mapped when intializing and + calling manage_window() for every window) */ + if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) { + LOG("Could not get attributes\n"); + return; + } + + if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) { + LOG("map_state unviewable\n"); + goto out; + } + + /* Don’t manage clients with the override_redirect flag */ + LOG("override_redirect is %d\n", attr->override_redirect); + if (attr->override_redirect) + goto out; + + /* Check if the window is already managed */ + if (con_by_window_id(window) != NULL) + goto out; + + /* Get the initial geometry (position, size, …) */ + if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL) + goto out; + + LOG("reparenting!\n"); + + i3Window *cwindow = scalloc(sizeof(i3Window)); + cwindow->id = window; + + class_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_CLASS, 128); + xcb_get_property_reply_t *preply; + preply = xcb_get_property_reply(conn, class_cookie, NULL); + if (preply == NULL || xcb_get_property_value_length(preply) == 0) { + LOG("cannot get wm_class\n"); + } else cwindow->class = strdup(xcb_get_property_value(preply)); + + Con *nc; + Match *match; + + /* TODO: assignments */ + /* TODO: two matches for one container */ + /* See if any container swallows this new window */ + nc = con_for_window(cwindow, &match); + if (nc == NULL) { + if (focused->type == CT_CON && con_accepts_window(focused)) { + LOG("using current container, focused = %p, focused->name = %s\n", + focused, focused->name); + nc = focused; + } else nc = tree_open_con(NULL); + } else { + if (match != NULL && match->insert_where == M_ACTIVE) { + /* We need to go down the focus stack starting from nc */ + while (TAILQ_FIRST(&(nc->focus_head)) != TAILQ_END(&(nc->focus_head))) { + printf("walking down one step...\n"); + nc = TAILQ_FIRST(&(nc->focus_head)); + } + /* We need to open a new con */ + /* TODO: make a difference between match-once containers (directly assign + * cwindow) and match-multiple (tree_open_con first) */ + nc = tree_open_con(nc->parent); } + } + nc->window = cwindow; - if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) { - LOG("map_state unviewable\n"); - goto out; - } + xcb_void_cookie_t rcookie = xcb_reparent_window_checked(conn, window, nc->frame, 0, 0); + if (xcb_request_check(conn, rcookie) != NULL) { + LOG("Could not reparent the window, aborting\n"); + goto out; + //xcb_destroy_window(conn, nc->frame); + } - /* Don’t manage clients with the override_redirect flag */ - LOG("override_redirect is %d\n", attr->override_redirect); - if (attr->override_redirect) - goto out; + xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window); - /* Check if the window is already managed */ - if (con_by_window_id(window) != NULL) - goto out; - - /* Get the initial geometry (position, size, …) */ - if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL) - goto out; - - LOG("reparenting!\n"); - - i3Window *cwindow = scalloc(sizeof(i3Window)); - cwindow->id = window; - - class_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_CLASS, 128); - xcb_get_property_reply_t *preply; - preply = xcb_get_property_reply(conn, class_cookie, NULL); - if (preply == NULL || xcb_get_property_value_length(preply) == 0) { - LOG("cannot get wm_class\n"); - } else cwindow->class = strdup(xcb_get_property_value(preply)); - - Con *nc; - Match *match; - - /* TODO: assignments */ - /* TODO: two matches for one container */ - /* See if any container swallows this new window */ - nc = con_for_window(cwindow, &match); - if (nc == NULL) { - if (focused->type == CT_CON && con_accepts_window(focused)) { - LOG("using current container, focused = %p, focused->name = %s\n", - focused, focused->name); - nc = focused; - } else nc = tree_open_con(NULL); - } else { - if (match != NULL && match->insert_where == M_ACTIVE) { - /* We need to go down the focus stack starting from nc */ - while (TAILQ_FIRST(&(nc->focus_head)) != TAILQ_END(&(nc->focus_head))) { - printf("walking down one step...\n"); - nc = TAILQ_FIRST(&(nc->focus_head)); - } - /* We need to open a new con */ - /* TODO: make a difference between match-once containers (directly assign - * cwindow) and match-multiple (tree_open_con first) */ - nc = tree_open_con(nc->parent); - - } - - } - nc->window = cwindow; - - xcb_void_cookie_t rcookie = xcb_reparent_window_checked(conn, window, nc->frame, 0, 0); - if (xcb_request_check(conn, rcookie) != NULL) { - LOG("Could not reparent the window, aborting\n"); - goto out; - //xcb_destroy_window(conn, nc->frame); - } - - xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window); - - tree_render(); + tree_render(); #if 0 - /* Reparent the window and add it to our list of managed windows */ - reparent_window(conn, window, attr->visual, geom->root, geom->depth, - geom->x, geom->y, geom->width, geom->height, - geom->border_width); + /* Reparent the window and add it to our list of managed windows */ + reparent_window(conn, window, attr->visual, geom->root, geom->depth, + geom->x, geom->y, geom->width, geom->height, + geom->border_width); #endif - /* Generate callback events for every property we watch */ - free(geom); + /* Generate callback events for every property we watch */ + free(geom); out: - free(attr); - return; + free(attr); + return; } #if 0 diff --git a/src/nc.c b/src/nc.c index 709cb541..f5843d55 100644 --- a/src/nc.c +++ b/src/nc.c @@ -172,12 +172,12 @@ int main(int argc, char *argv[]) { bool force_xinerama = false; xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS]; static struct option long_options[] = { - {"no-autostart", no_argument, 0, 'a'}, - {"config", required_argument, 0, 'c'}, - {"version", no_argument, 0, 'v'}, - {"help", no_argument, 0, 'h'}, - {"force-xinerama", no_argument, 0, 0}, - {0, 0, 0, 0} + {"no-autostart", no_argument, 0, 'a'}, + {"config", required_argument, 0, 'c'}, + {"version", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {"force-xinerama", no_argument, 0, 0}, + {0, 0, 0, 0} }; int option_index = 0, opt; From fd8735a6fdab6760cce712e7e127b1b2196dd841 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Apr 2010 17:22:34 +0200 Subject: [PATCH 007/867] correctly update/display window title/class --- Makefile | 2 +- include/all.h | 1 + include/data.h | 7 +++- include/handlers.h | 3 +- include/i3.h | 1 + include/window.h | 7 ++++ src/con.c | 14 +++++-- src/handlers.c | 91 +++++++++------------------------------------- src/manage.c | 16 ++++---- src/nc.c | 7 ++++ src/util.c | 4 +- src/window.c | 46 +++++++++++++++++++++++ src/x.c | 10 ++--- 13 files changed, 114 insertions(+), 95 deletions(-) create mode 100644 include/window.h create mode 100644 src/window.c diff --git a/Makefile b/Makefile index 28a78064..810a7863 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c -FILES:=src/ipc.c src/nc.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c +FILES:=src/ipc.c src/nc.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) diff --git a/include/all.h b/include/all.h index a86bd5d2..84e4a2f9 100644 --- a/include/all.h +++ b/include/all.h @@ -48,5 +48,6 @@ #include "con.h" #include "load_layout.h" #include "render.h" +#include "window.h" #endif diff --git a/include/data.h b/include/data.h index c034d115..154c0828 100644 --- a/include/data.h +++ b/include/data.h @@ -247,7 +247,12 @@ struct xoutput { struct Window { xcb_window_t id; - const char *class; + const char *class_class; + const char *class_instance; + const char *name_ucs2; + const char *name_utf8; + int name_len; + bool uses_net_wm_name; }; struct Match { diff --git a/include/handlers.h b/include/handlers.h index b92b59a4..ecfa6a53 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -107,6 +107,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_notify_event_t *event); +#endif /** * Called when a window changes its title * @@ -114,7 +115,7 @@ int handle_destroy_notify_event(void *data, xcb_connection_t *conn, int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop); - +#if 0 /** * We handle legacy window names (titles) which are in COMPOUND_TEXT * encoding. However, we just pass them along, so when containing non-ASCII diff --git a/include/i3.h b/include/i3.h index 562c557d..e437943e 100644 --- a/include/i3.h +++ b/include/i3.h @@ -33,6 +33,7 @@ extern TAILQ_HEAD(autostarts_head, Autostart) autostarts; extern TAILQ_HEAD(assignments_head, Assignment) assignments; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; extern xcb_event_handlers_t evenths; +extern xcb_property_handlers_t prophs; extern uint8_t root_depth; extern bool xkb_supported; extern xcb_atom_t atoms[NUM_ATOMS]; diff --git a/include/window.h b/include/window.h new file mode 100644 index 00000000..d2b3e9dc --- /dev/null +++ b/include/window.h @@ -0,0 +1,7 @@ +#ifndef _WINDOW_H +#define _WINDOW_H + +void window_update_class(i3Window *win, xcb_get_property_reply_t *prop); +void window_update_name(i3Window *win, xcb_get_property_reply_t *prop); + +#endif diff --git a/src/con.c b/src/con.c index 00001d4c..528a0f38 100644 --- a/src/con.c +++ b/src/con.c @@ -184,17 +184,23 @@ Con *con_by_frame_id(xcb_window_t frame) { static bool match_matches_window(Match *match, i3Window *window) { /* TODO: pcre, full matching, … */ - if (match->class != NULL && strcasecmp(match->class, window->class) == 0) { - LOG("match made by window class (%s)\n", window->class); + if (match->class != NULL && strcasecmp(match->class, window->class_class) == 0) { + LOG("match made by window class (%s)\n", window->class_class); return true; } + if (match->instance != NULL && strcasecmp(match->instance, window->class_instance) == 0) { + LOG("match made by window instance (%s)\n", window->class_instance); + return true; + } + + if (match->id != XCB_NONE && window->id == match->id) { LOG("match made by window id (%d)\n", window->id); return true; } - LOG("window %d (%s) could not be matched\n", window->id, window->class); + LOG("window %d (%s) could not be matched\n", window->id, window->class_class); return false; } @@ -208,7 +214,7 @@ Con *con_for_window(i3Window *window, Match **store_match) { Con *con; Match *match; LOG("searching con for window %p\n", window); - LOG("class == %s\n", window->class); + LOG("class == %s\n", window->class_class); TAILQ_FOREACH(con, &all_cons, all_cons) TAILQ_FOREACH(match, &(con->swallow_head), matches) { diff --git a/src/handlers.c b/src/handlers.c index c679da81..2f89afe9 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -2,10 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * - * © 2009-2010 Michael Stapelberg and contributors - * - * See file LICENSE for license information. + * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) * */ #include @@ -568,61 +565,24 @@ int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_ return handle_unmap_notify_event(NULL, conn, &unmap); } - +#endif /* * Called when a window changes its title * */ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) { - if (prop == NULL || xcb_get_property_value_length(prop) == 0) { - DLOG("_NET_WM_NAME not specified, not changing\n"); - return 1; - } - Client *client = table_get(&by_child, window); - if (client == NULL) - return 1; - - /* Save the old pointer to make the update atomic */ - char *new_name; - int new_len; - asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)); - /* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */ - char *ucs2_name = convert_utf8_to_ucs2(new_name, &new_len); - LOG("_NET_WM_NAME changed to \"%s\"\n", new_name); - free(new_name); - - /* Check if they are the same and don’t update if so. - Note the use of new_len * 2 to check all bytes as each glyph takes 2 bytes. - Also note the use of memcmp() instead of strncmp() because the latter stops on nullbytes, - but UCS-2 uses nullbytes to fill up glyphs which only use one byte. */ - if ((new_len == client->name_len) && - (client->name != NULL) && - (memcmp(client->name, ucs2_name, new_len * 2) == 0)) { - free(ucs2_name); - return 1; - } - - char *old_name = client->name; - client->name = ucs2_name; - client->name_len = new_len; - client->uses_net_wm_name = true; - - FREE(old_name); - - /* If the client is a dock window, we don’t need to render anything */ - if (client->dock) - return 1; - - int mode = container_mode(client->container, true); - if (mode == MODE_STACK || mode == MODE_TABBED) - render_container(conn, client->container); - else decorate_window(conn, client, client->frame, client->titlegc, 0, 0); - xcb_flush(conn); - + Con *con; + if ((con = con_by_window_id(window)) == NULL || con->window == NULL) return 1; -} + window_update_name(con->window, prop); + + x_push_changes(croot); + + return 1; +} +#if 0 /* * We handle legacy window names (titles) which are in COMPOUND_TEXT encoding. However, we * just pass them along, so when containing non-ASCII characters, those will be rendering @@ -689,6 +649,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t return 1; } +#endif /* * Updates the client’s WM_CLASS property @@ -696,32 +657,16 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t */ int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) { - if (prop == NULL || xcb_get_property_value_length(prop) == 0) { - DLOG("prop == NULL\n"); - return 1; - } - Client *client = table_get(&by_child, window); - if (client == NULL) - return 1; + Con *con; + if ((con = con_by_window_id(window)) == NULL || con->window == NULL) + return 1; - /* We cannot use asprintf here since this property contains two - * null-terminated strings (for compatibility reasons). Instead, we - * use strdup() on both strings */ - char *new_class = xcb_get_property_value(prop); + window_update_class(con->window, prop); - FREE(client->window_class_instance); - FREE(client->window_class_class); - - client->window_class_instance = strdup(new_class); - if ((strlen(new_class) + 1) < xcb_get_property_value_length(prop)) - client->window_class_class = strdup(new_class + strlen(new_class) + 1); - else client->window_class_class = NULL; - LOG("WM_CLASS changed to %s (instance), %s (class)\n", - client->window_class_instance, client->window_class_class); - - return 0; + return 0; } +#if 0 /* * Expose event means we should redraw our windows (= title bar) * diff --git a/src/manage.c b/src/manage.c index e15e802a..7ffcd61e 100644 --- a/src/manage.c +++ b/src/manage.c @@ -37,7 +37,6 @@ void manage_existing_windows(xcb_window_t root) { for (i = 0; i < len; ++i) manage_window(children[i], cookies[i], true); - free(reply); free(cookies); } @@ -119,16 +118,18 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki goto out; LOG("reparenting!\n"); + uint32_t mask = 0; + uint32_t values[1]; + mask = XCB_CW_EVENT_MASK; + values[0] = CHILD_EVENT_MASK; + xcb_change_window_attributes(conn, window, mask, values); i3Window *cwindow = scalloc(sizeof(i3Window)); cwindow->id = window; - class_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_CLASS, 128); - xcb_get_property_reply_t *preply; - preply = xcb_get_property_reply(conn, class_cookie, NULL); - if (preply == NULL || xcb_get_property_value_length(preply) == 0) { - LOG("cannot get wm_class\n"); - } else cwindow->class = strdup(xcb_get_property_value(preply)); + /* update as much information as possible so far (some replies may be NULL) */ + window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL)); + window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL)); Con *nc; Match *match; @@ -176,7 +177,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki geom->border_width); #endif - /* Generate callback events for every property we watch */ free(geom); out: free(attr); diff --git a/src/nc.c b/src/nc.c index f5843d55..7b2b126a 100644 --- a/src/nc.c +++ b/src/nc.c @@ -14,6 +14,7 @@ char **start_argv; xcb_connection_t *conn; xcb_event_handlers_t evenths; +xcb_property_handlers_t prophs; xcb_atom_t atoms[NUM_ATOMS]; xcb_window_t root; @@ -295,8 +296,11 @@ int main(int argc, char *argv[]) { REQUEST_ATOM(_NET_ACTIVE_WINDOW); REQUEST_ATOM(_NET_WORKAREA); + memset(&evenths, 0, sizeof(xcb_event_handlers_t)); + memset(&prophs, 0, sizeof(xcb_property_handlers_t)); xcb_event_handlers_init(conn, &evenths); + xcb_property_handlers_init(&prophs, &evenths); xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL); xcb_event_set_button_press_handler(&evenths, handle_button_press, NULL); @@ -308,6 +312,7 @@ int main(int argc, char *argv[]) { xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL); + /* Setup NetWM atoms */ #define GET_ATOM(name) \ do { \ @@ -342,6 +347,8 @@ int main(int argc, char *argv[]) { GET_ATOM(_NET_ACTIVE_WINDOW); GET_ATOM(_NET_WORKAREA); + xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL); + keysyms = xcb_key_symbols_alloc(conn); xcb_get_numlock_mask(conn); diff --git a/src/util.c b/src/util.c index e381aa52..24f99d6a 100644 --- a/src/util.c +++ b/src/util.c @@ -21,7 +21,7 @@ #include "all.h" -//static iconv_t conversion_descriptor = 0; +static iconv_t conversion_descriptor = 0; struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent); struct keyvalue_table_head by_child = TAILQ_HEAD_INITIALIZER(by_child); @@ -158,7 +158,6 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes } } -#if 0 /* * Converts the given string to UCS-2 big endian for use with * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen, @@ -202,6 +201,7 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) { return buffer; } +#if 0 /* * Returns the client which comes next in focus stack (= was selected before) for diff --git a/src/window.c b/src/window.c new file mode 100644 index 00000000..ac6f45a0 --- /dev/null +++ b/src/window.c @@ -0,0 +1,46 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * + */ +#include "all.h" + +void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) { + if (prop == NULL || xcb_get_property_value_length(prop) == 0) { + DLOG("empty property, not updating\n"); + return; + } + + /* We cannot use asprintf here since this property contains two + * null-terminated strings (for compatibility reasons). Instead, we + * use strdup() on both strings */ + char *new_class = xcb_get_property_value(prop); + + FREE(win->class_instance); + FREE(win->class_class); + + win->class_instance = strdup(new_class); + if ((strlen(new_class) + 1) < xcb_get_property_value_length(prop)) + win->class_class = strdup(new_class + strlen(new_class) + 1); + else win->class_class = NULL; + LOG("WM_CLASS changed to %s (instance), %s (class)\n", + win->class_instance, win->class_class); +} + +void window_update_name(i3Window *win, xcb_get_property_reply_t *prop) { + if (prop == NULL || xcb_get_property_value_length(prop) == 0) { + DLOG("_NET_WM_NAME not specified, not changing\n"); + return 1; + } + + /* Save the old pointer to make the update atomic */ + int new_len; + asprintf(&win->name_utf8, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)); + /* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */ + win->name_ucs2 = convert_utf8_to_ucs2(win->name_utf8, &win->name_len); + LOG("_NET_WM_NAME changed to \"%s\"\n", win->name_utf8); + + win->uses_net_wm_name = true; +} diff --git a/src/x.c b/src/x.c index 2ed8fd11..ee0bd07d 100644 --- a/src/x.c +++ b/src/x.c @@ -157,23 +157,23 @@ void x_draw_decoration(Con *con) { return; } - if (con->window->class == NULL) { + if (con->window->name_ucs2 == NULL) { LOG("not rendering decoration, not yet known\n"); return; } - LOG("should render text %s onto %p / %s\n", con->window->class, parent, parent->name); + LOG("should render text %s onto %p / %s\n", con->window->name_utf8, parent, parent->name); xcb_change_gc_single(conn, parent->gc, XCB_GC_FOREGROUND, get_colorpixel("#FFFFFF")); - xcb_image_text_8( + xcb_image_text_16( conn, - strlen(con->window->class), + con->window->name_len, parent->frame, parent->gc, con->deco_rect.x, con->deco_rect.y + 14, - con->window->class + (xcb_char2b_t*)con->window->name_ucs2 ); } From dd7acf73e9698717b82ab86ecec7c0d8bd76ff01 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Apr 2010 17:46:54 +0200 Subject: [PATCH 008/867] re-add support for legacy window titles (WM_NAME) --- include/data.h | 18 +++++++++--- include/handlers.h | 13 ++------- include/window.h | 1 + src/handlers.c | 72 +++++++--------------------------------------- src/manage.c | 1 + src/nc.c | 5 ++++ src/window.c | 64 +++++++++++++++++++++++++++++++++++++---- src/x.c | 38 +++++++++++++++--------- 8 files changed, 118 insertions(+), 94 deletions(-) diff --git a/include/data.h b/include/data.h index 154c0828..565dbaac 100644 --- a/include/data.h +++ b/include/data.h @@ -247,11 +247,21 @@ struct xoutput { struct Window { xcb_window_t id; - const char *class_class; - const char *class_instance; - const char *name_ucs2; - const char *name_utf8; + char *class_class; + char *class_instance; + + /** The name of the window as it will be passod to X11 (in UCS2 if the + * application supports _NET_WM_NAME, in COMPOUND_TEXT otherwise). */ + char *name_x; + + /** The name of the window as used in JSON (in UTF-8 if the application + * supports _NET_WM_NAME, in COMPOUND_TEXT otherwise) */ + char *name_json; + + /** The length of the name in glyphs (not bytes) */ int name_len; + + /** Whether the application used _NET_WM_NAME */ bool uses_net_wm_name; }; diff --git a/include/handlers.h b/include/handlers.h index ecfa6a53..f4aaf582 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -115,17 +115,9 @@ int handle_destroy_notify_event(void *data, xcb_connection_t *conn, int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop); -#if 0 /** - * We handle legacy window names (titles) which are in COMPOUND_TEXT - * encoding. However, we just pass them along, so when containing non-ASCII - * characters, those will be rendering incorrectly. In order to correctly - * render unicode window titles in i3, an application has to set _NET_WM_NAME, - * which is in UTF-8 encoding. - * - * On every update, a message is put out to the user, so he may improve the - * situation and update applications which display filenames in their title to - * correctly use _NET_WM_NAME and therefore support unicode. + * Handles legacy window name updates (WM_NAME), see also src/window.c, + * window_update_name_legacy(). * */ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, @@ -133,6 +125,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, xcb_atom_t atom, xcb_get_property_reply_t *prop); +#if 0 /** * Store the window classes for jumping to them later. * diff --git a/include/window.h b/include/window.h index d2b3e9dc..a126a36c 100644 --- a/include/window.h +++ b/include/window.h @@ -3,5 +3,6 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop); void window_update_name(i3Window *win, xcb_get_property_reply_t *prop); +void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop); #endif diff --git a/src/handlers.c b/src/handlers.c index 2f89afe9..b5609540 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -582,74 +582,24 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, return 1; } -#if 0 + /* - * We handle legacy window names (titles) which are in COMPOUND_TEXT encoding. However, we - * just pass them along, so when containing non-ASCII characters, those will be rendering - * incorrectly. In order to correctly render unicode window titles in i3, an application - * has to set _NET_WM_NAME, which is in UTF-8 encoding. - * - * On every update, a message is put out to the user, so he may improve the situation and - * update applications which display filenames in their title to correctly use - * _NET_WM_NAME and therefore support unicode. + * Handles legacy window name updates (WM_NAME), see also src/window.c, + * window_update_name_legacy(). * */ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) { - if (prop == NULL || xcb_get_property_value_length(prop) == 0) { - DLOG("prop == NULL\n"); - return 1; - } - Client *client = table_get(&by_child, window); - if (client == NULL) - return 1; - - /* Client capable of _NET_WM_NAME, ignore legacy name changes */ - if (client->uses_net_wm_name) - return 1; - - /* Save the old pointer to make the update atomic */ - char *new_name; - if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) { - perror("Could not get old name"); - DLOG("Could not get old name\n"); - return 1; - } - /* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */ - LOG("WM_NAME changed to \"%s\"\n", new_name); - - /* Check if they are the same and don’t update if so. */ - if (client->name != NULL && - strlen(new_name) == strlen(client->name) && - strcmp(client->name, new_name) == 0) { - free(new_name); - return 1; - } - - LOG("Using legacy window title. Note that in order to get Unicode window titles in i3, " - "the application has to set _NET_WM_NAME which is in UTF-8 encoding.\n"); - - char *old_name = client->name; - client->name = new_name; - client->name_len = -1; - - if (old_name != NULL) - free(old_name); - - /* If the client is a dock window, we don’t need to render anything */ - if (client->dock) - return 1; - - if (client->container != NULL && - (client->container->mode == MODE_STACK || - client->container->mode == MODE_TABBED)) - render_container(conn, client->container); - else decorate_window(conn, client, client->frame, client->titlegc, 0, 0); - xcb_flush(conn); - + Con *con; + if ((con = con_by_window_id(window)) == NULL || con->window == NULL) return 1; + + window_update_name_legacy(con->window, prop); + + x_push_changes(croot); + + return 1; } -#endif /* * Updates the client’s WM_CLASS property diff --git a/src/manage.c b/src/manage.c index 7ffcd61e..49f10599 100644 --- a/src/manage.c +++ b/src/manage.c @@ -129,6 +129,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* update as much information as possible so far (some replies may be NULL) */ window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL)); + window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL)); window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL)); Con *nc; diff --git a/src/nc.c b/src/nc.c index 7b2b126a..e1b8a409 100644 --- a/src/nc.c +++ b/src/nc.c @@ -347,8 +347,13 @@ int main(int argc, char *argv[]) { GET_ATOM(_NET_ACTIVE_WINDOW); GET_ATOM(_NET_WORKAREA); + /* Watch _NET_WM_NAME (title of the window encoded in UTF-8) */ xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL); + /* Watch WM_NAME (title of the window encoded in COMPOUND_TEXT) */ + xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL); + + keysyms = xcb_key_symbols_alloc(conn); xcb_get_numlock_mask(conn); diff --git a/src/window.c b/src/window.c index ac6f45a0..93812b3f 100644 --- a/src/window.c +++ b/src/window.c @@ -7,6 +7,11 @@ */ #include "all.h" +/* + * Updates the WM_CLASS (consisting of the class and instance) for the + * given window. + * + */ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { DLOG("empty property, not updating\n"); @@ -23,24 +28,71 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) { win->class_instance = strdup(new_class); if ((strlen(new_class) + 1) < xcb_get_property_value_length(prop)) - win->class_class = strdup(new_class + strlen(new_class) + 1); + win->class_class = strdup(new_class + strlen(new_class) + 1); else win->class_class = NULL; LOG("WM_CLASS changed to %s (instance), %s (class)\n", win->class_instance, win->class_class); } +/* + * Updates the name by using _NET_WM_NAME (encoded in UTF-8) for the given + * window. Further updates using window_update_name_legacy will be ignored. + * + */ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { DLOG("_NET_WM_NAME not specified, not changing\n"); - return 1; + return; } /* Save the old pointer to make the update atomic */ - int new_len; - asprintf(&win->name_utf8, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)); + char *new_name; + if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), + (char*)xcb_get_property_value(prop)) == -1) { + perror("asprintf()"); + DLOG("Could not get window name\n"); + } /* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */ - win->name_ucs2 = convert_utf8_to_ucs2(win->name_utf8, &win->name_len); - LOG("_NET_WM_NAME changed to \"%s\"\n", win->name_utf8); + FREE(win->name_x); + FREE(win->name_json); + win->name_json = new_name; + win->name_x = convert_utf8_to_ucs2(win->name_json, &win->name_len); + LOG("_NET_WM_NAME changed to \"%s\"\n", win->name_json); win->uses_net_wm_name = true; } + +/* + * Updates the name by using WM_NAME (encoded in COMPOUND_TEXT). We do not + * touch what the client sends us but pass it to xcb_image_text_8. To get + * proper unicode rendering, the application has to use _NET_WM_NAME (see + * window_update_name()). + * + */ +void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop) { + if (prop == NULL || xcb_get_property_value_length(prop) == 0) { + DLOG("prop == NULL\n"); + return; + } + + /* ignore update when the window is known to already have a UTF-8 name */ + if (win->uses_net_wm_name) + return; + + char *new_name; + if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), + (char*)xcb_get_property_value(prop)) == -1) { + perror("asprintf()"); + DLOG("Could not get legacy window name\n"); + return; + } + + LOG("Using legacy window title. Note that in order to get Unicode window " + "titles in i3, the application has to set _NET_WM_NAME (UTF-8)\n"); + + FREE(win->name_x); + FREE(win->name_json); + win->name_x = new_name; + win->name_json = strdup(new_name); + win->name_len = strlen(new_name); +} diff --git a/src/x.c b/src/x.c index ee0bd07d..347b8f76 100644 --- a/src/x.c +++ b/src/x.c @@ -153,28 +153,40 @@ void x_draw_decoration(Con *con) { xcb_rectangle_t drect = { con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height }; xcb_poly_fill_rectangle(conn, parent->frame, parent->gc, 1, &drect); - if (con->window == NULL) { + if (con->window == NULL) return; - } - if (con->window->name_ucs2 == NULL) { + i3Window *win = con->window; + + if (win->name_x == NULL) { LOG("not rendering decoration, not yet known\n"); return; } - LOG("should render text %s onto %p / %s\n", con->window->name_utf8, parent, parent->name); + LOG("should render text %s onto %p / %s\n", win->name_json, parent, parent->name); xcb_change_gc_single(conn, parent->gc, XCB_GC_FOREGROUND, get_colorpixel("#FFFFFF")); - xcb_image_text_16( - conn, - con->window->name_len, - parent->frame, - parent->gc, - con->deco_rect.x, - con->deco_rect.y + 14, - (xcb_char2b_t*)con->window->name_ucs2 - ); + if (win->uses_net_wm_name) + xcb_image_text_16( + conn, + win->name_len, + parent->frame, + parent->gc, + con->deco_rect.x, + con->deco_rect.y + 14, /* TODO: hardcoded */ + (xcb_char2b_t*)win->name_x + ); + else + xcb_image_text_8( + conn, + win->name_len, + parent->frame, + parent->gc, + con->deco_rect.x, + con->deco_rect.y + 14, /* TODO: hardcoded */ + win->name_x + ); } /* From eec762ea8f184735f5cfb366bb5a77ac410c3499 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Apr 2010 17:52:23 +0200 Subject: [PATCH 009/867] more reformatting/cleanups --- include/data.h | 195 ++++++++++++++++++++----------------------------- include/util.h | 24 ------ src/util.c | 42 ----------- 3 files changed, 80 insertions(+), 181 deletions(-) diff --git a/include/data.h b/include/data.h index 565dbaac..4f7983dc 100644 --- a/include/data.h +++ b/include/data.h @@ -1,11 +1,8 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * - * © 2009-2010 Michael Stapelberg and contributors - * - * See file LICENSE for license information. + * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) * * include/data.h: This file defines all data structures used by i3 * @@ -47,15 +44,15 @@ typedef enum { D_LEFT, D_RIGHT, D_UP, D_DOWN } direction_t; typedef enum { HORIZ, VERT, NO_ORIENTATION } orientation_t; enum { - BIND_NONE = 0, - BIND_SHIFT = XCB_MOD_MASK_SHIFT, /* (1 << 0) */ - BIND_CONTROL = XCB_MOD_MASK_CONTROL, /* (1 << 2) */ - BIND_MOD1 = XCB_MOD_MASK_1, /* (1 << 3) */ - BIND_MOD2 = XCB_MOD_MASK_2, /* (1 << 4) */ - BIND_MOD3 = XCB_MOD_MASK_3, /* (1 << 5) */ - BIND_MOD4 = XCB_MOD_MASK_4, /* (1 << 6) */ - BIND_MOD5 = XCB_MOD_MASK_5, /* (1 << 7) */ - BIND_MODE_SWITCH = (1 << 8) + BIND_NONE = 0, + BIND_SHIFT = XCB_MOD_MASK_SHIFT, /* (1 << 0) */ + BIND_CONTROL = XCB_MOD_MASK_CONTROL, /* (1 << 2) */ + BIND_MOD1 = XCB_MOD_MASK_1, /* (1 << 3) */ + BIND_MOD2 = XCB_MOD_MASK_2, /* (1 << 4) */ + BIND_MOD3 = XCB_MOD_MASK_3, /* (1 << 5) */ + BIND_MOD4 = XCB_MOD_MASK_4, /* (1 << 6) */ + BIND_MOD5 = XCB_MOD_MASK_5, /* (1 << 7) */ + BIND_MODE_SWITCH = (1 << 8) }; /** @@ -71,10 +68,10 @@ enum { * */ struct Rect { - uint32_t x; - uint32_t y; - uint32_t width; - uint32_t height; + uint32_t x; + uint32_t y; + uint32_t width; + uint32_t height; } __attribute__((packed)); /** @@ -82,43 +79,32 @@ struct Rect { * */ struct Colorpixel { - uint32_t pixel; - char *hex; - SLIST_ENTRY(Colorpixel) colorpixels; + uint32_t pixel; + char *hex; + SLIST_ENTRY(Colorpixel) colorpixels; }; struct Cached_Pixmap { - xcb_pixmap_t id; + xcb_pixmap_t id; - /* We’re going to paint on it, so a graphics context will be needed */ - xcb_gcontext_t gc; + /* We’re going to paint on it, so a graphics context will be needed */ + xcb_gcontext_t gc; - /* The rect with which the pixmap was created */ - Rect rect; + /* The rect with which the pixmap was created */ + Rect rect; - /* The rect of the object to which this pixmap belongs. Necessary to - * find out when we need to re-create the pixmap. */ - Rect *referred_rect; + /* The rect of the object to which this pixmap belongs. Necessary to + * find out when we need to re-create the pixmap. */ + Rect *referred_rect; - xcb_drawable_t referred_drawable; + xcb_drawable_t referred_drawable; }; struct Ignore_Event { - int sequence; - time_t added; + int sequence; + time_t added; - SLIST_ENTRY(Ignore_Event) ignore_events; -}; - -/** - * Emulates the behaviour of tables of libxcb-wm, which in libxcb 0.3.4 - * suddenly vanished. - * - */ -struct keyvalue_element { - uint32_t key; - void *value; - TAILQ_ENTRY(keyvalue_element) elements; + SLIST_ENTRY(Ignore_Event) ignore_events; }; /****************************************************************************** @@ -131,30 +117,30 @@ struct keyvalue_element { * */ struct Binding { - /** Symbol the user specified in configfile, if any. This needs to be - * stored with the binding to be able to re-convert it into a keycode - * if the keyboard mapping changes (using Xmodmap for example) */ - char *symbol; + /** Symbol the user specified in configfile, if any. This needs to be + * stored with the binding to be able to re-convert it into a keycode + * if the keyboard mapping changes (using Xmodmap for example) */ + char *symbol; - /** Only in use if symbol != NULL. Gets set to the value to which the - * symbol got translated when binding. Useful for unbinding and - * checking which binding was used when a key press event comes in. - * - * This is an array of number_keycodes size. */ - xcb_keycode_t *translated_to; + /** Only in use if symbol != NULL. Gets set to the value to which the + * symbol got translated when binding. Useful for unbinding and + * checking which binding was used when a key press event comes in. + * + * This is an array of number_keycodes size. */ + xcb_keycode_t *translated_to; - uint32_t number_keycodes; + uint32_t number_keycodes; - /** Keycode to bind */ - uint32_t keycode; + /** Keycode to bind */ + uint32_t keycode; - /** Bitmask consisting of BIND_MOD_1, BIND_MODE_SWITCH, … */ - uint32_t mods; + /** Bitmask consisting of BIND_MOD_1, BIND_MODE_SWITCH, … */ + uint32_t mods; - /** Command, like in command mode */ - char *command; + /** Command, like in command mode */ + char *command; - TAILQ_ENTRY(Binding) bindings; + TAILQ_ENTRY(Binding) bindings; }; /** @@ -162,30 +148,9 @@ struct Binding { * */ struct Autostart { - /** Command, like in command mode */ - char *command; - TAILQ_ENTRY(Autostart) autostarts; -}; - -/** - * Holds an assignment for a given window class/title to a specific workspace - * (see src/config.c) - * - */ -struct Assignment { - char *windowclass_title; - /** floating is true if this was an assignment to the special - * workspace "~". Matching clients will be put into floating mode - * automatically. */ - enum { - ASSIGN_FLOATING_NO, /* don’t float, but put on a workspace */ - ASSIGN_FLOATING_ONLY, /* float, but don’t assign on a workspace */ - ASSIGN_FLOATING /* float and put on a workspace */ - } floating; - - /** The number of the workspace to assign to. */ - int workspace; - TAILQ_ENTRY(Assignment) assignments; + /** Command, like in command mode */ + char *command; + TAILQ_ENTRY(Autostart) autostarts; }; /** @@ -195,16 +160,16 @@ struct Assignment { * */ struct Font { - /** The name of the font, that is what the pattern resolves to */ - char *name; - /** A copy of the pattern to build a cache */ - char *pattern; - /** The height of the font, built from font_ascent + font_descent */ - int height; - /** The xcb-id for the font */ - xcb_font_t id; + /** The name of the font, that is what the pattern resolves to */ + char *name; + /** A copy of the pattern to build a cache */ + char *pattern; + /** The height of the font, built from font_ascent + font_descent */ + int height; + /** The xcb-id for the font */ + xcb_font_t id; - TAILQ_ENTRY(Font) fonts; + TAILQ_ENTRY(Font) fonts; }; @@ -216,32 +181,32 @@ struct Font { * */ struct xoutput { - /** Output id, so that we can requery the output directly later */ - xcb_randr_output_t id; - /** Name of the output */ - char *name; + /** Output id, so that we can requery the output directly later */ + xcb_randr_output_t id; + /** Name of the output */ + char *name; - /** Whether the output is currently active (has a CRTC attached with a - * valid mode) */ - bool active; + /** Whether the output is currently active (has a CRTC attached with a + * valid mode) */ + bool active; - /** Internal flags, necessary for querying RandR screens (happens in - * two stages) */ - bool changed; - bool to_be_disabled; + /** Internal flags, necessary for querying RandR screens (happens in + * two stages) */ + bool changed; + bool to_be_disabled; - /** x, y, width, height */ - Rect rect; + /** x, y, width, height */ + Rect rect; - /** The bar window */ - xcb_window_t bar; - xcb_gcontext_t bargc; + /** The bar window */ + xcb_window_t bar; + xcb_gcontext_t bargc; - /** Contains all clients with _NET_WM_WINDOW_TYPE == - * _NET_WM_WINDOW_TYPE_DOCK */ - SLIST_HEAD(dock_clients_head, Client) dock_clients; + /** Contains all clients with _NET_WM_WINDOW_TYPE == + * _NET_WM_WINDOW_TYPE_DOCK */ + SLIST_HEAD(dock_clients_head, Client) dock_clients; - TAILQ_ENTRY(xoutput) outputs; + TAILQ_ENTRY(xoutput) outputs; }; struct Window { diff --git a/include/util.h b/include/util.h index 937e654b..5d954d39 100644 --- a/include/util.h +++ b/include/util.h @@ -34,10 +34,6 @@ } \ while (0) -TAILQ_HEAD(keyvalue_table_head, keyvalue_element); -extern struct keyvalue_table_head by_parent; -extern struct keyvalue_table_head by_child; - int min(int a, int b); int max(int a, int b); @@ -76,26 +72,6 @@ void *srealloc(void *ptr, size_t size); */ char *sstrdup(const char *str); -/** - * Inserts an element into the given keyvalue-table using the given key. - * - */ -bool table_put(struct keyvalue_table_head *head, uint32_t key, void *value); - -/** - * Removes the element from the given keyvalue-table with the given key and - * returns its value; - * - */ -void *table_remove(struct keyvalue_table_head *head, uint32_t key); - -/** - * Returns the value of the element of the given keyvalue-table with the given - * key. - * - */ -void *table_get(struct keyvalue_table_head *head, uint32_t key); - /** * Starts the given application by passing it through a shell. We use double * fork to avoid zombie processes. As the started application’s parent exits diff --git a/src/util.c b/src/util.c index 24f99d6a..cf8f8df6 100644 --- a/src/util.c +++ b/src/util.c @@ -22,8 +22,6 @@ #include "all.h" static iconv_t conversion_descriptor = 0; -struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent); -struct keyvalue_table_head by_child = TAILQ_HEAD_INITIALIZER(by_child); int min(int a, int b) { return (a < b ? a : b); @@ -73,46 +71,6 @@ char *sstrdup(const char *str) { return result; } -#if 0 - -/* - * The table_* functions emulate the behaviour of libxcb-wm, which in libxcb 0.3.4 suddenly - * vanished. Great. - * - */ -bool table_put(struct keyvalue_table_head *head, uint32_t key, void *value) { - struct keyvalue_element *element = scalloc(sizeof(struct keyvalue_element)); - element->key = key; - element->value = value; - - TAILQ_INSERT_TAIL(head, element, elements); - return true; -} - -void *table_remove(struct keyvalue_table_head *head, uint32_t key) { - struct keyvalue_element *element; - - TAILQ_FOREACH(element, head, elements) - if (element->key == key) { - void *value = element->value; - TAILQ_REMOVE(head, element, elements); - free(element); - return value; - } - - return NULL; -} - -void *table_get(struct keyvalue_table_head *head, uint32_t key) { - struct keyvalue_element *element; - - TAILQ_FOREACH(element, head, elements) - if (element->key == key) - return element->value; - - return NULL; -} -#endif /* * Starts the given application by passing it through a shell. We use double fork * to avoid zombie processes. As the started application’s parent exits (immediately), From 8e5a831e276453ea4cc61d35a9654ff8b8f18c2f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Apr 2010 18:43:37 +0200 Subject: [PATCH 010/867] re-add focus follows mouse handling --- include/handlers.h | 2 +- include/util.h | 1 + src/cfgparse.y | 8 +++ src/config.c | 2 + src/handlers.c | 163 ++++++++++++++++++++++++--------------------- src/nc.c | 7 ++ src/util.c | 7 ++ 7 files changed, 113 insertions(+), 77 deletions(-) diff --git a/include/handlers.h b/include/handlers.h index f4aaf582..a691b35a 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -21,7 +21,6 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event); -#if 0 /** * When the user moves the mouse pointer onto a window, this callback gets * called. @@ -30,6 +29,7 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_event_t *event); +#if 0 /** * When the user moves the mouse but does not change the active window * (e.g. when having no windows opened but moving mouse on the root screen diff --git a/include/util.h b/include/util.h index 5d954d39..cca6985d 100644 --- a/include/util.h +++ b/include/util.h @@ -36,6 +36,7 @@ while (0) int min(int a, int b); int max(int a, int b); +bool rect_contains(Rect rect, uint32_t x, uint32_t y); /** * Updates *destination with new_value and returns true if it was changed or false diff --git a/src/cfgparse.y b/src/cfgparse.y index 19bfaec1..99367adb 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -486,6 +486,7 @@ workspace_name: assign: TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target { +#if 0 printf("assignment of %s\n", $3); struct Assignment *new = $6; @@ -493,29 +494,36 @@ assign: printf(" floating = %d\n", new->floating); new->windowclass_title = $3; TAILQ_INSERT_TAIL(&assignments, new, assignments); +#endif } ; assign_target: NUMBER { +#if 0 struct Assignment *new = scalloc(sizeof(struct Assignment)); new->workspace = $1; new->floating = ASSIGN_FLOATING_NO; $$ = new; +#endif } | '~' { +#if 0 struct Assignment *new = scalloc(sizeof(struct Assignment)); new->floating = ASSIGN_FLOATING_ONLY; $$ = new; +#endif } | '~' NUMBER { +#if 0 struct Assignment *new = scalloc(sizeof(struct Assignment)); new->workspace = $2; new->floating = ASSIGN_FLOATING; $$ = new; +#endif } ; diff --git a/src/config.c b/src/config.c index 60c11fe7..2e611e95 100644 --- a/src/config.c +++ b/src/config.c @@ -305,6 +305,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, SLIST_REMOVE(&modes, mode, Mode, modes); } +#if 0 struct Assignment *assign; while (!TAILQ_EMPTY(&assignments)) { assign = TAILQ_FIRST(&assignments); @@ -312,6 +313,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, TAILQ_REMOVE(&assignments, assign, assignments); FREE(assign); } +#endif /* Clear workspace names */ #if 0 diff --git a/src/handlers.c b/src/handlers.c index b5609540..80f1e669 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -20,12 +20,12 @@ static SLIST_HEAD(ignore_head, Ignore_Event) ignore_events; static void add_ignore_event(const int sequence) { - struct Ignore_Event *event = smalloc(sizeof(struct Ignore_Event)); + struct Ignore_Event *event = smalloc(sizeof(struct Ignore_Event)); - event->sequence = sequence; - event->added = time(NULL); + event->sequence = sequence; + event->added = time(NULL); - SLIST_INSERT_HEAD(&ignore_events, event, ignore_events); + SLIST_INSERT_HEAD(&ignore_events, event, ignore_events); } /* @@ -33,26 +33,27 @@ static void add_ignore_event(const int sequence) { * */ static bool event_is_ignored(const int sequence) { - struct Ignore_Event *event; - time_t now = time(NULL); - for (event = SLIST_FIRST(&ignore_events); event != SLIST_END(&ignore_events);) { - if ((now - event->added) > 5) { - struct Ignore_Event *save = event; - event = SLIST_NEXT(event, ignore_events); - SLIST_REMOVE(&ignore_events, save, Ignore_Event, ignore_events); - free(save); - } else event = SLIST_NEXT(event, ignore_events); - } + struct Ignore_Event *event; + time_t now = time(NULL); + for (event = SLIST_FIRST(&ignore_events); event != SLIST_END(&ignore_events);) { + if ((now - event->added) > 5) { + struct Ignore_Event *save = event; + event = SLIST_NEXT(event, ignore_events); + SLIST_REMOVE(&ignore_events, save, Ignore_Event, ignore_events); + free(save); + } else event = SLIST_NEXT(event, ignore_events); + } - SLIST_FOREACH(event, &ignore_events, ignore_events) { - if (event->sequence == sequence) { - SLIST_REMOVE(&ignore_events, event, Ignore_Event, ignore_events); - free(event); - return true; - } - } + SLIST_FOREACH(event, &ignore_events, ignore_events) { + if (event->sequence != sequence) + continue; - return false; + SLIST_REMOVE(&ignore_events, event, Ignore_Event, ignore_events); + free(event); + return true; + } + + return false; } /* @@ -140,68 +141,78 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) { if (first_client != NULL) set_focus(global_conn, first_client, true); } +#endif /* * When the user moves the mouse pointer onto a window, this callback gets called. * */ -int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_event_t *event) { - DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", event->event, event->mode, event->detail, event->sequence); - if (event->mode != XCB_NOTIFY_MODE_NORMAL) { - DLOG("This was not a normal notify, ignoring\n"); - return 1; - } - /* Some events are not interesting, because they were not generated actively by the - user, but by reconfiguration of windows */ - if (event_is_ignored(event->sequence)) - return 1; - - /* This was either a focus for a client’s parent (= titlebar)… */ - Client *client = table_get(&by_parent, event->event); - /* …or the client itself */ - if (client == NULL) - client = table_get(&by_child, event->event); - - /* Check for stack windows */ - if (client == NULL) { - struct Stack_Window *stack_win; - SLIST_FOREACH(stack_win, &stack_wins, stack_windows) - if (stack_win->window == event->event) { - client = stack_win->container->currently_focused; - break; - } - } - - - /* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */ - if (client == NULL) { - DLOG("Getting screen at %d x %d\n", event->root_x, event->root_y); - check_crossing_screen_boundary(event->root_x, event->root_y); - return 1; - } - - /* Do plausibility checks: This event may be useless for us if it occurs on a window - which is in a stacked container but not the focused one */ - if (client->container != NULL && - client->container->mode == MODE_STACK && - client->container->currently_focused != client) { - DLOG("Plausibility check says: no\n"); - return 1; - } - - if (client->workspace != c_ws && client->workspace->output == c_ws->output) { - /* This can happen when a client gets assigned to a different workspace than - * the current one (see src/mainx.c:reparent_window). Shortly after it was created, - * an enter_notify will follow. */ - DLOG("enter_notify for a client on a different workspace but the same screen, ignoring\n"); - return 1; - } - - if (!config.disable_focus_follows_mouse) - set_focus(conn, client, false); +int handle_enter_notify(void *ignored, xcb_connection_t *conn, + xcb_enter_notify_event_t *event) { + Con *con; + DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", + event->event, event->mode, event->detail, event->sequence); + DLOG("coordinates %x, %x\n", event->event_x, event->event_y); + if (event->mode != XCB_NOTIFY_MODE_NORMAL) { + DLOG("This was not a normal notify, ignoring\n"); return 1; + } + /* Some events are not interesting, because they were not generated + * actively by the user, but by reconfiguration of windows */ + if (event_is_ignored(event->sequence)) + return 1; + + /* Get container by frame or by child window */ + if ((con = con_by_frame_id(event->event)) == NULL) + con = con_by_window_id(event->event); + + /* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */ + if (con == NULL) { + DLOG("Getting screen at %d x %d\n", event->root_x, event->root_y); + //check_crossing_screen_boundary(event->root_x, event->root_y); + return 1; + } + + /* see if the user entered the window on a certain window decoration */ + int layout = con->layout; + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) + if (rect_contains(child->deco_rect, event->event_x, event->event_y)) { + LOG("using child %p / %s instead!\n", child, child->name); + con = child; + break; + } + + /* for stacked/tabbed layout we do not want to change focus when the user + * enters the window at the decoration of any child window. */ + if (layout == L_STACKED || layout == L_TABBED) { + con = TAILQ_FIRST(&(con->parent->focus_head)); + LOG("using focused %p / %s instead\n", con, con->name); + } + +#if 0 + if (client->workspace != c_ws && client->workspace->output == c_ws->output) { + /* This can happen when a client gets assigned to a different workspace than + * the current one (see src/mainx.c:reparent_window). Shortly after it was created, + * an enter_notify will follow. */ + DLOG("enter_notify for a client on a different workspace but the same screen, ignoring\n"); + return 1; + } +#endif + + if (config.disable_focus_follows_mouse) + return 1; + Con *next = con; + while (!TAILQ_EMPTY(&(next->focus_head))) + next = TAILQ_FIRST(&(next->focus_head)); + + con_focus(next); + x_push_changes(croot); + + return 1; } +#if 0 /* * When the user moves the mouse but does not change the active window diff --git a/src/nc.c b/src/nc.c index e1b8a409..68646c87 100644 --- a/src/nc.c +++ b/src/nc.c @@ -139,7 +139,11 @@ void parse_command(const char *command) { tree_next('n', VERT); else if (strncasecmp(command, "workspace ", strlen("workspace ")) == 0) workspace_show(command + strlen("workspace ")); + else if (strcasecmp(command, "stack") == 0) { + focused->layout = L_STACKED; + x_push_changes(croot); + } else if (strcasecmp(command, "move before h") == 0) tree_move('p', HORIZ); else if (strcasecmp(command, "move before v") == 0) @@ -312,6 +316,9 @@ int main(int argc, char *argv[]) { xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL); + /* Enter window = user moved his mouse over the window */ + xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL); + /* Setup NetWM atoms */ #define GET_ATOM(name) \ diff --git a/src/util.c b/src/util.c index cf8f8df6..c8f4ee38 100644 --- a/src/util.c +++ b/src/util.c @@ -31,6 +31,13 @@ int max(int a, int b) { return (a > b ? a : b); } +bool rect_contains(Rect rect, uint32_t x, uint32_t y) { + return (x >= rect.x && + x <= (rect.x + rect.width) && + y >= rect.y && + y <= (rect.y + rect.height)); +} + /* * Updates *destination with new_value and returns true if it was changed or false * if it was the same From 8959c5005f77258613a203f5edb7dcbfc239036c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Apr 2010 19:33:40 +0200 Subject: [PATCH 011/867] cleanups --- include/handlers.h | 12 +-- src/handlers.c | 230 +++++++++++++++++---------------------------- src/nc.c | 52 ---------- 3 files changed, 91 insertions(+), 203 deletions(-) diff --git a/include/handlers.h b/include/handlers.h index a691b35a..c35defe7 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -54,14 +54,14 @@ int handle_mapping_notify(void *ignored, xcb_connection_t *conn, */ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event); - +#endif /** * A new window appeared on the screen (=was mapped), so let’s manage it. * */ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_event_t *event); - +#if 0 /** * Configuration notifies are only handled because we need to set up ignore * for the following enter notify events @@ -87,14 +87,14 @@ int handle_screen_change(void *prophs, xcb_connection_t *conn, */ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event); - +#endif /** * Our window decorations were unmapped. That means, the window will be killed * now, so we better clean up before. * */ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event); - +#if 0 /** * A destroy notify event is sent when the window is not unmapped, but * immediately destroyed (for example when starting a window and immediately @@ -134,14 +134,14 @@ int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop); - +#endif /** * Expose event means we should redraw our windows (= title bar) * */ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *event); - +#if 0 /** * Handle client messages (EWMH) * diff --git a/src/handlers.c b/src/handlers.c index 80f1e669..11cbc37c 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -253,22 +253,23 @@ int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_not return 0; } +#endif /* * A new window appeared on the screen (=was mapped), so let’s manage it. * */ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_event_t *event) { - xcb_get_window_attributes_cookie_t cookie; + xcb_get_window_attributes_cookie_t cookie; - cookie = xcb_get_window_attributes_unchecked(conn, event->window); + cookie = xcb_get_window_attributes_unchecked(conn, event->window); - DLOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence); - add_ignore_event(event->sequence); + DLOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence); + add_ignore_event(event->sequence); - manage_window(prophs, conn, event->window, cookie, false); - return 1; + manage_window(event->window, cookie, false); + return 1; } - +#if 0 /* * Configure requests are received when the application wants to resize windows on their own. * @@ -437,27 +438,30 @@ int handle_screen_change(void *prophs, xcb_connection_t *conn, return 1; } +#endif /* - * Our window decorations were unmapped. That means, the window will be killed now, - * so we better clean up before. + * Our window decorations were unmapped. That means, the window will be killed + * now, so we better clean up before. * */ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event) { - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; - add_ignore_event(event->sequence); + //add_ignore_event(event->sequence); - Client *client = table_get(&by_child, event->window); - /* First, we need to check if the client is awaiting an unmap-request which - was generated by us reparenting the window. In that case, we just ignore it. */ - if (client != NULL && client->awaiting_useless_unmap) { - client->awaiting_useless_unmap = false; - return 1; - } + DLOG("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event); + Con *con = con_by_window_id(event->window); + if (con == NULL) { + LOG("Not a managed window, ignoring\n"); + return 1; + } - DLOG("event->window = %08x, event->event = %08x\n", event->window, event->event); - DLOG("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event); + tree_close(con); + tree_render(); + x_push_changes(croot); + return 1; + +#if 0 if (client == NULL) { DLOG("not a managed window. Ignoring.\n"); @@ -469,52 +473,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti return 0; } +#endif - client = table_remove(&by_child, event->window); - - /* If this was the fullscreen client, we need to unset it */ - if (client->fullscreen) - client->workspace->fullscreen_client = NULL; - - /* Clients without a container are either floating or dock windows */ - if (client->container != NULL) { - Container *con = client->container; - - /* Remove the client from the list of clients */ - client_remove_from_container(conn, client, con, true); - - /* Set focus to the last focused client in this container */ - con->currently_focused = get_last_focused_client(conn, con, NULL); - - /* Only if this is the active container, we need to really change focus */ - if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen)) - set_focus(conn, con->currently_focused, true); - } else if (client_is_floating(client)) { - DLOG("Removing from floating clients\n"); - TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients); - SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); - } - - if (client->dock) { - DLOG("Removing from dock clients\n"); - SLIST_REMOVE(&(client->workspace->output->dock_clients), client, Client, dock_clients); - } - - DLOG("child of 0x%08x.\n", client->frame); - xcb_reparent_window(conn, client->child, root, 0, 0); - - client_unmap(conn, client); - - xcb_destroy_window(conn, client->frame); - xcb_flush(conn); - table_remove(&by_parent, client->frame); - - if (client->container != NULL) { - Workspace *workspace = client->container->workspace; - cleanup_table(conn, workspace); - fix_colrowspan(conn, workspace); - } +#if 0 /* Let’s see how many clients there are left on the workspace to delete it if it’s empty */ bool workspace_empty = SLIST_EMPTY(&(client->workspace->focus_stack)); bool workspace_focused = (c_ws == client->workspace); @@ -533,30 +495,13 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti client->urgent = false; workspace_update_urgent_flag(client->workspace); - FREE(client->window_class_instance); - FREE(client->window_class_class); - FREE(client->name); - free(client); - render_layout(conn); - - /* Ensure the focus is set to the next client in the focus stack or to - * the screen itself (if we do not focus the screen, it can happen that - * the focus is "nowhere" and thus keypress events will not be received - * by i3, thus the user cannot use any hotkeys). */ - if (workspace_focused) { - if (to_focus != NULL) - set_focus(conn, to_focus, true); - else { - DLOG("Restoring focus to root screen\n"); - xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME); - xcb_flush(conn); - } - } +#endif return 1; } +#if 0 /* * A destroy notify event is sent when the window is not unmapped, but * immediately destroyed (for example when starting a window and immediately @@ -627,77 +572,72 @@ int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, return 0; } -#if 0 /* * Expose event means we should redraw our windows (= title bar) * */ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) { - /* event->count is the number of minimum remaining expose events for this window, so we - skip all events but the last one */ - if (event->count != 0) - return 1; - DLOG("window = %08x\n", event->window); + Con *parent, *con; - Client *client = table_get(&by_parent, event->window); - if (client == NULL) { - /* There was no client in the table, so this is probably an expose event for - one of our stack_windows. */ - struct Stack_Window *stack_win; - SLIST_FOREACH(stack_win, &stack_wins, stack_windows) - if (stack_win->window == event->window) { - render_container(conn, stack_win->container); - return 1; - } - - /* …or one of the bars? */ - Output *output; - TAILQ_FOREACH(output, &outputs, outputs) - if (output->bar == event->window) - render_layout(conn); - return 1; - } - - if (client->dock) - return 1; - - if (container_mode(client->container, true) == MODE_DEFAULT) - decorate_window(conn, client, client->frame, client->titlegc, 0, 0); - else { - uint32_t background_color; - if (client->urgent) - background_color = config.client.urgent.background; - /* Distinguish if the window is currently focused… */ - else if (CUR_CELL != NULL && CUR_CELL->currently_focused == client) - background_color = config.client.focused.background; - /* …or if it is the focused window in a not focused container */ - else background_color = config.client.focused_inactive.background; - - /* Set foreground color to current focused color, line width to 2 */ - uint32_t values[] = {background_color, 2}; - xcb_change_gc(conn, client->titlegc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); - - /* Draw the border, the ±1 is for line width = 2 */ - xcb_point_t points[] = {{1, 0}, /* left upper edge */ - {1, client->rect.height-1}, /* left bottom edge */ - {client->rect.width-1, client->rect.height-1}, /* right bottom edge */ - {client->rect.width-1, 0}}; /* right upper edge */ - xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, client->frame, client->titlegc, 4, points); - - /* Draw a black background */ - xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); - if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) { - xcb_rectangle_t crect = {1, 0, client->rect.width - (1 + 1), client->rect.height - 1}; - xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); - } else { - xcb_rectangle_t crect = {2, 0, client->rect.width - (2 + 2), client->rect.height - 2}; - xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); - } - } - xcb_flush(conn); + /* event->count is the number of minimum remaining expose events for this + * window, so we skip all events but the last one */ + if (event->count != 0) return 1; + + DLOG("window = %08x\n", event->window); + + if ((parent = con_by_frame_id(event->window)) == NULL) { + LOG("expose event for unknown window, ignoring\n"); + return 1; + } + + TAILQ_FOREACH(con, &(parent->nodes_head), nodes) { + LOG("expose for con %p / %s\n", con, con->name); + if (con->window) + x_draw_decoration(con); + } + xcb_flush(conn); + + return 1; + +#if 0 + else { + uint32_t background_color; + if (client->urgent) + background_color = config.client.urgent.background; + /* Distinguish if the window is currently focused… */ + else if (CUR_CELL != NULL && CUR_CELL->currently_focused == client) + background_color = config.client.focused.background; + /* …or if it is the focused window in a not focused container */ + else background_color = config.client.focused_inactive.background; + + /* Set foreground color to current focused color, line width to 2 */ + uint32_t values[] = {background_color, 2}; + xcb_change_gc(conn, client->titlegc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); + + /* Draw the border, the ±1 is for line width = 2 */ + xcb_point_t points[] = {{1, 0}, /* left upper edge */ + {1, client->rect.height-1}, /* left bottom edge */ + {client->rect.width-1, client->rect.height-1}, /* right bottom edge */ + {client->rect.width-1, 0}}; /* right upper edge */ + xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, client->frame, client->titlegc, 4, points); + + /* Draw a black background */ + xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); + if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) { + xcb_rectangle_t crect = {1, 0, client->rect.width - (1 + 1), client->rect.height - 1}; + xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); + } else { + xcb_rectangle_t crect = {2, 0, client->rect.width - (2 + 2), client->rect.height - 2}; + xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); + } + } + xcb_flush(conn); + return 1; +#endif } +#if 0 /* * Handle client messages (EWMH) * diff --git a/src/nc.c b/src/nc.c index 68646c87..44a43461 100644 --- a/src/nc.c +++ b/src/nc.c @@ -62,58 +62,6 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { } } -int handle_map_request(void *unused, xcb_connection_t *conn, xcb_map_request_event_t *event) { - xcb_get_window_attributes_cookie_t cookie; - - cookie = xcb_get_window_attributes_unchecked(conn, event->window); - - LOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence); - //add_ignore_event(event->sequence); - - manage_window(event->window, cookie, false); - return 1; -} - - -int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event) { - LOG("unmap event for 0x%08x\n", event->window); - Con *con = con_by_window_id(event->window); - if (con == NULL) { - LOG("Not a managed window, ignoring\n"); - return 1; - } - - tree_close(con); - tree_render(); - return 1; -} - -int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) { - Con *parent, *con; - - /* event->count is the number of minimum remaining expose events for this window, so we - skip all events but the last one */ - if (event->count != 0) - return 1; - LOG("expose-event, window = %08x\n", event->window); - - if ((parent = con_by_frame_id(event->window)) == NULL) { - LOG("expose event for unknown window, ignoring\n"); - return 1; - } - - TAILQ_FOREACH(con, &(parent->nodes_head), nodes) { - LOG("expose for con %p / %s\n", con, con->name); - if (con->window) - x_draw_decoration(con); - } - xcb_flush(conn); - - return 1; -} - - - void parse_command(const char *command) { printf("received command: %s\n", command); From ab03b3bd41f5cabd1e6af6412a404c61511c13a0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Apr 2010 20:16:40 +0200 Subject: [PATCH 012/867] compile with -freorder-blocks-and-partition --- common.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/common.mk b/common.mk index f55264a1..dcba6976 100644 --- a/common.mk +++ b/common.mk @@ -19,6 +19,7 @@ CFLAGS += -Wunused-value CFLAGS += -Iinclude CFLAGS += -I/usr/local/include CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" +CFLAGS += -freorder-blocks-and-partition LDFLAGS += -lm LDFLAGS += -lxcb-event From a3e0ce53a970bdbd0cacef13d4d7bccef27711a0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Apr 2010 20:20:03 +0200 Subject: [PATCH 013/867] add dump-asy.pl, renders the tree with asymptote --- dump-asy.pl | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100755 dump-asy.pl diff --git a/dump-asy.pl b/dump-asy.pl new file mode 100755 index 00000000..343a39c4 --- /dev/null +++ b/dump-asy.pl @@ -0,0 +1,45 @@ +#!/usr/bin/env perl +# vim:ts=4:sw=4:expandtab +# renders the layout tree using asymptote + +use strict; +use warnings; +use Data::Dumper; +use AnyEvent::I3; +use File::Temp; +use v5.10; + +my $i3 = i3("/tmp/nestedcons"); + +my $tree = $i3->get_workspaces->recv; + +my $tmp = File::Temp->new(UNLINK => 0, SUFFIX => '.asy'); + +say $tmp "import drawtree;"; + +say $tmp "treeLevelStep = 2cm;"; + +sub dump_node { + my ($n, $parent) = @_; + + my $o = ($n->{orientation} == 0 ? "h" : "v"); + my $w = (defined($n->{window}) ? $n->{window} : "N"); + my $na = $n->{name}; + $na =~ s/#/\\#/g; + my $name = "($na, $o, $w)"; + + print $tmp "TreeNode n" . $n->{id} . " = makeNode("; + + print $tmp "n" . $parent->{id} . ", " if defined($parent); + print $tmp "\"" . $name . "\");\n"; + + dump_node($_, $n) for @{$n->{nodes}}; +} + +dump_node($tree); +say $tmp "draw(n" . $tree->{id} . ", (0, 0));"; + +close($tmp); +my $rep = "$tmp"; +$rep =~ s/asy$/eps/; +system("cd /tmp && asy $tmp && gv $rep && rm $rep"); From 24725cd94a5350e561ea7bc8e75f703542744836 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Apr 2010 20:51:43 +0200 Subject: [PATCH 014/867] re-add fullscreen mode --- include/handlers.h | 3 +++ src/con.c | 49 +++++++++++++++++++++++++++++++++------------- src/handlers.c | 9 ++++++--- src/nc.c | 7 +++++-- src/render.c | 23 +++++++++++----------- src/xcb.c | 5 ++++- 6 files changed, 65 insertions(+), 31 deletions(-) diff --git a/include/handlers.h b/include/handlers.h index c35defe7..f5ea0591 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -13,6 +13,9 @@ #include + +void add_ignore_event(const int sequence); + /** * There was a key press. We compare this key code with our bindings table and * pass the bound action to parse_command(). diff --git a/src/con.c b/src/con.c index 528a0f38..75440973 100644 --- a/src/con.c +++ b/src/con.c @@ -123,34 +123,55 @@ Con *con_get_workspace(Con *con) { return result; } +/* + * helper data structure for the breadth-first-search in + * con_get_fullscreen_con() + * + */ +struct bfs_entry { + Con *con; + + TAILQ_ENTRY(bfs_entry) entries; +}; + /* * Returns the first fullscreen node below this node. * */ Con *con_get_fullscreen_con(Con *con) { - Con *current; + Con *current, *child; LOG("looking for fullscreen node\n"); /* TODO: is breadth-first-search really appropriate? (check as soon as * fullscreen levels and fullscreen for containers is implemented) */ - Con **queue = NULL; - int queue_len = 0; - TAILQ_FOREACH(current, &(con->nodes_head), nodes) { - queue_len++; - queue = srealloc(queue, queue_len * sizeof(Con*)); - queue[queue_len-1] = current; - } + TAILQ_HEAD(bfs_head, bfs_entry) bfs_head = TAILQ_HEAD_INITIALIZER(bfs_head); + struct bfs_entry *entry = smalloc(sizeof(struct bfs_entry)); + entry->con = con; + TAILQ_INSERT_TAIL(&bfs_head, entry, entries); - while (queue_len > 0) { - current = queue[queue_len-1]; + while (!TAILQ_EMPTY(&bfs_head)) { + entry = TAILQ_FIRST(&bfs_head); + current = entry->con; LOG("checking %p\n", current); - if (current->fullscreen_mode != CF_NONE) { - free(queue); + if (current != con && current->fullscreen_mode != CF_NONE) { + /* empty the queue */ + while (!TAILQ_EMPTY(&bfs_head)) { + entry = TAILQ_FIRST(&bfs_head); + TAILQ_REMOVE(&bfs_head, entry, entries); + free(entry); + } return current; } + LOG("deleting from queue\n"); - queue_len--; - queue = realloc(queue, queue_len * sizeof(Con*)); + TAILQ_REMOVE(&bfs_head, entry, entries); + free(entry); + + TAILQ_FOREACH(child, &(current->nodes_head), nodes) { + entry = smalloc(sizeof(struct bfs_entry)); + entry->con = child; + TAILQ_INSERT_TAIL(&bfs_head, entry, entries); + } } return NULL; diff --git a/src/handlers.c b/src/handlers.c index 11cbc37c..4d3e88b9 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -19,7 +19,7 @@ changing workspaces */ static SLIST_HEAD(ignore_head, Ignore_Event) ignore_events; -static void add_ignore_event(const int sequence) { +void add_ignore_event(const int sequence) { struct Ignore_Event *event = smalloc(sizeof(struct Ignore_Event)); event->sequence = sequence; @@ -48,8 +48,11 @@ static bool event_is_ignored(const int sequence) { if (event->sequence != sequence) continue; - SLIST_REMOVE(&ignore_events, event, Ignore_Event, ignore_events); - free(event); + /* instead of removing a sequence number we better wait until it gets + * garbage collected. it may generate multiple events (there are multiple + * enter_notifies for one configure_request, for example). */ + //SLIST_REMOVE(&ignore_events, event, Ignore_Event, ignore_events); + //free(event); return true; } diff --git a/src/nc.c b/src/nc.c index 44a43461..2b523ee5 100644 --- a/src/nc.c +++ b/src/nc.c @@ -89,8 +89,11 @@ void parse_command(const char *command) { workspace_show(command + strlen("workspace ")); else if (strcasecmp(command, "stack") == 0) { focused->layout = L_STACKED; - x_push_changes(croot); - + } + else if (strcasecmp(command, "fullscreen") == 0) { + if (focused->fullscreen_mode == CF_NONE) + focused->fullscreen_mode = CF_OUTPUT; + else focused->fullscreen_mode = CF_NONE; } else if (strcasecmp(command, "move before h") == 0) tree_move('p', HORIZ); diff --git a/src/render.c b/src/render.c index b2932f50..c0dbdd5f 100644 --- a/src/render.c +++ b/src/render.c @@ -37,6 +37,18 @@ void render_con(Con *con) { printf("mapped = true\n"); con->mapped = true; + /* if this container contains a window, set the coordinates */ + if (con->window) { + /* depending on the border style, the rect of the child window + * needs to be smaller */ + Rect *inset = &(con->window_rect); + *inset = (Rect){0, 0, con->rect.width, con->rect.height}; + /* TODO: different border styles */ + inset->x += 2; + inset->width -= 2 * 2; + inset->height -= 2; + } + /* Check for fullscreen nodes */ Con *fullscreen = con_get_fullscreen_con(con); if (fullscreen) { @@ -46,7 +58,6 @@ void render_con(Con *con) { return; } - TAILQ_FOREACH(child, &(con->nodes_head), nodes) { /* default layout */ @@ -105,16 +116,6 @@ void render_con(Con *con) { printf("child at (%d, %d) with (%d x %d)\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height); printf("x now %d, y now %d\n", x, y); - if (child->window) { - /* depending on the border style, the rect of the child window - * needs to be smaller */ - Rect *inset = &(child->window_rect); - *inset = (Rect){0, 0, child->rect.width, child->rect.height}; - /* TODO: different border styles */ - inset->x += 2; - inset->width -= 2 * 2; - inset->height -= 2; - } x_raise_con(child); render_con(child); i++; diff --git a/src/xcb.c b/src/xcb.c index 1fd677b7..a57bad04 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -318,10 +318,13 @@ int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *t * */ void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r) { - xcb_configure_window(conn, window, + xcb_void_cookie_t cookie; + cookie = xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, &(r.x)); + /* ignore events which are generated because we configured a window */ + add_ignore_event(cookie.sequence); } From 98bce1d8b0dabefe0f4058f3ad9514717efed863 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Apr 2010 21:05:04 +0200 Subject: [PATCH 015/867] userguide: add missing $ (Thanks artoj) --- docs/userguide | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index c706c631..2d6e50f1 100644 --- a/docs/userguide +++ b/docs/userguide @@ -347,7 +347,7 @@ variables can be handy. *Syntax*: -------------- -set name value +set $name value -------------- *Examples*: From 769501420ddeee0f808b3fd01b0463f426e9ef43 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 14 Apr 2010 20:26:56 +0200 Subject: [PATCH 016/867] add first version of a new flex/bison based command parser --- Makefile | 16 ++- include/con.h | 1 + src/cmdparse.l | 112 ++++++++++++++++++++ src/cmdparse.y | 274 +++++++++++++++++++++++++++++++++++++++++++++++++ src/con.c | 2 +- src/nc.c | 13 ++- 6 files changed, 413 insertions(+), 5 deletions(-) create mode 100644 src/cmdparse.l create mode 100644 src/cmdparse.y diff --git a/Makefile b/Makefile index 810a7863..368dc777 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ TOPDIR=$(shell pwd) include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files -AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c +AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c FILES:=src/ipc.c src/nc.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) @@ -23,7 +23,7 @@ src/%.o: src/%.c ${HEADERS} echo "CC $<" $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/$(shell basename $< .c)/ { print NR }' loglevels.tmp))" -c -o $@ $< -all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES} +all: src/cfgparse.y.o src/cfgparse.yy.o src/cmdparse.y.o src/cmdparse.yy.o ${FILES} echo "LINK i3" $(CC) -o i3 $^ $(LDFLAGS) @@ -44,11 +44,23 @@ src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS} flex -i -o$(@:.o=.c) $< $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) +src/cmdparse.yy.o: src/cmdparse.l src/cmdparse.y.o ${HEADERS} + echo "LEX $<" + flex -P cmdyy -i -o$(@:.o=.c) $< + $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) + + src/cfgparse.y.o: src/cfgparse.y ${HEADERS} echo "YACC $<" bison --debug --verbose -b $(basename $< .y) -d $< $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) +src/cmdparse.y.o: src/cmdparse.y ${HEADERS} + echo "YACC $<" + bison -p cmdyy --debug --verbose -b $(basename $< .y) -d $< + $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) + + install: all echo "INSTALL" $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin diff --git a/include/con.h b/include/con.h index 9b14d897..71f73e3d 100644 --- a/include/con.h +++ b/include/con.h @@ -13,6 +13,7 @@ Con *con_by_frame_id(xcb_window_t frame); Con *con_for_window(i3Window *window, Match **store_match); void con_attach(Con *con, Con *parent); void con_detach(Con *con); +bool match_matches_window(Match *match, i3Window *window); enum { WINDOW_ADD = 0, WINDOW_REMOVE = 1 }; void con_fix_percent(Con *con, int action); diff --git a/src/cmdparse.l b/src/cmdparse.l new file mode 100644 index 00000000..684a588c --- /dev/null +++ b/src/cmdparse.l @@ -0,0 +1,112 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * + * cmdparse.l: the lexer for commands you send to i3 (or bind on keys) + * + */ +%option nounput +%option noinput +%option noyy_top_state +%option stack + +%{ +#include +#include +#include "cmdparse.tab.h" + +#include "config.h" +#include "util.h" + +int cmdyycolumn = 1; + +#define YY_DECL int yylex (struct context *context) + +#define YY_USER_ACTION { \ + context->first_column = cmdyycolumn; \ + context->last_column = cmdyycolumn+yyleng-1; \ + cmdyycolumn += yyleng; \ +} + +%} + +EOL (\r?\n) + +/* handle everything up to \n as a string */ +%s WANT_STRING +/* first expect a whitespace, then a string */ +%s WANT_WS_STRING +/* handle a quoted string or everything up to the next whitespace */ +%s WANT_QSTRING + +%x BUFFER_LINE + +%% + + { + /* This is called when a new line is lexed. We only want the + * first line to match to go into state BUFFER_LINE */ + if (context->line_number == 0) { + context->line_number = 1; + BEGIN(INITIAL); + yy_push_state(BUFFER_LINE); + } + } + +^[^\r\n]*/{EOL}? { + /* save whole line */ + context->line_copy = sstrdup(yytext); + + yyless(0); + yy_pop_state(); + yy_set_bol(true); + cmdyycolumn = 1; +} + +[^\n]+ { BEGIN(INITIAL); cmdyylval.string = sstrdup(yytext); return STR; } +[ \t]* { BEGIN(WANT_STRING); return WHITESPACE; } +\"[^\"]+\" { + BEGIN(INITIAL); + /* strip quotes */ + char *copy = sstrdup(yytext+1); + copy[strlen(copy)-1] = '\0'; + cmdyylval.string = copy; + return STR; + } + +[ \t]* { return WHITESPACE; } +attach { return TOK_ATTACH; } +exec { BEGIN(WANT_WS_STRING); return TOK_EXEC; } +exit { return TOK_EXIT; } +reload { return TOK_RELOAD; } +restart { return TOK_RESTART; } +kill { return TOK_KILL; } +fullscreen { return TOK_FULLSCREEN; } +global { return TOK_GLOBAL; } +layout { return TOK_LAYOUT; } +default { return TOK_DEFAULT; } +stacked { return TOK_STACKED; } +tabbed { return TOK_TABBED; } +border { return TOK_BORDER; } +none { return TOK_NONE; } +1pixel { return TOK_1PIXEL; } +mode { return TOK_MODE; } +tiling { return TOK_TILING; } +floating { return TOK_FLOATING; } +workspace { return TOK_WORKSPACE; } +focus { return TOK_FOCUS; } +move { return TOK_MOVE; } + +class { BEGIN(WANT_QSTRING); return TOK_CLASS; } + +. { return (int)yytext[0]; } + +<> { + while (yy_start_stack_ptr > 0) + yy_pop_state(); + yyterminate(); +} + +%% diff --git a/src/cmdparse.y b/src/cmdparse.y new file mode 100644 index 00000000..eb5ab71f --- /dev/null +++ b/src/cmdparse.y @@ -0,0 +1,274 @@ +%{ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * + * cmdparse.y: the parser for commands you send to i3 (or bind on keys) + * + + */ +#include +#include +#include +#include + +#include "all.h" + +typedef struct yy_buffer_state *YY_BUFFER_STATE; +extern int cmdyylex(struct context *context); +extern int cmdyyparse(void); +extern FILE *cmdyyin; +YY_BUFFER_STATE cmdyy_scan_string(const char *); + +static struct bindings_head *current_bindings; +static struct context *context; +static Match current_match; + +/* + * Helper data structure for an operation window (window on which the operation + * will be performed). Used to build the TAILQ owindows. + * + */ +typedef struct owindow { + Con *con; + TAILQ_ENTRY(owindow) owindows; +} owindow; +static TAILQ_HEAD(owindows_head, owindow) owindows; + +/* We don’t need yydebug for now, as we got decent error messages using + * yyerror(). Should you ever want to extend the parser, it might be handy + * to just comment it in again, so it stays here. */ +//int cmdyydebug = 1; + +void cmdyyerror(const char *error_message) { + ELOG("\n"); + ELOG("CMD: %s\n", error_message); + ELOG("CMD: in file \"%s\", line %d:\n", + context->filename, context->line_number); + ELOG("CMD: %s\n", context->line_copy); + ELOG("CMD: "); + for (int c = 1; c <= context->last_column; c++) + if (c >= context->first_column) + printf("^"); + else printf(" "); + printf("\n"); + ELOG("\n"); +} + +int cmdyywrap() { + return 1; +} + +void parse_cmd(const char *new) { + + //const char *new = "[level-up workspace] attach $output, focus"; + + cmdyy_scan_string(new); + + context = scalloc(sizeof(struct context)); + context->filename = "cmd"; + if (cmdyyparse() != 0) { + fprintf(stderr, "Could not parse configfile\n"); + exit(1); + } + printf("done\n"); + + FREE(context->line_copy); + free(context); +} + +%} + +%error-verbose +%lex-param { struct context *context } + +%union { + char *string; +} + +%token TOK_ATTACH "attach" +%token TOK_EXEC "exec" +%token TOK_EXIT "exit" +%token TOK_RELOAD "reload" +%token TOK_RESTART "restart" +%token TOK_KILL "kill" +%token TOK_FULLSCREEN "fullscreen" +%token TOK_GLOBAL "global" +%token TOK_LAYOUT "layout" +%token TOK_DEFAULT "default" +%token TOK_STACKED "stacked" +%token TOK_TABBED "tabbed" +%token TOK_BORDER "border" +%token TOK_NONE "none" +%token TOK_1PIXEL "1pixel" +%token TOK_MODE "mode" +%token TOK_TILING "tiling" +%token TOK_FLOATING "floating" +%token TOK_WORKSPACE "workspace" +%token TOK_FOCUS "focus" +%token TOK_MOVE "move" + +%token TOK_CLASS "class" + +%token WHITESPACE "" +%token STR "" + +%% + +commands: /* empty */ + | commands optwhitespace ';' optwhitespace command + | command + { + owindow *current; + + printf("single command completely parsed, dropping state...\n"); + while (!TAILQ_EMPTY(&owindows)) { + current = TAILQ_FIRST(&owindows); + TAILQ_REMOVE(&owindows, current, owindows); + free(current); + } + } + ; + +optwhitespace: + | WHITESPACE + ; + +command: + match optwhitespace operations + ; + +match: + | matchstart optwhitespace criteria optwhitespace matchend + { + printf("match parsed\n"); + } + ; + +matchstart: + '[' + { + printf("start\n"); + memset(¤t_match, '\0', sizeof(Match)); + TAILQ_INIT(&owindows); + /* copy all_cons */ + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) { + if (con->window == NULL) + continue; + + owindow *ow = smalloc(sizeof(owindow)); + ow->con = con; + TAILQ_INSERT_TAIL(&owindows, ow, owindows); + } + } + ; + +matchend: + ']' + { + owindow *next, *current; + + printf("match specification finished, matching...\n"); + /* copy the old list head to iterate through it and start with a fresh + * list which will contain only matching windows */ + struct owindows_head old = owindows; + TAILQ_INIT(&owindows); + for (next = TAILQ_FIRST(&old); next != TAILQ_END(&old);) { + /* make a copy of the next pointer and advance the pointer to the + * next element as we are going to invalidate the element’s + * next/prev pointers by calling TAILQ_INSERT_TAIL later */ + current = next; + next = TAILQ_NEXT(next, owindows); + + printf("checking if con %p / %s matches\n", current->con, current->con->name); + if (match_matches_window(¤t_match, current->con->window)) { + printf("matches!\n"); + TAILQ_INSERT_TAIL(&owindows, current, owindows); + } else { + printf("doesnt match\n"); + free(current); + } + } + + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + } + + } + ; + +criteria: + TOK_CLASS '=' STR + { + printf("criteria: class = %s\n", $3); + current_match.class = $3; + } + ; + +operations: + operation + | operation optwhitespace + | operations ',' optwhitespace operation + ; + +operation: + exec + | exit + /*| reload + | restart + | mark + | fullscreen + | layout + | border + | mode + | workspace + | move*/ + | attach + | focus + | kill + ; + +exec: + TOK_EXEC WHITESPACE STR + { + printf("should execute %s\n", $3); + } + ; + +exit: + TOK_EXIT + { + printf("exit, bye bye\n"); + } + ; + +attach: + TOK_ATTACH + { + printf("should attach\n"); + } + ; + +focus: + TOK_FOCUS + { + printf("should focus\n"); + } + ; + +kill: + TOK_KILL + { + owindow *current; + + printf("killing!\n"); + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + tree_close(current->con); + } + + } + ; diff --git a/src/con.c b/src/con.c index 75440973..5c220458 100644 --- a/src/con.c +++ b/src/con.c @@ -203,7 +203,7 @@ Con *con_by_frame_id(xcb_window_t frame) { return NULL; } -static bool match_matches_window(Match *match, i3Window *window) { +bool match_matches_window(Match *match, i3Window *window) { /* TODO: pcre, full matching, … */ if (match->class != NULL && strcasecmp(match->class, window->class_class) == 0) { LOG("match made by window class (%s)\n", window->class_class); diff --git a/src/nc.c b/src/nc.c index 2b523ee5..92dba08d 100644 --- a/src/nc.c +++ b/src/nc.c @@ -109,8 +109,16 @@ void parse_command(const char *command) { start_application(command + strlen("exec ")); else if (strcasecmp(command, "restart") == 0) i3_restart(); - else if (strcasecmp(command, "floating") == 0) - toggle_floating_mode(focused, false); + else if (strcasecmp(command, "floating") == 0) { + //toggle_floating_mode(focused, false); + parse_cmd("exit"); + parse_cmd("exec /usr/bin/bleh"); + parse_cmd("exec kill -9 33"); + parse_cmd("kill"); + parse_cmd("[ class=\"Xpdf\" ] kill"); + parse_cmd("[ class=\"firefox\" ] kill"); + + } tree_render(); @@ -121,6 +129,7 @@ void parse_command(const char *command) { } int main(int argc, char *argv[]) { + //parse_cmd("[ foo ] attach, attach ; focus"); int screens; char *override_configpath = NULL; bool autostart = true; From 9b737f631d1cf71b131a093ed7e4d3cbc033c1e2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 16 Apr 2010 14:24:29 +0200 Subject: [PATCH 017/867] add testcase for changing workspaces --- testcases/t/17-workspace.t | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 testcases/t/17-workspace.t diff --git a/testcases/t/17-workspace.t b/testcases/t/17-workspace.t new file mode 100644 index 00000000..403f3661 --- /dev/null +++ b/testcases/t/17-workspace.t @@ -0,0 +1,48 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Tests whether we can switch to a non-existant workspace +# (necessary for further tests) +# +use Test::More tests => 2; +use List::MoreUtils qw(all none); +use Data::Dumper; +use AnyEvent::I3; +use File::Temp qw(tmpnam); +use v5.10; + +my $i3 = i3("/tmp/nestedcons"); + +sub get_workspace_names { + my $tree = $i3->get_workspaces->recv; + my @workspaces = map { @{$_->{nodes}} } @{$tree->{nodes}}; + [ map { $_->{name} } @workspaces ] +} + +sub workspace_exists { + my ($name) = @_; + ($name ~~ @{get_workspace_names()}) +} + +sub get_unused_workspace { + my @names = get_workspace_names(); + my $tmp; + do { $tmp = tmpnam() } while ($tmp ~~ @names); + $tmp +} + +my $tmp = get_unused_workspace(); +diag("Temporary workspace name: $tmp\n"); + +$i3->command("workspace $tmp")->recv; +ok(workspace_exists($tmp), 'workspace created'); + +my $otmp = get_unused_workspace(); +diag("Other temporary workspace name: $otmp\n"); + +# As the old workspace was empty, it should get +# cleaned up as we switch away from it +$i3->command("workspace $otmp")->recv; +ok(!workspace_exists($tmp), 'old workspace cleaned up'); + +diag( "Testing i3, Perl $], $^X" ); From d94bef2ebb87f7a8fb64c8105e00ad4e0b8f7a31 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 16 Apr 2010 14:34:12 +0200 Subject: [PATCH 018/867] retab! --- testcases/t/17-workspace.t | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/testcases/t/17-workspace.t b/testcases/t/17-workspace.t index 403f3661..1c0c7e28 100644 --- a/testcases/t/17-workspace.t +++ b/testcases/t/17-workspace.t @@ -14,21 +14,21 @@ use v5.10; my $i3 = i3("/tmp/nestedcons"); sub get_workspace_names { - my $tree = $i3->get_workspaces->recv; - my @workspaces = map { @{$_->{nodes}} } @{$tree->{nodes}}; - [ map { $_->{name} } @workspaces ] + my $tree = $i3->get_workspaces->recv; + my @workspaces = map { @{$_->{nodes}} } @{$tree->{nodes}}; + [ map { $_->{name} } @workspaces ] } sub workspace_exists { - my ($name) = @_; - ($name ~~ @{get_workspace_names()}) + my ($name) = @_; + ($name ~~ @{get_workspace_names()}) } sub get_unused_workspace { - my @names = get_workspace_names(); - my $tmp; - do { $tmp = tmpnam() } while ($tmp ~~ @names); - $tmp + my @names = get_workspace_names(); + my $tmp; + do { $tmp = tmpnam() } while ($tmp ~~ @names); + $tmp } my $tmp = get_unused_workspace(); From 64d34d7e706fe663dd4aab415466410e79128059 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 16 Apr 2010 14:52:31 +0200 Subject: [PATCH 019/867] add testcase for opening/killing containers --- testcases/t/18-openkill.t | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 testcases/t/18-openkill.t diff --git a/testcases/t/18-openkill.t b/testcases/t/18-openkill.t new file mode 100644 index 00000000..2763d3c2 --- /dev/null +++ b/testcases/t/18-openkill.t @@ -0,0 +1,48 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Tests whether opening an empty container and killing it again works +# +use Test::More tests => 3; +use Data::Dumper; +use AnyEvent::I3; +use File::Temp qw(tmpnam); +use v5.10; + +my $i3 = i3("/tmp/nestedcons"); + +sub get_workspace_names { + my $tree = $i3->get_workspaces->recv; + my @workspaces = map { @{$_->{nodes}} } @{$tree->{nodes}}; + [ map { $_->{name} } @workspaces ] +} + +sub get_unused_workspace { + my @names = get_workspace_names(); + my $tmp; + do { $tmp = tmpnam() } while ($tmp ~~ @names); + $tmp +} + +sub get_ws_content { + my ($name) = @_; + my $tree = $i3->get_workspaces->recv; + my @ws = map { @{$_->{nodes}} } @{$tree->{nodes}}; + my @cons = map { $_->{nodes} } grep { $_->{name} eq $name } @ws; + return $cons[0]; +} + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + +# Open a new container +$i3->command("open")->recv; + +ok(@{get_ws_content($tmp)} == 1, 'container opened'); + +$i3->command("kill")->recv; +ok(@{get_ws_content($tmp)} == 0, 'container killed'); + +diag( "Testing i3, Perl $], $^X" ); From caa1e9a962d90188e9588caa99c993821701e21c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 16 Apr 2010 15:03:27 +0200 Subject: [PATCH 020/867] move common functions to i3test, export them, bail out if workspace creation fails --- testcases/t/17-workspace.t | 24 ++++++++---------------- testcases/t/18-openkill.t | 26 +++----------------------- testcases/t/lib/i3test.pm | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 39 deletions(-) diff --git a/testcases/t/17-workspace.t b/testcases/t/17-workspace.t index 1c0c7e28..227b754d 100644 --- a/testcases/t/17-workspace.t +++ b/testcases/t/17-workspace.t @@ -5,37 +5,29 @@ # (necessary for further tests) # use Test::More tests => 2; -use List::MoreUtils qw(all none); -use Data::Dumper; +use FindBin; +use lib "$FindBin::Bin/lib"; +use i3test; use AnyEvent::I3; -use File::Temp qw(tmpnam); use v5.10; my $i3 = i3("/tmp/nestedcons"); -sub get_workspace_names { - my $tree = $i3->get_workspaces->recv; - my @workspaces = map { @{$_->{nodes}} } @{$tree->{nodes}}; - [ map { $_->{name} } @workspaces ] -} - sub workspace_exists { my ($name) = @_; ($name ~~ @{get_workspace_names()}) } -sub get_unused_workspace { - my @names = get_workspace_names(); - my $tmp; - do { $tmp = tmpnam() } while ($tmp ~~ @names); - $tmp -} - my $tmp = get_unused_workspace(); diag("Temporary workspace name: $tmp\n"); $i3->command("workspace $tmp")->recv; ok(workspace_exists($tmp), 'workspace created'); +# if the workspace could not be created, we cannot run any other test +# (every test starts by creating its workspace) +if (!workspace_exists($tmp)) { + BAIL_OUT('Cannot create workspace, further tests make no sense'); +} my $otmp = get_unused_workspace(); diag("Other temporary workspace name: $otmp\n"); diff --git a/testcases/t/18-openkill.t b/testcases/t/18-openkill.t index 2763d3c2..e13ca873 100644 --- a/testcases/t/18-openkill.t +++ b/testcases/t/18-openkill.t @@ -4,34 +4,14 @@ # Tests whether opening an empty container and killing it again works # use Test::More tests => 3; -use Data::Dumper; +use FindBin; +use lib "$FindBin::Bin/lib"; +use i3test; use AnyEvent::I3; -use File::Temp qw(tmpnam); use v5.10; my $i3 = i3("/tmp/nestedcons"); -sub get_workspace_names { - my $tree = $i3->get_workspaces->recv; - my @workspaces = map { @{$_->{nodes}} } @{$tree->{nodes}}; - [ map { $_->{name} } @workspaces ] -} - -sub get_unused_workspace { - my @names = get_workspace_names(); - my $tmp; - do { $tmp = tmpnam() } while ($tmp ~~ @names); - $tmp -} - -sub get_ws_content { - my ($name) = @_; - my $tree = $i3->get_workspaces->recv; - my @ws = map { @{$_->{nodes}} } @{$tree->{nodes}}; - my @cons = map { $_->{nodes} } grep { $_->{name} eq $name } @ws; - return $cons[0]; -} - my $tmp = get_unused_workspace(); $i3->command("workspace $tmp")->recv; diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 540551b0..e98685f7 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -1,9 +1,15 @@ package i3test; # vim:ts=4:sw=4:expandtab +use File::Temp qw(tmpnam); use X11::XCB::Rect; use X11::XCB::Window; use X11::XCB qw(:all); +use AnyEvent::I3; +use Exporter qw(import); +use base 'Exporter'; + +our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content); BEGIN { my $window_count = 0; @@ -58,4 +64,34 @@ sub recv_ipc_command { decode_json($buffer) } +sub get_workspace_names { + my $i3 = i3("/tmp/nestedcons"); + # TODO: use correct command as soon as AnyEvent::i3 is updated + my $tree = $i3->get_workspaces->recv; + my @workspaces = map { @{$_->{nodes}} } @{$tree->{nodes}}; + [ map { $_->{name} } @workspaces ] +} + +sub get_unused_workspace { + my @names = get_workspace_names(); + my $tmp; + do { $tmp = tmpnam() } while ($tmp ~~ @names); + $tmp +} + +# +# returns the content (== tree, starting from the node of a workspace) +# of a workspace +# +sub get_ws_content { + my ($name) = @_; + my $i3 = i3("/tmp/nestedcons"); + my $tree = $i3->get_workspaces->recv; + my @ws = map { @{$_->{nodes}} } @{$tree->{nodes}}; + my @cons = map { $_->{nodes} } grep { $_->{name} eq $name } @ws; + # as there can only be one workspace with this name, we can safely + # return the first entry + return $cons[0]; +} + 1 From 93600ce0fdad72f4820c673498e4cc4d3dc330f2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 16 Apr 2010 15:30:07 +0200 Subject: [PATCH 021/867] implement con_id for matching containers, extend testcase --- include/data.h | 1 + src/cmdparse.l | 5 +++- src/cmdparse.y | 57 +++++++++++++++++++++++++++++++++------ src/ipc.c | 2 +- testcases/t/18-openkill.t | 24 ++++++++++++++++- 5 files changed, 78 insertions(+), 11 deletions(-) diff --git a/include/data.h b/include/data.h index 4f7983dc..13ce08b4 100644 --- a/include/data.h +++ b/include/data.h @@ -239,6 +239,7 @@ struct Match { char *class; char *instance; xcb_window_t id; + Con *con_id; bool floating; enum { M_GLOBAL, M_OUTPUT, M_WORKSPACE } levels; diff --git a/src/cmdparse.l b/src/cmdparse.l index 684a588c..079aa8f9 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -95,11 +95,14 @@ none { return TOK_NONE; } mode { return TOK_MODE; } tiling { return TOK_TILING; } floating { return TOK_FLOATING; } -workspace { return TOK_WORKSPACE; } +workspace { BEGIN(WANT_WS_STRING); return TOK_WORKSPACE; } focus { return TOK_FOCUS; } move { return TOK_MOVE; } +open { return TOK_OPEN; } class { BEGIN(WANT_QSTRING); return TOK_CLASS; } +id { BEGIN(WANT_QSTRING); return TOK_ID; } +con_id { BEGIN(WANT_QSTRING); return TOK_CON_ID; } . { return (int)yytext[0]; } diff --git a/src/cmdparse.y b/src/cmdparse.y index eb5ab71f..81dd148d 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -109,8 +109,11 @@ void parse_cmd(const char *new) { %token TOK_WORKSPACE "workspace" %token TOK_FOCUS "focus" %token TOK_MOVE "move" +%token TOK_OPEN "open" %token TOK_CLASS "class" +%token TOK_ID "id" +%token TOK_CON_ID "con_id" %token WHITESPACE "" %token STR "" @@ -156,9 +159,6 @@ matchstart: /* copy all_cons */ Con *con; TAILQ_FOREACH(con, &all_cons, all_cons) { - if (con->window == NULL) - continue; - owindow *ow = smalloc(sizeof(owindow)); ow->con = con; TAILQ_INSERT_TAIL(&owindows, ow, owindows); @@ -184,12 +184,22 @@ matchend: next = TAILQ_NEXT(next, owindows); printf("checking if con %p / %s matches\n", current->con, current->con->name); - if (match_matches_window(¤t_match, current->con->window)) { - printf("matches!\n"); - TAILQ_INSERT_TAIL(&owindows, current, owindows); + if (current_match.con_id != NULL) { + if (current_match.con_id == current->con) { + printf("matches container!\n"); + TAILQ_INSERT_TAIL(&owindows, current, owindows); + + } } else { - printf("doesnt match\n"); - free(current); + if (current->con->window == NULL) + continue; + if (match_matches_window(¤t_match, current->con->window)) { + printf("matches window!\n"); + TAILQ_INSERT_TAIL(&owindows, current, owindows); + } else { + printf("doesnt match\n"); + free(current); + } } } @@ -206,6 +216,13 @@ criteria: printf("criteria: class = %s\n", $3); current_match.class = $3; } + | TOK_CON_ID '=' STR + { + printf("criteria: id = %s\n", $3); + /* TODO: correctly parse number */ + current_match.con_id = atoi($3); + printf("id as int = %d\n", current_match.con_id); + } ; operations: @@ -226,9 +243,11 @@ operation: | mode | workspace | move*/ + | workspace | attach | focus | kill + | open ; exec: @@ -265,10 +284,32 @@ kill: owindow *current; printf("killing!\n"); + /* TODO: check if the match is empty, not if the result is empty */ + if (TAILQ_EMPTY(&owindows)) + tree_close(focused); + else { TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); tree_close(current->con); } + } } ; + +workspace: + TOK_WORKSPACE WHITESPACE STR + { + printf("should switch to workspace %s\n", $3); + workspace_show($3); + free($3); + } + ; + +open: + TOK_OPEN + { + printf("opening new container\n"); + tree_open_con(NULL); + } + ; diff --git a/src/ipc.c b/src/ipc.c index 0412bdae..704c0d47 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -114,7 +114,7 @@ IPC_HANDLER(command) { * message_size bytes out of the buffer */ char *command = scalloc(message_size); strncpy(command, (const char*)message, message_size); - parse_command((const char*)command); + parse_cmd((const char*)command); free(command); /* For now, every command gets a positive acknowledge diff --git a/testcases/t/18-openkill.t b/testcases/t/18-openkill.t index e13ca873..935523d4 100644 --- a/testcases/t/18-openkill.t +++ b/testcases/t/18-openkill.t @@ -3,7 +3,8 @@ # # Tests whether opening an empty container and killing it again works # -use Test::More tests => 3; +use Test::More tests => 6; +use Data::Dumper; use FindBin; use lib "$FindBin::Bin/lib"; use i3test; @@ -25,4 +26,25 @@ ok(@{get_ws_content($tmp)} == 1, 'container opened'); $i3->command("kill")->recv; ok(@{get_ws_content($tmp)} == 0, 'container killed'); +############################################################## +# open two containers and kill the one which is not focused +# by its ID to test if the parser correctly matches the window +############################################################## + +$i3->command('open')->recv; +$i3->command('open')->recv; +ok(@{get_ws_content($tmp)} == 2, 'two containers opened'); + +my $content = get_ws_content($tmp); +# TODO: get the focused window, don’t assume that it is +# the latest one +my $id = $content->[0]->{id}; +diag('id of not focused = ' . $id); + +$i3->command("[con_id=\"$id\"] kill")->recv; + +$content = get_ws_content($tmp); +ok(@{$content} == 1, 'one container killed'); +ok($content->[0]->{id} != $id, 'correct window killed'); + diag( "Testing i3, Perl $], $^X" ); From 2534f219400718ef20d60fb31dbb2c03ec3fbb27 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 16 Apr 2010 20:56:49 +0200 Subject: [PATCH 022/867] ignore sequence of unmapnotify events (generates enternotify events) --- src/handlers.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 4d3e88b9..510148d0 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -450,9 +450,11 @@ int handle_screen_change(void *prophs, xcb_connection_t *conn, */ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event) { - //add_ignore_event(event->sequence); + /* we need to ignore EnterNotify events which will be generated because a + * different window is visible now */ + add_ignore_event(event->sequence); - DLOG("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event); + DLOG("UnmapNotify for 0x%08x (received from 0x%08x), serial %d\n", event->window, event->event, event->sequence); Con *con = con_by_window_id(event->window); if (con == NULL) { LOG("Not a managed window, ignoring\n"); From 138a790cd01b43d56bec0ba00abee08bcdeb230d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 16 Apr 2010 20:59:21 +0200 Subject: [PATCH 023/867] clean up old workspace when switching (makes test 2 of t/16-*.t pass) --- src/workspace.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/workspace.c b/src/workspace.c index 4d7922a8..9b5593d6 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -33,7 +33,7 @@ Con *workspace_get(const char *num) { } } - LOG("should switch to ws %s\n", num); + LOG("getting ws %s\n", num); if (workspace == NULL) { LOG("need to create this one\n"); output = con_get_output(focused); @@ -96,7 +96,9 @@ bool workspace_is_visible(Workspace *ws) { * */ void workspace_show(const char *num) { - Con *workspace, *current; + Con *workspace, *current, *old; + + old = con_get_workspace(focused); workspace = workspace_get(num); workspace->fullscreen_mode = CF_OUTPUT; @@ -110,6 +112,12 @@ void workspace_show(const char *num) { while (!TAILQ_EMPTY(&(next->focus_head))) next = TAILQ_FIRST(&(next->focus_head)); + + if (TAILQ_EMPTY(&(old->nodes_head))) { + LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); + tree_close(old); + } + con_focus(next); workspace->fullscreen_mode = CF_OUTPUT; LOG("focused now = %p / %s\n", focused, focused->name); From f48cc9ee215f1c25608cf71a068b81c4f8bc8e23 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 16 Apr 2010 21:01:08 +0200 Subject: [PATCH 024/867] update t/16* for data structure --- testcases/t/16-nestedcons.t | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t index 2f19b0c2..39087e85 100644 --- a/testcases/t/16-nestedcons.t +++ b/testcases/t/16-nestedcons.t @@ -49,6 +49,9 @@ my $expected = { orientation => ignore(), type => 0, id => ignore(), + rect => ignore(), + layout => 0, + focus => ignore(), }; cmp_deeply($tree, $expected, 'root node OK'); From 479679807473d2c41b870f31b2f28b5a884157ab Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 16 Apr 2010 21:02:34 +0200 Subject: [PATCH 025/867] better debug messages --- src/x.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/x.c b/src/x.c index 347b8f76..7ba56c65 100644 --- a/src/x.c +++ b/src/x.c @@ -255,7 +255,7 @@ void x_push_changes(Con *con) { LOG("\n\n PUSHING CHANGES\n\n"); x_push_node(con); - LOG("-- PUSHING FOCUS STACK --\n"); + LOG("-- PUSHING WINDOW STACK --\n"); /* X11 correctly represents the stack if we push it from bottom to top */ CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { LOG("stack: 0x%08x\n", state->id); @@ -278,7 +278,7 @@ void x_push_changes(Con *con) { to_focus = focused->window->id; if (focused_id != to_focus) { - LOG("Updating focus\n"); + LOG("Updating focus (focused: %p / %s)\n", focused, focused->name); xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME); } From e0b7ae872eb826d9f3a7dd78844938bdbb17db7c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 16 Apr 2010 21:04:36 +0200 Subject: [PATCH 026/867] move con_focus to con.c --- include/con.h | 1 + include/tree.h | 1 - src/con.c | 18 ++++++++++++++++++ src/tree.c | 18 ------------------ 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/include/con.h b/include/con.h index 71f73e3d..8022fc01 100644 --- a/include/con.h +++ b/include/con.h @@ -2,6 +2,7 @@ #define _CON_H Con *con_new(Con *parent); +void con_focus(Con *con); bool con_is_leaf(Con *con); bool con_accepts_window(Con *con); Con *con_get_output(Con *con); diff --git a/include/tree.h b/include/tree.h index 21c02967..029bbe34 100644 --- a/include/tree.h +++ b/include/tree.h @@ -15,7 +15,6 @@ extern struct all_cons_head all_cons; void tree_init(); Con *tree_open_con(Con *con); void tree_split(Con *con, orientation_t orientation); -void con_focus(Con *con); void level_up(); void level_down(); void tree_render(); diff --git a/src/con.c b/src/con.c index 5c220458..d0daba59 100644 --- a/src/con.c +++ b/src/con.c @@ -74,6 +74,24 @@ void con_detach(Con *con) { } } +/* + * Sets input focus to the given container. Will be updated in X11 in the next + * run of x_push_changes(). + * + */ +void con_focus(Con *con) { + assert(con != NULL); + + /* 1: set focused-pointer to the new con */ + /* 2: exchange the position of the container in focus stack of the parent all the way up */ + TAILQ_REMOVE(&(con->parent->focus_head), con, focused); + TAILQ_INSERT_HEAD(&(con->parent->focus_head), con, focused); + if (con->parent->parent != NULL) + con_focus(con->parent); + + focused = con; +} + /* * Returns true when this node is a leaf node (has no children) * diff --git a/src/tree.c b/src/tree.c index 22dcf5cc..3a657983 100644 --- a/src/tree.c +++ b/src/tree.c @@ -9,24 +9,6 @@ struct Con *focused; struct all_cons_head all_cons = TAILQ_HEAD_INITIALIZER(all_cons); -/* - * Sets input focus to the given container. Will be updated in X11 in the next - * run of x_push_changes(). - * - */ -void con_focus(Con *con) { - assert(con != NULL); - - /* 1: set focused-pointer to the new con */ - /* 2: exchange the position of the container in focus stack of the parent all the way up */ - TAILQ_REMOVE(&(con->parent->focus_head), con, focused); - TAILQ_INSERT_HEAD(&(con->parent->focus_head), con, focused); - if (con->parent->parent != NULL) - con_focus(con->parent); - - focused = con; -} - /* * Loads tree from ~/.i3/_restart.json * From bb220b27d7b0706f270250fcca809cd316c34b02 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 16 Apr 2010 22:50:20 +0200 Subject: [PATCH 027/867] check for empty matches --- src/cmdparse.y | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index 81dd148d..f588ad1a 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -285,7 +285,7 @@ kill: printf("killing!\n"); /* TODO: check if the match is empty, not if the result is empty */ - if (TAILQ_EMPTY(&owindows)) + if (match_is_empty(¤t_match)) tree_close(focused); else { TAILQ_FOREACH(current, &owindows, owindows) { From 8d05039b04ec909d24c98154832ad5b1cdada374 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 16 Apr 2010 22:51:25 +0200 Subject: [PATCH 028/867] move match_* to match.c --- Makefile | 2 +- include/con.h | 1 - include/match.h | 7 +++++++ src/con.c | 23 ----------------------- src/match.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 54 insertions(+), 25 deletions(-) create mode 100644 include/match.h create mode 100644 src/match.c diff --git a/Makefile b/Makefile index 368dc777..93c98e1e 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c -FILES:=src/ipc.c src/nc.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c +FILES:=src/ipc.c src/nc.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) diff --git a/include/con.h b/include/con.h index 8022fc01..2b01475a 100644 --- a/include/con.h +++ b/include/con.h @@ -14,7 +14,6 @@ Con *con_by_frame_id(xcb_window_t frame); Con *con_for_window(i3Window *window, Match **store_match); void con_attach(Con *con, Con *parent); void con_detach(Con *con); -bool match_matches_window(Match *match, i3Window *window); enum { WINDOW_ADD = 0, WINDOW_REMOVE = 1 }; void con_fix_percent(Con *con, int action); diff --git a/include/match.h b/include/match.h new file mode 100644 index 00000000..eb6aec1b --- /dev/null +++ b/include/match.h @@ -0,0 +1,7 @@ +#ifndef _MATCH_H +#define _MATCH_H + +bool match_is_empty(Match *match); +bool match_matches_window(Match *match, i3Window *window); + +#endif diff --git a/src/con.c b/src/con.c index d0daba59..2f61363b 100644 --- a/src/con.c +++ b/src/con.c @@ -221,29 +221,6 @@ Con *con_by_frame_id(xcb_window_t frame) { return NULL; } -bool match_matches_window(Match *match, i3Window *window) { - /* TODO: pcre, full matching, … */ - if (match->class != NULL && strcasecmp(match->class, window->class_class) == 0) { - LOG("match made by window class (%s)\n", window->class_class); - return true; - } - - if (match->instance != NULL && strcasecmp(match->instance, window->class_instance) == 0) { - LOG("match made by window instance (%s)\n", window->class_instance); - return true; - } - - - if (match->id != XCB_NONE && window->id == match->id) { - LOG("match made by window id (%d)\n", window->id); - return true; - } - - LOG("window %d (%s) could not be matched\n", window->id, window->class_class); - - return false; -} - /* * Returns the first container which wants to swallow this window * TODO: priority diff --git a/src/match.c b/src/match.c new file mode 100644 index 00000000..cdabdc3c --- /dev/null +++ b/src/match.c @@ -0,0 +1,46 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * + */ + +#include "all.h" + +bool match_is_empty(Match *match) { + /* we cannot simply use memcmp() because the structure is part of a + * TAILQ and I don’t want to start with things like assuming that the + * last member of a struct really is at the end in memory… */ + return (match->title == NULL && + match->application == NULL && + match->class == NULL && + match->instance == NULL && + match->id == XCB_NONE && + match->con_id == NULL && + match->floating == false); +} + +bool match_matches_window(Match *match, i3Window *window) { + /* TODO: pcre, full matching, … */ + if (match->class != NULL && window->class_class != NULL && strcasecmp(match->class, window->class_class) == 0) { + LOG("match made by window class (%s)\n", window->class_class); + return true; + } + + if (match->instance != NULL && window->class_instance != NULL && strcasecmp(match->instance, window->class_instance) == 0) { + LOG("match made by window instance (%s)\n", window->class_instance); + return true; + } + + + if (match->id != XCB_NONE && window->id == match->id) { + LOG("match made by window id (%d)\n", window->id); + return true; + } + + LOG("window %d (%s) could not be matched\n", window->id, window->class_class); + + return false; +} + From 77ec4219c97e7fa055af85ebe2768a4ee900a69f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 16 Apr 2010 22:57:21 +0200 Subject: [PATCH 029/867] make floating an enum (we need three states, not only two) --- include/data.h | 8 ++++---- src/match.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/data.h b/include/data.h index 13ce08b4..bdfcbcc3 100644 --- a/include/data.h +++ b/include/data.h @@ -240,16 +240,16 @@ struct Match { char *instance; xcb_window_t id; Con *con_id; - bool floating; + enum { M_ANY = 0, M_TILING, M_FLOATING } floating; - enum { M_GLOBAL, M_OUTPUT, M_WORKSPACE } levels; + enum { M_GLOBAL = 0, M_OUTPUT, M_WORKSPACE } levels; - enum { M_USER, M_RESTART } source; + enum { M_USER = 0, M_RESTART } source; /* wo das fenster eingefügt werden soll. bei here wird es direkt * diesem Con zugewiesen, also layout saving. bei active ist es * ein assignment, welches an der momentan fokussierten stelle einfügt */ - enum { M_HERE, M_ACTIVE } insert_where; + enum { M_HERE = 0, M_ACTIVE } insert_where; TAILQ_ENTRY(Match) matches; }; diff --git a/src/match.c b/src/match.c index cdabdc3c..763a4e7e 100644 --- a/src/match.c +++ b/src/match.c @@ -18,7 +18,7 @@ bool match_is_empty(Match *match) { match->instance == NULL && match->id == XCB_NONE && match->con_id == NULL && - match->floating == false); + match->floating == M_ANY); } bool match_matches_window(Match *match, i3Window *window) { From 50d590df28d602deebf143b0948ecf133d007e02 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 16 Apr 2010 23:04:42 +0200 Subject: [PATCH 030/867] add test for the match functionality in the new parser --- testcases/t/19-match.t | 63 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 testcases/t/19-match.t diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t new file mode 100644 index 00000000..9babb074 --- /dev/null +++ b/testcases/t/19-match.t @@ -0,0 +1,63 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Tests all kinds of matching methods +# +use Test::More tests => 4; +use Data::Dumper; +use FindBin; +use lib "$FindBin::Bin/lib"; +use i3test; +use AnyEvent::I3; +use X11::XCB qw(:all); +use v5.10; + +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + +# Open a new window +my $x = X11::XCB::Connection->new; +my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#C0C0C0', +); + +$window->map; +# give it some time to be picked up by the window manager +# TODO: better check for $window->mapped or something like that? +# maybe we can even wait for getting mapped? +my $c = 0; +while (@{get_ws_content($tmp)} == 0 and $c++ < 5) { + sleep 0.25; +} +my $content = get_ws_content($tmp); +ok(@{$content} == 1, 'window mapped'); +my $win = $content->[0]; + +###################################################################### +# first test that matches which should not match this window really do +# not match it +###################################################################### +# TODO: use PCRE expressions +# TODO: specify more match types +$i3->command(q|[class="*"] kill|)->recv; +$i3->command(q|[con_id="99999"] kill|)->recv; + +$content = get_ws_content($tmp); +ok(@{$content} == 1, 'window still there'); + +# now kill the window +my $id = $win->{id}; +$i3->command(qq|[con_id="$id"] kill|)->recv; + +$content = get_ws_content($tmp); +ok(@{$content} == 0, 'window killed'); + +# TODO: same test, but with pcre expressions + +diag( "Testing i3, Perl $], $^X" ); From 22f38ebde4d197db893ec2020f5e6ba63cc35937 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 12:57:06 +0200 Subject: [PATCH 031/867] clear current_match when dropping state --- src/cmdparse.y | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cmdparse.y b/src/cmdparse.y index f588ad1a..acac73e2 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -132,6 +132,7 @@ commands: /* empty */ TAILQ_REMOVE(&owindows, current, owindows); free(current); } + memset(¤t_match, 0, sizeof(Match)); } ; From 9488e3d2499f69666b5173754c0d0d6d1e5a2a2b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 12:58:36 +0200 Subject: [PATCH 032/867] add testcase for multiple commands (and whitespace variations) --- testcases/t/20-multiple-cmds.t | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 testcases/t/20-multiple-cmds.t diff --git a/testcases/t/20-multiple-cmds.t b/testcases/t/20-multiple-cmds.t new file mode 100644 index 00000000..361bb06b --- /dev/null +++ b/testcases/t/20-multiple-cmds.t @@ -0,0 +1,45 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Tests multiple commands (using ';') and multiple operations (using ',') +# +use Test::More tests => 24; +use Data::Dumper; +use FindBin; +use lib "$FindBin::Bin/lib"; +use i3test; +use AnyEvent::I3; +use X11::XCB qw(:all); +use v5.10; + +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +sub multiple_cmds { + my ($cmd) = @_; + + $i3->command('open')->recv; + $i3->command('open')->recv; + ok(@{get_ws_content($tmp)} == 2, 'two containers opened'); + + $i3->command($cmd)->recv; + ok(@{get_ws_content($tmp)} == 0, "both containers killed (cmd = $cmd)"); +} +multiple_cmds('kill;kill'); +multiple_cmds('kill; kill'); +multiple_cmds('kill ; kill'); +multiple_cmds('kill ;kill'); +multiple_cmds('kill ;kill'); +multiple_cmds('kill ; kill'); +multiple_cmds("kill;\tkill"); +multiple_cmds("kill\t;kill"); +multiple_cmds("kill\t;\tkill"); +multiple_cmds("kill\t ;\tkill"); +multiple_cmds("kill\t ;\t kill"); +multiple_cmds("kill \t ; \t kill"); + +# TODO: need a non-invasive command before implementing a test which uses ',' + +diag( "Testing i3, Perl $], $^X" ); From 1b4bd96ea989d14052fabf7c84a03ca53387fe67 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 13:53:41 +0200 Subject: [PATCH 033/867] extend t/02-fullscreen.t --- testcases/t/02-fullscreen.t | 60 ++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/testcases/t/02-fullscreen.t b/testcases/t/02-fullscreen.t index 40a7d983..766a43af 100644 --- a/testcases/t/02-fullscreen.t +++ b/testcases/t/02-fullscreen.t @@ -1,16 +1,33 @@ #!perl +# vim:ts=4:sw=4:expandtab -use Test::More tests => 8; +use Test::More tests => 16; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; +use FindBin; +use lib "$FindBin::Bin/lib"; +use i3test; +use AnyEvent::I3; +use List::Util qw(first); +use v5.10; # We use relatively long sleeps (1/4 second) to make sure the window manager # reacted. use Time::HiRes qw(sleep); +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +# get the output of this workspace +my $tree = $i3->get_workspaces->recv; +my @outputs = @{$tree->{nodes}}; +my $output = first { defined(first { $_->{name} eq $tmp } @{$_->{nodes}}) } @outputs; + BEGIN { - use_ok('X11::XCB::Window'); + use_ok('X11::XCB::Window'); } my $x = X11::XCB::Connection->new; @@ -18,9 +35,9 @@ my $x = X11::XCB::Connection->new; my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30); my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => $original_rect, - background_color => '#C0C0C0', + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => $original_rect, + background_color => '#C0C0C0', ); isa_ok($window, 'X11::XCB::Window'); @@ -31,6 +48,9 @@ $window->map; sleep(0.25); +# open another container to make the window get only half of the screen +$i3->command('open')->recv; + my $new_rect = $window->rect; ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned"); $original_rect = $new_rect; @@ -44,12 +64,26 @@ sleep(0.25); $new_rect = $window->rect; ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen"); +my $orect = $output->{rect}; +my $wrect = $new_rect; + +# see if the window really is fullscreen. 20 px for borders are allowed +my $threshold = 20; +ok(($wrect->{x} - $orect->{x}) < $threshold, 'x coordinate fullscreen'); +ok(($wrect->{y} - $orect->{y}) < $threshold, 'y coordinate fullscreen'); +ok(abs($wrect->{width} - $orect->{width}) < $threshold, 'width coordinate fullscreen'); +ok(abs($wrect->{height} - $orect->{height}) < $threshold, 'height coordinate fullscreen'); + $window->unmap; +# open another container because the empty one will swallow the window we +# map in a second +$i3->command('open')->recv; + $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => $original_rect, - background_color => 61440, + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => $original_rect, + background_color => 61440, ); is_deeply($window->rect, $original_rect, "rect unmodified before mapping"); @@ -59,7 +93,17 @@ $window->map; sleep(0.25); +$new_rect = $window->rect; ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen"); ok($window->mapped, "Window is mapped after opening it in fullscreen mode"); +$wrect = $new_rect; + +# see if the window really is fullscreen. 20 px for borders are allowed +my $threshold = 20; +ok(($wrect->{x} - $orect->{x}) < $threshold, 'x coordinate fullscreen'); +ok(($wrect->{y} - $orect->{y}) < $threshold, 'y coordinate fullscreen'); +ok(abs($wrect->{width} - $orect->{width}) < $threshold, 'width coordinate fullscreen'); +ok(abs($wrect->{height} - $orect->{height}) < $threshold, 'height coordinate fullscreen'); + diag( "Testing i3, Perl $], $^X" ); From c56867792aa98ccedecfa40ca55e4a1a141a65c4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 13:54:45 +0200 Subject: [PATCH 034/867] handle client messages (fullscreen window state) --- include/handlers.h | 2 +- src/handlers.c | 44 ++++++++++++++++++++++++-------------------- src/nc.c | 3 +++ 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/include/handlers.h b/include/handlers.h index f5ea0591..270822b8 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -144,7 +144,6 @@ int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, */ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *event); -#if 0 /** * Handle client messages (EWMH) * @@ -152,6 +151,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message_event_t *event); +#if 0 /** * Handles _NET_WM_WINDOW_TYPE changes * diff --git a/src/handlers.c b/src/handlers.c index 510148d0..6b713169 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -642,36 +642,40 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * #endif } -#if 0 /* * Handle client messages (EWMH) * */ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message_event_t *event) { - if (event->type == atoms[_NET_WM_STATE]) { - if (event->format != 32 || event->data.data32[1] != atoms[_NET_WM_STATE_FULLSCREEN]) - return 0; + LOG("ClientMessage for window 0x%08x\n", event->window); + if (event->type == atoms[_NET_WM_STATE]) { + if (event->format != 32 || event->data.data32[1] != atoms[_NET_WM_STATE_FULLSCREEN]) + return 0; - Client *client = table_get(&by_child, event->window); - if (client == NULL) - return 0; + Con *con = con_by_window_id(event->window); + if (con == NULL) + return 0; - /* Check if the fullscreen state should be toggled */ - if ((client->fullscreen && - (event->data.data32[0] == _NET_WM_STATE_REMOVE || - event->data.data32[0] == _NET_WM_STATE_TOGGLE)) || - (!client->fullscreen && - (event->data.data32[0] == _NET_WM_STATE_ADD || - event->data.data32[0] == _NET_WM_STATE_TOGGLE))) - client_toggle_fullscreen(conn, client); - } else { - ELOG("unhandled clientmessage\n"); - return 0; - } + /* Check if the fullscreen state should be toggled */ + if ((con->fullscreen_mode != CF_NONE && + (event->data.data32[0] == _NET_WM_STATE_REMOVE || + event->data.data32[0] == _NET_WM_STATE_TOGGLE)) || + (con->fullscreen_mode == CF_NONE && + (event->data.data32[0] == _NET_WM_STATE_ADD || + event->data.data32[0] == _NET_WM_STATE_TOGGLE))) + con_toggle_fullscreen(con); - return 1; + tree_render(); + x_push_changes(croot); + } else { + ELOG("unhandled clientmessage\n"); + return 0; + } + + return 1; } +#if 0 int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property) { /* TODO: Implement this one. To do this, implement a little test program which sleep(1)s diff --git a/src/nc.c b/src/nc.c index 92dba08d..869a6f86 100644 --- a/src/nc.c +++ b/src/nc.c @@ -279,6 +279,9 @@ int main(int argc, char *argv[]) { /* Enter window = user moved his mouse over the window */ xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL); + /* Client message are sent to the root window. The only interesting client message + for us is _NET_WM_STATE, we honour _NET_WM_STATE_FULLSCREEN */ + xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL); /* Setup NetWM atoms */ #define GET_ATOM(name) \ From 53dcebfd8a900fe4c317985c0160003a8c9e13fc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 14:21:34 +0200 Subject: [PATCH 035/867] put container in fullscreen mode if the fullscreen state is set when mapping --- src/manage.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/manage.c b/src/manage.c index 49f10599..ce11a2c1 100644 --- a/src/manage.c +++ b/src/manage.c @@ -167,6 +167,19 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki //xcb_destroy_window(conn, nc->frame); } + xcb_atom_t *state; + xcb_get_property_reply_t *preply; + if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL && + (state = xcb_get_property_value(preply)) != NULL) { + /* Check all set _NET_WM_STATEs */ + for (int i = 0; i < xcb_get_property_value_length(preply); i++) { + if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN]) + continue; + con_toggle_fullscreen(nc); + break; + } + } + xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window); tree_render(); From 68542f3c2214b3a024a0039b10719a078b2eaf79 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 16:41:20 +0200 Subject: [PATCH 036/867] When assigning children to containers, reset their x window state --- include/x.h | 1 + src/manage.c | 9 +++++++-- src/x.c | 25 +++++++++++++++++++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/include/x.h b/include/x.h index 85dfc3ce..1e564d4f 100644 --- a/include/x.h +++ b/include/x.h @@ -6,6 +6,7 @@ #define _X_H void x_con_init(Con *con); +void x_reinit(Con *con); void x_con_kill(Con *con); void x_window_kill(xcb_window_t window); void x_draw_decoration(Con *con); diff --git a/src/manage.c b/src/manage.c index ce11a2c1..940013e2 100644 --- a/src/manage.c +++ b/src/manage.c @@ -110,12 +110,16 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki goto out; /* Check if the window is already managed */ - if (con_by_window_id(window) != NULL) + if (con_by_window_id(window) != NULL) { + LOG("already managed\n"); goto out; + } /* Get the initial geometry (position, size, …) */ - if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL) + if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL) { + LOG("could not get geometry\n"); goto out; + } LOG("reparenting!\n"); uint32_t mask = 0; @@ -159,6 +163,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki } } nc->window = cwindow; + x_reinit(nc); xcb_void_cookie_t rcookie = xcb_reparent_window_checked(conn, window, nc->frame, 0, 0); if (xcb_request_check(conn, rcookie) != NULL) { diff --git a/src/x.c b/src/x.c index 7ba56c65..f73fc846 100644 --- a/src/x.c +++ b/src/x.c @@ -86,6 +86,25 @@ void x_con_init(Con *con) { LOG("adding new state for window id 0x%08x\n", state->id); } +/* + * Re-initializes the associated X window state for this container. You have + * to call this when you assign a client to an empty container to ensure that + * its state gets updated correctly. + * + */ +void x_reinit(Con *con) { + struct con_state *state; + + if ((state = state_for_frame(con->frame)) == NULL) { + ELOG("window state not found\n"); + return; + } + + LOG("resetting state %p to initial\n", state); + state->initial = true; + memset(&(state->window_rect), 0, sizeof(Rect)); +} + void x_con_kill(Con *con) { con_state *state; @@ -202,8 +221,10 @@ static void x_push_node(Con *con) { LOG("Pushing changes for node %p / %s\n", con, con->name); state = state_for_frame(con->frame); - /* map/unmap if map state changed */ - if (state->mapped != con->mapped) { + /* map/unmap if map state changed, also ensure that the child window + * is changed if we are mapped *and* in initial state (meaning the + * container was empty before, but now got a child) */ + if (state->mapped != con->mapped || (con->mapped && state->initial)) { if (!con->mapped) { LOG("unmapping container\n"); xcb_unmap_window(conn, con->frame); From d973f30fc214e8ededfbd7630f2528fc01250f98 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 16:43:34 +0200 Subject: [PATCH 037/867] push X11 changes after a window is mapped --- src/handlers.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/handlers.c b/src/handlers.c index 6b713169..0d2f10bf 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -270,6 +270,7 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve add_ignore_event(event->sequence); manage_window(event->window, cookie, false); + x_push_changes(croot); return 1; } #if 0 From c4d87e2f81f752c709898dd9d91505c5257890e8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 17:27:53 +0200 Subject: [PATCH 038/867] handle destroynotify events --- include/handlers.h | 3 +-- src/handlers.c | 15 +++++++-------- src/nc.c | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/include/handlers.h b/include/handlers.h index 270822b8..12d64c48 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -97,7 +97,7 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, * */ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event); -#if 0 + /** * A destroy notify event is sent when the window is not unmapped, but * immediately destroyed (for example when starting a window and immediately @@ -110,7 +110,6 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_notify_event_t *event); -#endif /** * Called when a window changes its title * diff --git a/src/handlers.c b/src/handlers.c index 0d2f10bf..535d963e 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -507,7 +507,6 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti return 1; } -#if 0 /* * A destroy notify event is sent when the window is not unmapped, but * immediately destroyed (for example when starting a window and immediately @@ -518,16 +517,16 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti * */ int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_notify_event_t *event) { - DLOG("destroy notify for 0x%08x, 0x%08x\n", event->event, event->window); + DLOG("destroy notify for 0x%08x, 0x%08x\n", event->event, event->window); - xcb_unmap_notify_event_t unmap; - unmap.sequence = event->sequence; - unmap.event = event->event; - unmap.window = event->window; + xcb_unmap_notify_event_t unmap; + unmap.sequence = event->sequence; + unmap.event = event->event; + unmap.window = event->window; - return handle_unmap_notify_event(NULL, conn, &unmap); + return handle_unmap_notify_event(NULL, conn, &unmap); } -#endif + /* * Called when a window changes its title * diff --git a/src/nc.c b/src/nc.c index 869a6f86..8cd325a8 100644 --- a/src/nc.c +++ b/src/nc.c @@ -272,7 +272,7 @@ int main(int argc, char *argv[]) { xcb_event_set_map_request_handler(&evenths, handle_map_request, NULL); xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL); - //xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL); + xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL); xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL); From b244ce3915e5e41bb24df49292da189bea8059c4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 17:40:19 +0200 Subject: [PATCH 039/867] extend fullscreen testcase --- testcases/t/02-fullscreen.t | 44 +++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/testcases/t/02-fullscreen.t b/testcases/t/02-fullscreen.t index 766a43af..4ca40e3f 100644 --- a/testcases/t/02-fullscreen.t +++ b/testcases/t/02-fullscreen.t @@ -1,7 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -use Test::More tests => 16; +use Test::More tests => 19; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -32,6 +32,10 @@ BEGIN { my $x = X11::XCB::Connection->new; +################################## +# map a window, then fullscreen it +################################## + my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30); my $window = $x->root->create_child( @@ -74,12 +78,18 @@ ok(($wrect->{y} - $orect->{y}) < $threshold, 'y coordinate fullscreen'); ok(abs($wrect->{width} - $orect->{width}) < $threshold, 'width coordinate fullscreen'); ok(abs($wrect->{height} - $orect->{height}) < $threshold, 'height coordinate fullscreen'); + $window->unmap; +######################################################### +# test with a window which is fullscreened before mapping +######################################################### + # open another container because the empty one will swallow the window we # map in a second $i3->command('open')->recv; +$original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30); $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => $original_rect, @@ -100,10 +110,40 @@ ok($window->mapped, "Window is mapped after opening it in fullscreen mode"); $wrect = $new_rect; # see if the window really is fullscreen. 20 px for borders are allowed -my $threshold = 20; ok(($wrect->{x} - $orect->{x}) < $threshold, 'x coordinate fullscreen'); ok(($wrect->{y} - $orect->{y}) < $threshold, 'y coordinate fullscreen'); ok(abs($wrect->{width} - $orect->{width}) < $threshold, 'width coordinate fullscreen'); ok(abs($wrect->{height} - $orect->{height}) < $threshold, 'height coordinate fullscreen'); +############################################################################### +# test if setting two windows in fullscreen mode at the same time does not work +############################################################################### + +$original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30); +my $swindow = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => $original_rect, + background_color => '#C0C0C0', +); + +$swindow->map; +sleep(0.25); + +ok(!$swindow->mapped, 'window not mapped while fullscreen window active'); + +$new_rect = $swindow->rect; +ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned"); + +sleep(0.25); +$swindow->fullscreen(1); +sleep(0.25); + +my $content = get_ws_content($tmp); + +my $fullscreen_windows = grep { $_->{fullscreen_mode} != 0 } @{$content}; +is($fullscreen_windows, 1, 'amount of fullscreen windows'); + +# clean up the workspace so that it will be cleaned when switching away +$i3->command('kill')->recv for (@{$content}); + diag( "Testing i3, Perl $], $^X" ); From 6bf55dc35664ad1cfb9d12950b76327856682bbd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 17:40:41 +0200 Subject: [PATCH 040/867] implement con_toggle_fullscreen --- include/con.h | 1 + src/con.c | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/include/con.h b/include/con.h index 2b01475a..076a5cf5 100644 --- a/include/con.h +++ b/include/con.h @@ -17,5 +17,6 @@ void con_detach(Con *con); enum { WINDOW_ADD = 0, WINDOW_REMOVE = 1 }; void con_fix_percent(Con *con, int action); +void con_toggle_fullscreen(Con *con); #endif diff --git a/src/con.c b/src/con.c index 2f61363b..83e89631 100644 --- a/src/con.c +++ b/src/con.c @@ -268,3 +268,25 @@ void con_fix_percent(Con *con, int action) { child->percent *= fix; } } + +void con_toggle_fullscreen(Con *con) { + Con *workspace, *fullscreen; + LOG("toggling fullscreen for %p / %s\n", con, con->name); + if (con->fullscreen_mode == CF_NONE) { + /* 1: check if there already is a fullscreen con */ + workspace = con_get_workspace(con); + if ((fullscreen = con_get_fullscreen_con(workspace)) != NULL) { + LOG("Not entering fullscreen mode, container (%p/%s) " + "already is in fullscreen mode\n", + fullscreen, fullscreen->name); + return; + } + + /* 2: enable fullscreen */ + con->fullscreen_mode = CF_OUTPUT; + } else { + /* 1: disable fullscreen */ + con->fullscreen_mode = CF_NONE; + } + LOG("mode now: %d\n", con->fullscreen_mode); +} From b93413ca49c59638e3d0c4854665ca4907a8a15c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 17:43:50 +0200 Subject: [PATCH 041/867] remove old fullscreen code --- include/client.h | 28 --------- src/client.c | 149 ----------------------------------------------- 2 files changed, 177 deletions(-) diff --git a/include/client.h b/include/client.h index 8f97eb44..627cab84 100644 --- a/include/client.h +++ b/include/client.h @@ -46,34 +46,6 @@ void client_kill(xcb_connection_t *conn, Client *window); bool client_matches_class_name(Client *client, char *to_class, char *to_title, char *to_title_ucs, int to_title_ucs_len); -/** - * Enters fullscreen mode for the given client. This is called by toggle_fullscreen - * and when moving a fullscreen client to another screen. - * - */ -void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global); - -/** - * Leaves fullscreen mode for the given client. This is called by toggle_fullscreen. - * - */ -void client_leave_fullscreen(xcb_connection_t *conn, Client *client); - -/** - * Toggles fullscreen mode for the given client. It updates the data - * structures and reconfigures (= resizes/moves) the client and its frame to - * the full size of the screen. When leaving fullscreen, re-rendering the - * layout is forced. - * - */ -void client_toggle_fullscreen(xcb_connection_t *conn, Client *client); - -/** - * Like client_toggle_fullscreen(), but putting it in global fullscreen-mode. - * - */ -void client_toggle_fullscreen_global(xcb_connection_t *conn, Client *client); - /** * Sets the position of the given client in the X stack to the highest (tiling * layer is always on the same position, so this doesn’t matter) below the diff --git a/src/client.c b/src/client.c index 9c136ca6..43fcd7b4 100644 --- a/src/client.c +++ b/src/client.c @@ -148,155 +148,6 @@ bool client_matches_class_name(Client *client, char *to_class, char *to_title, return true; } -/* - * Enters fullscreen mode for the given client. This is called by toggle_fullscreen - * and when moving a fullscreen client to another screen. - * - */ -void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global) { - Workspace *workspace; - Output *output; - Rect r; - - if (global) { - TAILQ_FOREACH(output, &outputs, outputs) { - if (!output->active) - continue; - - if (output->current_workspace->fullscreen_client == NULL) - continue; - - LOG("Not entering global fullscreen mode, there already " - "is a fullscreen client on output %s.\n", output->name); - return; - } - - r = (Rect) { UINT_MAX, UINT_MAX, 0,0 }; - Output *output; - - /* Set fullscreen_client for each active workspace. - * Expand the rectangle to contain all outputs. */ - TAILQ_FOREACH(output, &outputs, outputs) { - if (!output->active) - continue; - - output->current_workspace->fullscreen_client = client; - - /* Temporarily abuse width/heigth as coordinates of the lower right corner */ - if (r.x > output->rect.x) - r.x = output->rect.x; - if (r.y > output->rect.y) - r.y = output->rect.y; - if (r.x + r.width < output->rect.x + output->rect.width) - r.width = output->rect.x + output->rect.width; - if (r.y + r.height < output->rect.y + output->rect.height) - r.height = output->rect.y + output->rect.height; - } - - /* Putting them back to their original meaning */ - r.height -= r.x; - r.width -= r.y; - - LOG("Entering global fullscreen mode...\n"); - } else { - workspace = client->workspace; - if (workspace->fullscreen_client != NULL && workspace->fullscreen_client != client) { - LOG("Not entering fullscreen mode, there already is a fullscreen client.\n"); - return; - } - - workspace->fullscreen_client = client; - r = workspace->rect; - - LOG("Entering fullscreen mode...\n"); - } - - client->fullscreen = true; - - /* We just entered fullscreen mode, let’s configure the window */ - DLOG("child itself will be at %dx%d with size %dx%d\n", - r.x, r.y, r.width, r.height); - - xcb_set_window_rect(conn, client->frame, r); - - /* Child’s coordinates are relative to the parent (=frame) */ - r.x = 0; - r.y = 0; - xcb_set_window_rect(conn, client->child, r); - - /* Raise the window */ - uint32_t values[] = { XCB_STACK_MODE_ABOVE }; - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); - - fake_configure_notify(conn, r, client->child); - - xcb_flush(conn); -} - -/* - * Leaves fullscreen mode for the current client. This is called by toggle_fullscreen. - * - */ -void client_leave_fullscreen(xcb_connection_t *conn, Client *client) { - LOG("leaving fullscreen mode\n"); - client->fullscreen = false; - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) - if (ws->fullscreen_client == client) - ws->fullscreen_client = NULL; - - if (client_is_floating(client)) { - /* For floating clients it’s enough if we just reconfigure that window (in fact, - * re-rendering the layout will not update the client.) */ - reposition_client(conn, client); - resize_client(conn, client); - /* redecorate_window flushes */ - redecorate_window(conn, client); - } else { - client_set_below_floating(conn, client); - - /* Because the coordinates of the window haven’t changed, it would not be - re-configured if we don’t set the following flag */ - client->force_reconfigure = true; - /* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */ - render_layout(conn); - } - - xcb_flush(conn); -} - -/* - * Toggles fullscreen mode for the given client. It updates the data structures and - * reconfigures (= resizes/moves) the client and its frame to the full size of the - * screen. When leaving fullscreen, re-rendering the layout is forced. - * - */ -void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) { - /* dock clients cannot enter fullscreen mode */ - assert(!client->dock); - - if (!client->fullscreen) { - client_enter_fullscreen(conn, client, false); - } else { - client_leave_fullscreen(conn, client); - } -} - -/* - * Like client_toggle_fullscreen(), but putting it in global fullscreen-mode. - * - */ -void client_toggle_fullscreen_global(xcb_connection_t *conn, Client *client) { - /* dock clients cannot enter fullscreen mode */ - assert(!client->dock); - - if (!client->fullscreen) { - client_enter_fullscreen(conn, client, true); - } else { - client_leave_fullscreen(conn, client); - } -} - /* * Sets the position of the given client in the X stack to the highest (tiling layer is always * on the same position, so this doesn’t matter) below the first floating client, so that From 7f3a77ac6a78e82f87b16fae70f1281a707d3e65 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 17:46:11 +0200 Subject: [PATCH 042/867] loglevel bitmasks needs to be larger because we got more than 32 files --- Makefile | 2 +- include/log.h | 2 +- src/log.c | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 93c98e1e..b2b1b036 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ endif # Depend on the specific file (.c for each .o) and on all headers src/%.o: src/%.c ${HEADERS} echo "CC $<" - $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/$(shell basename $< .c)/ { print NR }' loglevels.tmp))" -c -o $@ $< + $(CC) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR }' loglevels.tmp))" -c -o $@ $< all: src/cfgparse.y.o src/cfgparse.yy.o src/cmdparse.y.o src/cmdparse.yy.o ${FILES} echo "LINK i3" diff --git a/include/log.h b/include/log.h index 6d529a00..9b284f0a 100644 --- a/include/log.h +++ b/include/log.h @@ -41,7 +41,7 @@ void set_verbosity(bool _verbose); * but only if the corresponding debug loglevel was activated. * */ -void debuglog(int lev, char *fmt, ...); +void debuglog(uint64_t lev, char *fmt, ...); /** * Logs the given message to stdout while prefixing the current time to it. diff --git a/src/log.c b/src/log.c index 28b51423..a899efcb 100644 --- a/src/log.c +++ b/src/log.c @@ -21,7 +21,7 @@ /* loglevels.h is autogenerated at make time */ #include "loglevels.h" -static uint32_t loglevel = 0; +static uint64_t loglevel = 0; static bool verbose = true; /** @@ -41,7 +41,7 @@ void set_verbosity(bool _verbose) { void add_loglevel(const char *level) { /* Handle the special loglevel "all" */ if (strcasecmp(level, "all") == 0) { - loglevel = UINT32_MAX; + loglevel = UINT64_MAX; return; } @@ -109,7 +109,7 @@ void errorlog(char *fmt, ...) { * This is to be called by DLOG() which includes filename/linenumber * */ -void debuglog(int lev, char *fmt, ...) { +void debuglog(uint64_t lev, char *fmt, ...) { va_list args; if ((loglevel & lev) == 0) From 42bed06b9a59d574b1e183b07ccf069f80f01eee Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 17:47:07 +0200 Subject: [PATCH 043/867] include match.h in all.h --- include/all.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/all.h b/include/all.h index 84e4a2f9..acf5e337 100644 --- a/include/all.h +++ b/include/all.h @@ -49,5 +49,6 @@ #include "load_layout.h" #include "render.h" #include "window.h" +#include "match.h" #endif From b0f47b25a0e3eb35cefb7464d9d513018bd01ec8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 18:26:46 +0200 Subject: [PATCH 044/867] throw out some old code from manage.c, cleanups --- include/xcb.h | 3 + src/manage.c | 229 ++++++++------------------------------------------ src/xcb.c | 20 +++++ 3 files changed, 58 insertions(+), 194 deletions(-) diff --git a/include/xcb.h b/include/xcb.h index 004a64de..a7aeaf21 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -172,4 +172,7 @@ int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *t */ void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r); + +bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom); + #endif diff --git a/src/manage.c b/src/manage.c index 940013e2..b1b9e9d6 100644 --- a/src/manage.c +++ b/src/manage.c @@ -169,33 +169,31 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if (xcb_request_check(conn, rcookie) != NULL) { LOG("Could not reparent the window, aborting\n"); goto out; - //xcb_destroy_window(conn, nc->frame); } - xcb_atom_t *state; - xcb_get_property_reply_t *preply; - if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL && - (state = xcb_get_property_value(preply)) != NULL) { - /* Check all set _NET_WM_STATEs */ - for (int i = 0; i < xcb_get_property_value_length(preply); i++) { - if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN]) - continue; - con_toggle_fullscreen(nc); - break; - } - } + xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, state_cookie, NULL); + if (xcb_reply_contains_atom(reply, atoms[_NET_WM_STATE_FULLSCREEN])) + con_toggle_fullscreen(nc); + reply = xcb_get_property_reply(conn, wm_type_cookie, NULL); + if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DOCK])) + LOG("this window is a dock\n"); + + if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DIALOG]) || + xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_UTILITY]) || + xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_TOOLBAR]) || + xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_SPLASH])) + LOG("This window is a dialog window\n"); + + + /* Put the client inside the save set. Upon termination (whether killed or + * normal exit does not matter) of the window manager, these clients will + * be correctly reparented to their most closest living ancestor (= + * cleanup) */ xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window); tree_render(); -#if 0 - /* Reparent the window and add it to our list of managed windows */ - reparent_window(conn, window, attr->visual, geom->root, geom->depth, - geom->x, geom->y, geom->width, geom->height, - geom->border_width); -#endif - free(geom); out: free(attr); @@ -203,119 +201,18 @@ out: } #if 0 -/* - * reparent_window() gets called when a new window was opened and becomes a child of the root - * window, or it gets called by us when we manage the already existing windows at startup. - * - * Essentially, this is the point where we take over control. - * - */ void reparent_window(xcb_connection_t *conn, xcb_window_t child, xcb_visualid_t visual, xcb_window_t root, uint8_t depth, int16_t x, int16_t y, uint16_t width, uint16_t height, uint32_t border_width) { - xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie, - utf8_title_cookie, title_cookie, - class_cookie, leader_cookie; - uint32_t mask = 0; - uint32_t values[3]; - uint16_t original_height = height; - bool map_frame = true; - - /* We are interested in property changes */ - mask = XCB_CW_EVENT_MASK; - values[0] = CHILD_EVENT_MASK; - xcb_change_window_attributes(conn, child, mask, values); - - /* Place requests for properties ASAP */ - wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX); - strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX); - state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX); - utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128); - leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX); - title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128); - class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128); - - Client *new = table_get(&by_child, child); - - /* Events for already managed windows should already be filtered in manage_window() */ - assert(new == NULL); - - LOG("Managing window 0x%08x\n", child); - DLOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height); - new = scalloc(sizeof(Client)); - new->force_reconfigure = true; - - /* Update the data structures */ - Client *old_focused = CUR_CELL->currently_focused; - - new->container = CUR_CELL; - new->workspace = new->container->workspace; - - /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */ + /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */ width = max(width, 75); height = max(height, 50); - new->frame = xcb_generate_id(conn); - new->child = child; - new->rect.width = width; - new->rect.height = height; - new->width_increment = 1; - new->height_increment = 1; - new->border_width = border_width; - /* Pre-initialize the values for floating */ - new->floating_rect.x = -1; - new->floating_rect.width = width; - new->floating_rect.height = height; - if (config.default_border != NULL) client_init_border(conn, new, config.default_border[1]); - mask = 0; - - /* Don’t generate events for our new window, it should *not* be managed */ - mask |= XCB_CW_OVERRIDE_REDIRECT; - values[0] = 1; - - /* We want to know when… */ - mask |= XCB_CW_EVENT_MASK; - values[1] = FRAME_EVENT_MASK; - - i3Font *font = load_font(conn, config.font); - width = min(width, c_ws->rect.x + c_ws->rect.width); - height = min(height, c_ws->rect.y + c_ws->rect.height); - - Rect framerect = {x, y, - width + 2 + 2, /* 2 px border at each side */ - height + 2 + 2 + font->height}; /* 2 px border plus font’s height */ - - /* Yo dawg, I heard you like windows, so I create a window around your window… */ - new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values); - - /* Put the client inside the save set. Upon termination (whether killed or normal exit - does not matter) of the window manager, these clients will be correctly reparented - to their most closest living ancestor (= cleanup) */ - xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child); - - /* Generate a graphics context for the titlebar */ - new->titlegc = xcb_generate_id(conn); - xcb_create_gc(conn, new->titlegc, new->frame, 0, 0); - - /* Moves the original window into the new frame we've created for it */ - new->awaiting_useless_unmap = true; - xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height); - if (xcb_request_check(conn, cookie) != NULL) { - DLOG("Could not reparent the window, aborting\n"); - xcb_destroy_window(conn, new->frame); - free(new); - return; - } - - /* Put our data structure (Client) into the table */ - table_put(&by_parent, new->frame, new); - table_put(&by_child, child, new); - /* We need to grab the mouse buttons for click to focus */ xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, @@ -327,36 +224,22 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, 3 /* right mouse button */, XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); - /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */ - xcb_atom_t *atom; - xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL); - if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) { - for (int i = 0; i < xcb_get_property_value_length(preply); i++) - if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) { - DLOG("Window is a dock.\n"); - Output *t_out = get_output_containing(x, y); - if (t_out != c_ws->output) { - DLOG("Dock client requested to be on output %s by geometry (%d, %d)\n", - t_out->name, x, y); - new->workspace = t_out->current_workspace; - } - new->dock = true; - new->borderless = true; - new->titlebar_position = TITLEBAR_OFF; - new->force_reconfigure = true; - new->container = NULL; - SLIST_INSERT_HEAD(&(t_out->dock_clients), new, dock_clients); - /* If it’s a dock we can’t make it float, so we break */ - new->floating = FLOATING_AUTO_OFF; - break; - } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] || - atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] || - atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] || - atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) { - /* Set the dialog window to automatically floating, will be used below */ - new->floating = FLOATING_AUTO_ON; - DLOG("dialog/utility/toolbar/splash window, automatically floating\n"); - } + if (dock) { + DLOG("Window is a dock.\n"); + Output *t_out = get_output_containing(x, y); + if (t_out != c_ws->output) { + DLOG("Dock client requested to be on output %s by geometry (%d, %d)\n", + t_out->name, x, y); + new->workspace = t_out->current_workspace; + } + new->dock = true; + new->borderless = true; + new->titlebar_position = TITLEBAR_OFF; + new->force_reconfigure = true; + new->container = NULL; + SLIST_INSERT_HEAD(&(t_out->dock_clients), new, dock_clients); + /* If it’s a dock we can’t make it float, so we break */ + new->floating = FLOATING_AUTO_OFF; } /* All clients which have a leader should be floating */ @@ -465,34 +348,6 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } } - if (new->workspace->fullscreen_client != NULL) { - DLOG("Setting below fullscreen window\n"); - - /* If we are in fullscreen, we should place the window below - * the fullscreen window to not be annoying */ - uint32_t values[] = { - new->workspace->fullscreen_client->frame, - XCB_STACK_MODE_BELOW - }; - xcb_configure_window(conn, new->frame, - XCB_CONFIG_WINDOW_SIBLING | - XCB_CONFIG_WINDOW_STACK_MODE, values); - } - - /* Insert into the currently active container, if it’s not a dock window */ - if (!new->dock && !client_is_floating(new)) { - /* Insert after the old active client, if existing. If it does not exist, the - container is empty and it does not matter, where we insert it */ - if (old_focused != NULL && !old_focused->dock) - CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients); - else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients); - - if (new->container->workspace->fullscreen_client != NULL) - SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients); - else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients); - - client_set_below_floating(conn, new); - } if (client_is_floating(new)) { SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients); @@ -539,20 +394,6 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, new->initialized = true; - /* Check if the window already got the fullscreen hint set */ - xcb_atom_t *state; - if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL && - (state = xcb_get_property_value(preply)) != NULL) - /* Check all set _NET_WM_STATEs */ - for (int i = 0; i < xcb_get_property_value_length(preply); i++) { - if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN]) - continue; - /* If the window got the fullscreen state, we just toggle fullscreen - and don’t event bother to redraw the layout – that would not change - anything anyways */ - client_toggle_fullscreen(conn, new); - goto map; - } render_layout(conn); diff --git a/src/xcb.c b/src/xcb.c index a57bad04..3da1081d 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -328,3 +328,23 @@ void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r) { /* ignore events which are generated because we configured a window */ add_ignore_event(cookie.sequence); } + +/* + * Returns true if the given reply contains the given atom. + * + */ +bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom) { + if (prop == NULL || xcb_get_property_value_length(prop) == 0) + return false; + + xcb_atom_t *atoms; + if ((atoms = xcb_get_property_value(prop)) == NULL) + return false; + + for (int i = 0; i < xcb_get_property_value_length(prop); i++) + if (atoms[i] == atom) + return true; + + return false; + +} From fdd44dcadaa79d6faad4ff1e7d88144dd7edd87a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 19:29:27 +0200 Subject: [PATCH 045/867] make the fullscreen testcase test the 'fullscreen' command, too --- testcases/t/02-fullscreen.t | 40 ++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/testcases/t/02-fullscreen.t b/testcases/t/02-fullscreen.t index 4ca40e3f..cf542ee7 100644 --- a/testcases/t/02-fullscreen.t +++ b/testcases/t/02-fullscreen.t @@ -1,7 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -use Test::More tests => 19; +use Test::More tests => 24; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -21,6 +21,10 @@ my $i3 = i3("/tmp/nestedcons"); my $tmp = get_unused_workspace(); $i3->command("workspace $tmp")->recv; +sub fullscreen_windows { + scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($tmp)} +} + # get the output of this workspace my $tree = $i3->get_workspaces->recv; my @outputs = @{$tree->{nodes}}; @@ -134,16 +138,38 @@ ok(!$swindow->mapped, 'window not mapped while fullscreen window active'); $new_rect = $swindow->rect; ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned"); -sleep(0.25); $swindow->fullscreen(1); -sleep(0.25); +sleep 0.25; -my $content = get_ws_content($tmp); +is(fullscreen_windows(), 1, 'amount of fullscreen windows'); -my $fullscreen_windows = grep { $_->{fullscreen_mode} != 0 } @{$content}; -is($fullscreen_windows, 1, 'amount of fullscreen windows'); +$window->fullscreen(0); +sleep 0.25; +is(fullscreen_windows(), 0, 'amount of fullscreen windows'); + +ok($swindow->mapped, 'window mapped after other fullscreen ended'); + +########################################################################### +# as $swindow is out of state at the moment (it requested to be fullscreen, +# but the WM denied), we check what happens if we go out of fullscreen now +# (nothing should happen) +########################################################################### + +$swindow->fullscreen(0); +sleep 0.25; + +is(fullscreen_windows(), 0, 'amount of fullscreen windows after disabling'); + +$i3->command('fullscreen')->recv; + +is(fullscreen_windows(), 1, 'amount of fullscreen windows after fullscreen command'); + +$i3->command('fullscreen')->recv; + +is(fullscreen_windows(), 0, 'amount of fullscreen windows after fullscreen command'); # clean up the workspace so that it will be cleaned when switching away -$i3->command('kill')->recv for (@{$content}); +$i3->command('kill')->recv for (@{get_ws_content($tmp)}); + diag( "Testing i3, Perl $], $^X" ); From 0ea85c1b9d720a229c8ac02aa0cde92fcb28a66e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 19:29:44 +0200 Subject: [PATCH 046/867] implement 'fullscreen' command --- src/cmdparse.y | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index acac73e2..e9b2538c 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -238,7 +238,6 @@ operation: /*| reload | restart | mark - | fullscreen | layout | border | mode @@ -249,6 +248,7 @@ operation: | focus | kill | open + | fullscreen ; exec: @@ -285,14 +285,14 @@ kill: owindow *current; printf("killing!\n"); - /* TODO: check if the match is empty, not if the result is empty */ + /* check if the match is empty, not if the result is empty */ if (match_is_empty(¤t_match)) tree_close(focused); else { - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - tree_close(current->con); - } + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + tree_close(current->con); + } } } @@ -314,3 +314,22 @@ open: tree_open_con(NULL); } ; + +fullscreen: + TOK_FULLSCREEN + { + printf("toggling fullscreen\n"); + owindow *current; + + /* check if the match is empty, not if the result is empty */ + if (match_is_empty(¤t_match)) + con_toggle_fullscreen(focused); + else { + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + con_toggle_fullscreen(current->con); + } + } + + } + ; From a2e3bb1cdd63cc92c569e8a1d370693df8035ff6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 15:20:38 +0100 Subject: [PATCH 047/867] make testcases use AnyEvent::I3 --- testcases/t/05-ipc.t | 16 ++++--------- testcases/t/06-focus.t | 23 +++++++------------ testcases/t/07-move.t | 22 +++++++----------- testcases/t/08-focus-stack.t | 13 ++++------- testcases/t/09-stacking.t | 14 ++++-------- testcases/t/10-dock.t | 1 - testcases/t/11-goto.t | 16 +++++-------- testcases/t/12-floating-resize.t | 17 +++++--------- testcases/t/13-urgent.t | 23 +++++++------------ testcases/t/14-client-leader.t | 18 +++++---------- testcases/t/15-ipc-workspaces.t | 39 ++++---------------------------- testcases/t/lib/i3test.pm | 29 ------------------------ 12 files changed, 61 insertions(+), 170 deletions(-) diff --git a/testcases/t/05-ipc.t b/testcases/t/05-ipc.t index ac52911a..8f427938 100644 --- a/testcases/t/05-ipc.t +++ b/testcases/t/05-ipc.t @@ -1,7 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -use Test::More tests => 4; +use Test::More tests => 3; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -9,6 +9,7 @@ use Time::HiRes qw(sleep); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); @@ -17,19 +18,14 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); - -isa_ok($sock, 'IO::Socket::UNIX'); - +my $i3 = i3; ##################################################################### # Ensure IPC works by switching workspaces ##################################################################### # Switch to the first workspace to get a clean testing environment -$sock->write(i3test::format_ipc_command("1")); - -sleep(0.25); +$i3->command('1')->recv; # Create a window so we can get a focus different from NULL my $window = i3test::open_standard_window($x); @@ -41,9 +37,7 @@ my $focus = $x->input_focus; diag("old focus = $focus"); # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; my $new_focus = $x->input_focus; isnt($focus, $new_focus, "Focus changed"); diff --git a/testcases/t/06-focus.t b/testcases/t/06-focus.t index 5ca3e062..a95e0e40 100644 --- a/testcases/t/06-focus.t +++ b/testcases/t/06-focus.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 14; +use Test::More tests => 13; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -12,29 +12,25 @@ use Time::HiRes qw(sleep); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; ##################################################################### # Create two windows and make sure focus switching works ##################################################################### # Change mode of the container to "default" for following tests -$sock->write(i3test::format_ipc_command("d")); -sleep(0.25); +$i3->command('d')->recv; my $top = i3test::open_standard_window($x); my $mid = i3test::open_standard_window($x); @@ -52,8 +48,7 @@ diag("bottom id = " . $bottom->id); sub focus_after { my $msg = shift; - $sock->write(i3test::format_ipc_command($msg)); - sleep(0.5); + $i3->command($msg)->recv; return $x->input_focus; } @@ -81,8 +76,7 @@ is($focus, $top->id, "Top window focused (wrapping to the bottom works)"); ############################################### # Switch to the 10. workspace -$sock->write(i3test::format_ipc_command("10")); -sleep 0.25; +$i3->command('10')->recv; $top = i3test::open_standard_window($x); $bottom = i3test::open_standard_window($x); @@ -102,8 +96,7 @@ is($focus, $top->id, "Top window focused"); # Same thing, but left/right instead of top/bottom # Switch to the 11. workspace -$sock->write(i3test::format_ipc_command("11")); -sleep 0.25; +$i3->command('11')->recv; my $left = i3test::open_standard_window($x); my $right = i3test::open_standard_window($x); diff --git a/testcases/t/07-move.t b/testcases/t/07-move.t index 10cb9830..efd3df15 100644 --- a/testcases/t/07-move.t +++ b/testcases/t/07-move.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 10; +use Test::More tests => 8; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -12,21 +12,18 @@ use Time::HiRes qw(sleep); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; ##################################################################### # Create two windows and make sure focus switching works @@ -50,8 +47,7 @@ diag("bottom id = " . $bottom->id); sub focus_after { my $msg = shift; - $sock->write(i3test::format_ipc_command($msg)); - sleep(0.5); + $i3->command($msg)->recv; return $x->input_focus; } @@ -82,9 +78,7 @@ is($focus, $top->id, "Top window focused"); # Move window cross-workspace ##################################################################### -$sock->write(i3test::format_ipc_command("m12")); -$sock->write(i3test::format_ipc_command("t")); -$sock->write(i3test::format_ipc_command("m13")); -$sock->write(i3test::format_ipc_command("12")); -$sock->write(i3test::format_ipc_command("13")); +for my $cmd (qw(m12 t m13 12 13)) { + $i3->command($cmd)->recv; +} ok(1, "Still living"); diff --git a/testcases/t/08-focus-stack.t b/testcases/t/08-focus-stack.t index 370369d8..4ae92407 100644 --- a/testcases/t/08-focus-stack.t +++ b/testcases/t/08-focus-stack.t @@ -3,7 +3,7 @@ # Checks if the focus is correctly restored, when creating a floating client # over an unfocused tiling client and destroying the floating one again. -use Test::More tests => 6; +use Test::More tests => 4; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -11,28 +11,25 @@ use Time::HiRes qw(sleep); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Window') or BAIL_OUT('Could not load X11::XCB::Window'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; my $tiled_left = i3test::open_standard_window($x); my $tiled_right = i3test::open_standard_window($x); sleep(0.25); -$sock->write(i3test::format_ipc_command("ml")); +$i3->command('ml')->recv; # Get input focus before creating the floating window my $focus = $x->input_focus; diff --git a/testcases/t/09-stacking.t b/testcases/t/09-stacking.t index 8f40047e..59d2e6f4 100644 --- a/testcases/t/09-stacking.t +++ b/testcases/t/09-stacking.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 24; +use Test::More tests => 22; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -12,21 +12,18 @@ use Time::HiRes qw(sleep); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; ##################################################################### # Create two windows and make sure focus switching works @@ -50,8 +47,7 @@ diag("bottom id = " . $bottom->id); sub focus_after { my $msg = shift; - $sock->write(i3test::format_ipc_command($msg)); - sleep(0.25); + $i3->command($msg)->recv; return $x->input_focus; } diff --git a/testcases/t/10-dock.t b/testcases/t/10-dock.t index b1b7bfcb..52063131 100644 --- a/testcases/t/10-dock.t +++ b/testcases/t/10-dock.t @@ -12,7 +12,6 @@ use i3test; use List::Util qw(first); BEGIN { - #use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t index 47675903..9b06112b 100644 --- a/testcases/t/11-goto.t +++ b/testcases/t/11-goto.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 9; +use Test::More tests => 7; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -13,21 +13,18 @@ use FindBin; use Digest::SHA1 qw(sha1_base64); use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; ##################################################################### # Create two windows and make sure focus switching works @@ -51,8 +48,7 @@ diag("bottom id = " . $bottom->id); sub focus_after { my $msg = shift; - $sock->write(i3test::format_ipc_command($msg)); - sleep(0.5); + $i3->command($msg)->recv; return $x->input_focus; } @@ -74,7 +70,7 @@ my $random_mark = sha1_base64(rand()); $focus = focus_after("goto $random_mark"); is($focus, $mid->id, "focus unchanged"); -$sock->write(i3test::format_ipc_command("mark $random_mark")); +$i3->command("mark $random_mark")->recv; $focus = focus_after("k"); is($focus, $top->id, "Top window focused"); diff --git a/testcases/t/12-floating-resize.t b/testcases/t/12-floating-resize.t index d908d345..74f66535 100644 --- a/testcases/t/12-floating-resize.t +++ b/testcases/t/12-floating-resize.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 17; +use Test::More tests => 15; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -13,21 +13,18 @@ use FindBin; use Digest::SHA1 qw(sha1_base64); use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep 0.25; +$i3->command('9')->recv; ##################################################################### # Create a floating window and see if resizing works @@ -78,13 +75,11 @@ sub test_resize { test_resize; # Test borderless -$sock->write(i3test::format_ipc_command("bb")); -sleep 0.25; +$i3->command('bb')->recv; test_resize; # Test with 1-px-border -$sock->write(i3test::format_ipc_command("bp")); -sleep 0.25; +$i3->command('bp')->recv; test_resize; diff --git a/testcases/t/13-urgent.t b/testcases/t/13-urgent.t index 7dee21c6..5fce6aee 100644 --- a/testcases/t/13-urgent.t +++ b/testcases/t/13-urgent.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 9; +use Test::More tests => 7; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -13,21 +13,18 @@ use FindBin; use Digest::SHA1 qw(sha1_base64); use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep 0.25; +$i3->command('9')->recv; ##################################################################### # Create two windows and put them in stacking mode @@ -38,8 +35,7 @@ sleep 0.25; my $bottom = i3test::open_standard_window($x); sleep 0.25; -$sock->write(i3test::format_ipc_command("s")); -sleep 0.25; +$i3->command('s')->recv; ##################################################################### # Add the urgency hint, switch to a different workspace and back again @@ -47,12 +43,9 @@ sleep 0.25; $top->add_hint('urgency'); sleep 1; -$sock->write(i3test::format_ipc_command("1")); -sleep 0.25; -$sock->write(i3test::format_ipc_command("9")); -sleep 0.25; -$sock->write(i3test::format_ipc_command("1")); -sleep 0.25; +$i3->command('1')->recv; +$i3->command('9')->recv; +$i3->command('1')->recv; my $std = i3test::open_standard_window($x); sleep 0.25; diff --git a/testcases/t/14-client-leader.t b/testcases/t/14-client-leader.t index ead52764..b9160131 100644 --- a/testcases/t/14-client-leader.t +++ b/testcases/t/14-client-leader.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 5; +use Test::More tests => 3; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -13,21 +13,17 @@ use FindBin; use Digest::SHA1 qw(sha1_base64); use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; - -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep 0.25; +$i3->command('9')->recv; ##################################################################### # Create a parent window @@ -48,8 +44,7 @@ sleep 0.25; # Switch workspace to 10 and open a child window. It should be positioned # on workspace 9. ######################################################################### -$sock->write(i3test::format_ipc_command("10")); -sleep 0.25; +$i3->command('10')->recv; my $child = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, @@ -66,7 +61,6 @@ sleep 0.25; isnt($x->input_focus, $child->id, "Child window focused"); # Switch back -$sock->write(i3test::format_ipc_command("9")); -sleep 0.25; +$i3->command('9')->recv; is($x->input_focus, $child->id, "Child window focused"); diff --git a/testcases/t/15-ipc-workspaces.t b/testcases/t/15-ipc-workspaces.t index 01947094..4e2c0e8d 100644 --- a/testcases/t/15-ipc-workspaces.t +++ b/testcases/t/15-ipc-workspaces.t @@ -1,52 +1,21 @@ #!perl # vim:ts=4:sw=4:expandtab -use Test::More tests => 8; +use Test::More tests => 3; use Test::Exception; -use Data::Dumper; -use JSON::XS; use List::MoreUtils qw(all); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; -BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); - use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); -} - -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; #################### # Request workspaces #################### -# message type 1 is GET_WORKSPACES -my $message = "i3-ipc" . pack("LL", 0, 1); -$sock->write($message); - -####################################### -# Test the reply format for correctness -####################################### - -# The following lines duplicate functionality from recv_ipc_command -# to have it included in the test-suite. -my $buffer; -$sock->read($buffer, length($message)); -is(substr($buffer, 0, length("i3-ipc")), "i3-ipc", "ipc message received"); -my ($len, $type) = unpack("LL", substr($buffer, 6)); -is($type, 1, "correct reply type"); - -# read the payload -$sock->read($buffer, $len); -my $workspaces; - -######################### -# Actually test the reply -######################### - -lives_ok { $workspaces = decode_json($buffer) } 'JSON could be decoded'; +my $workspaces = $i3->get_workspaces->recv; ok(@{$workspaces} > 0, "More than zero workspaces found"); diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index e98685f7..684e8118 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -35,35 +35,6 @@ sub open_standard_window { return $window; } -sub format_ipc_command { - my $msg = shift; - my $len; - - { use bytes; $len = length($msg); } - - my $message = "i3-ipc" . pack("LL", $len, 0) . $msg; - - return $message; -} - -sub recv_ipc_command { - my ($sock, $expected) = @_; - - my $buffer; - # header is 14 bytes ("i3-ipc" + 32 bit + 32 bit) - $sock->read($buffer, 14); - return undef unless substr($buffer, 0, length("i3-ipc")) eq "i3-ipc"; - - my ($len, $type) = unpack("LL", substr($buffer, 6)); - - return undef unless $type == $expected; - - # read the payload - $sock->read($buffer, $len); - - decode_json($buffer) -} - sub get_workspace_names { my $i3 = i3("/tmp/nestedcons"); # TODO: use correct command as soon as AnyEvent::i3 is updated From 206e1ed0415b737df5fdefa20fe088e024da5679 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 22:49:26 +0200 Subject: [PATCH 048/867] less boilerplate by using Test::Kit and -It/lib in Makefile --- testcases/Makefile | 5 ++++- testcases/t/01-tile.t | 4 +--- testcases/t/02-fullscreen.t | 13 ++----------- testcases/t/03-unmanaged.t | 4 +--- testcases/t/04-floating.t | 7 +------ testcases/t/05-ipc.t | 9 +-------- testcases/t/06-focus.t | 8 +------- testcases/t/07-move.t | 8 +------- testcases/t/08-focus-stack.t | 8 +------- testcases/t/09-stacking.t | 8 +------- testcases/t/10-dock.t | 7 +------ testcases/t/11-goto.t | 8 +------- testcases/t/12-floating-resize.t | 9 +-------- testcases/t/13-urgent.t | 9 +-------- testcases/t/14-client-leader.t | 9 +-------- testcases/t/15-ipc-workspaces.t | 7 +------ testcases/t/16-nestedcons.t | 5 +---- testcases/t/17-workspace.t | 6 +----- testcases/t/18-openkill.t | 7 +------ testcases/t/19-match.t | 7 +------ testcases/t/20-multiple-cmds.t | 7 +------ testcases/t/lib/i3test.pm | 12 +++++++++++- 22 files changed, 36 insertions(+), 131 deletions(-) diff --git a/testcases/Makefile b/testcases/Makefile index 1e7a9191..0e78f6b2 100644 --- a/testcases/Makefile +++ b/testcases/Makefile @@ -1,4 +1,7 @@ test: - PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" t/16*.t + PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/03* t/05* all: test + +testfull: + PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/01* t/02* t/03* t/05* t/17* t/18* t/19* t/20* diff --git a/testcases/t/01-tile.t b/testcases/t/01-tile.t index a5aa143d..b40eae24 100644 --- a/testcases/t/01-tile.t +++ b/testcases/t/01-tile.t @@ -1,9 +1,7 @@ #!perl -use Test::More tests => 4; -use Test::Deep; +use i3test tests => 4; use X11::XCB qw(:all); -use Data::Dumper; use Time::HiRes qw(sleep); BEGIN { diff --git a/testcases/t/02-fullscreen.t b/testcases/t/02-fullscreen.t index cf542ee7..98194ba2 100644 --- a/testcases/t/02-fullscreen.t +++ b/testcases/t/02-fullscreen.t @@ -1,20 +1,11 @@ #!perl # vim:ts=4:sw=4:expandtab -use Test::More tests => 24; -use Test::Deep; +use i3test tests => 24; use X11::XCB qw(:all); -use Data::Dumper; -use FindBin; -use lib "$FindBin::Bin/lib"; -use i3test; -use AnyEvent::I3; use List::Util qw(first); -use v5.10; - -# We use relatively long sleeps (1/4 second) to make sure the window manager -# reacted. use Time::HiRes qw(sleep); +use v5.10; my $i3 = i3("/tmp/nestedcons"); diff --git a/testcases/t/03-unmanaged.t b/testcases/t/03-unmanaged.t index 3cd9833a..3e8cdfc1 100644 --- a/testcases/t/03-unmanaged.t +++ b/testcases/t/03-unmanaged.t @@ -1,10 +1,8 @@ #!perl # vim:ts=4:sw=4:expandtab -use Test::More tests => 5; -use Test::Deep; +use i3test tests => 5; use X11::XCB qw(:all); -use Data::Dumper; BEGIN { use_ok('X11::XCB::Window'); diff --git a/testcases/t/04-floating.t b/testcases/t/04-floating.t index 9049b9dc..4463bcbe 100644 --- a/testcases/t/04-floating.t +++ b/testcases/t/04-floating.t @@ -1,14 +1,9 @@ #!perl # vim:ts=4:sw=4:expandtab -use Test::More tests => 10; -use Test::Deep; +use i3test tests => 10; use X11::XCB qw(:all); -use Data::Dumper; use Time::HiRes qw(sleep); -use FindBin; -use lib "$FindBin::Bin/lib"; -use i3test; BEGIN { use_ok('X11::XCB::Window'); diff --git a/testcases/t/05-ipc.t b/testcases/t/05-ipc.t index 8f427938..13ebc4b2 100644 --- a/testcases/t/05-ipc.t +++ b/testcases/t/05-ipc.t @@ -1,18 +1,11 @@ #!perl # vim:ts=4:sw=4:expandtab -use Test::More tests => 3; -use Test::Deep; +use i3test tests => 3; use X11::XCB qw(:all); -use Data::Dumper; use Time::HiRes qw(sleep); -use FindBin; -use lib "$FindBin::Bin/lib"; -use i3test; -use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } diff --git a/testcases/t/06-focus.t b/testcases/t/06-focus.t index a95e0e40..b91f8cc5 100644 --- a/testcases/t/06-focus.t +++ b/testcases/t/06-focus.t @@ -4,15 +4,9 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 13; -use Test::Deep; +use i3test tests => 13; use X11::XCB qw(:all); -use Data::Dumper; use Time::HiRes qw(sleep); -use FindBin; -use lib "$FindBin::Bin/lib"; -use i3test; -use AnyEvent::I3; BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); diff --git a/testcases/t/07-move.t b/testcases/t/07-move.t index efd3df15..c852d001 100644 --- a/testcases/t/07-move.t +++ b/testcases/t/07-move.t @@ -4,15 +4,9 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 8; -use Test::Deep; +use i3test tests => 8; use X11::XCB qw(:all); -use Data::Dumper; use Time::HiRes qw(sleep); -use FindBin; -use lib "$FindBin::Bin/lib"; -use i3test; -use AnyEvent::I3; BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); diff --git a/testcases/t/08-focus-stack.t b/testcases/t/08-focus-stack.t index 4ae92407..9dd7726f 100644 --- a/testcases/t/08-focus-stack.t +++ b/testcases/t/08-focus-stack.t @@ -3,15 +3,9 @@ # Checks if the focus is correctly restored, when creating a floating client # over an unfocused tiling client and destroying the floating one again. -use Test::More tests => 4; -use Test::Deep; +use i3test tests => 4; use X11::XCB qw(:all); -use Data::Dumper; use Time::HiRes qw(sleep); -use FindBin; -use lib "$FindBin::Bin/lib"; -use i3test; -use AnyEvent::I3; BEGIN { use_ok('X11::XCB::Window') or BAIL_OUT('Could not load X11::XCB::Window'); diff --git a/testcases/t/09-stacking.t b/testcases/t/09-stacking.t index 59d2e6f4..c809be9e 100644 --- a/testcases/t/09-stacking.t +++ b/testcases/t/09-stacking.t @@ -4,15 +4,9 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 22; -use Test::Deep; +use i3test tests => 22; use X11::XCB qw(:all); -use Data::Dumper; use Time::HiRes qw(sleep); -use FindBin; -use lib "$FindBin::Bin/lib"; -use i3test; -use AnyEvent::I3; BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); diff --git a/testcases/t/10-dock.t b/testcases/t/10-dock.t index 52063131..83816296 100644 --- a/testcases/t/10-dock.t +++ b/testcases/t/10-dock.t @@ -1,14 +1,9 @@ #!perl # vim:ts=4:sw=4:expandtab -use Test::More tests => 2; -use Test::Deep; +use i3test tests => 2; use X11::XCB qw(:all); -use Data::Dumper; use Time::HiRes qw(sleep); -use FindBin; -use lib "$FindBin::Bin/lib"; -use i3test; use List::Util qw(first); BEGIN { diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t index 9b06112b..ea17b406 100644 --- a/testcases/t/11-goto.t +++ b/testcases/t/11-goto.t @@ -4,16 +4,10 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 7; -use Test::Deep; +use i3test tests => 7; use X11::XCB qw(:all); -use Data::Dumper; use Time::HiRes qw(sleep); -use FindBin; use Digest::SHA1 qw(sha1_base64); -use lib "$FindBin::Bin/lib"; -use i3test; -use AnyEvent::I3; BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); diff --git a/testcases/t/12-floating-resize.t b/testcases/t/12-floating-resize.t index 74f66535..0d309c68 100644 --- a/testcases/t/12-floating-resize.t +++ b/testcases/t/12-floating-resize.t @@ -4,16 +4,9 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 15; -use Test::Deep; +use i3test tests => 15; use X11::XCB qw(:all); -use Data::Dumper; use Time::HiRes qw(sleep); -use FindBin; -use Digest::SHA1 qw(sha1_base64); -use lib "$FindBin::Bin/lib"; -use i3test; -use AnyEvent::I3; BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); diff --git a/testcases/t/13-urgent.t b/testcases/t/13-urgent.t index 5fce6aee..cf847456 100644 --- a/testcases/t/13-urgent.t +++ b/testcases/t/13-urgent.t @@ -4,16 +4,9 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 7; -use Test::Deep; +use i3test tests => 7; use X11::XCB qw(:all); -use Data::Dumper; use Time::HiRes qw(sleep); -use FindBin; -use Digest::SHA1 qw(sha1_base64); -use lib "$FindBin::Bin/lib"; -use i3test; -use AnyEvent::I3; BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); diff --git a/testcases/t/14-client-leader.t b/testcases/t/14-client-leader.t index b9160131..bf07b927 100644 --- a/testcases/t/14-client-leader.t +++ b/testcases/t/14-client-leader.t @@ -4,16 +4,9 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 3; -use Test::Deep; +use i3test tests => 3; use X11::XCB qw(:all); -use Data::Dumper; use Time::HiRes qw(sleep); -use FindBin; -use Digest::SHA1 qw(sha1_base64); -use lib "$FindBin::Bin/lib"; -use i3test; -use AnyEvent::I3; BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); diff --git a/testcases/t/15-ipc-workspaces.t b/testcases/t/15-ipc-workspaces.t index 4e2c0e8d..3cf3e562 100644 --- a/testcases/t/15-ipc-workspaces.t +++ b/testcases/t/15-ipc-workspaces.t @@ -1,13 +1,8 @@ #!perl # vim:ts=4:sw=4:expandtab -use Test::More tests => 3; -use Test::Exception; +use i3test tests => 2; use List::MoreUtils qw(all); -use FindBin; -use lib "$FindBin::Bin/lib"; -use i3test; -use AnyEvent::I3; my $i3 = i3; diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t index 39087e85..3159929b 100644 --- a/testcases/t/16-nestedcons.t +++ b/testcases/t/16-nestedcons.t @@ -1,11 +1,8 @@ #!perl # vim:ts=4:sw=4:expandtab -use Test::More tests => 8; -use Test::Deep; +use i3test tests => 8; use List::MoreUtils qw(all none); -use Data::Dumper; -use AnyEvent::I3; my $i3 = i3("/tmp/nestedcons"); diff --git a/testcases/t/17-workspace.t b/testcases/t/17-workspace.t index 227b754d..c350d9d9 100644 --- a/testcases/t/17-workspace.t +++ b/testcases/t/17-workspace.t @@ -4,11 +4,7 @@ # Tests whether we can switch to a non-existant workspace # (necessary for further tests) # -use Test::More tests => 2; -use FindBin; -use lib "$FindBin::Bin/lib"; -use i3test; -use AnyEvent::I3; +use i3test tests => 2; use v5.10; my $i3 = i3("/tmp/nestedcons"); diff --git a/testcases/t/18-openkill.t b/testcases/t/18-openkill.t index 935523d4..1d9d705d 100644 --- a/testcases/t/18-openkill.t +++ b/testcases/t/18-openkill.t @@ -3,12 +3,7 @@ # # Tests whether opening an empty container and killing it again works # -use Test::More tests => 6; -use Data::Dumper; -use FindBin; -use lib "$FindBin::Bin/lib"; -use i3test; -use AnyEvent::I3; +use i3test tests => 6; use v5.10; my $i3 = i3("/tmp/nestedcons"); diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t index 9babb074..b5c01977 100644 --- a/testcases/t/19-match.t +++ b/testcases/t/19-match.t @@ -3,12 +3,7 @@ # # Tests all kinds of matching methods # -use Test::More tests => 4; -use Data::Dumper; -use FindBin; -use lib "$FindBin::Bin/lib"; -use i3test; -use AnyEvent::I3; +use i3test tests => 4; use X11::XCB qw(:all); use v5.10; diff --git a/testcases/t/20-multiple-cmds.t b/testcases/t/20-multiple-cmds.t index 361bb06b..9ee5b41c 100644 --- a/testcases/t/20-multiple-cmds.t +++ b/testcases/t/20-multiple-cmds.t @@ -3,12 +3,7 @@ # # Tests multiple commands (using ';') and multiple operations (using ',') # -use Test::More tests => 24; -use Data::Dumper; -use FindBin; -use lib "$FindBin::Bin/lib"; -use i3test; -use AnyEvent::I3; +use i3test tests => 24; use X11::XCB qw(:all); use v5.10; diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 684e8118..68455bd2 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -1,12 +1,22 @@ package i3test; # vim:ts=4:sw=4:expandtab +use Test::Kit qw( + Test::Exception + Data::Dumper + AnyEvent::I3 +), + 'Test::Deep' => { + exclude => [ qw(all) ], + }; + use File::Temp qw(tmpnam); use X11::XCB::Rect; use X11::XCB::Window; use X11::XCB qw(:all); use AnyEvent::I3; -use Exporter qw(import); +# Test::Kit already uses Exporter +#use Exporter qw(import); use base 'Exporter'; our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content); From a97c876d31d11ee43d0e9f0a68fd7425df8cf8c8 Mon Sep 17 00:00:00 2001 From: batman Date: Thu, 15 Apr 2010 10:40:18 -0700 Subject: [PATCH 049/867] added a move mode and command --- src/commands.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/commands.c b/src/commands.c index b2649bee..0f970e9d 100644 --- a/src/commands.c +++ b/src/commands.c @@ -866,6 +866,35 @@ static void next_previous_workspace(xcb_connection_t *conn, int direction) { } } +static void parse_move_command(xcb_connection_t *conn, Client *last_focused, const char *command) { + int first, second; + resize_orientation_t orientation = O_VERTICAL; + Container *con = last_focused->container; + Workspace *ws = last_focused->workspace; + if (client_is_floating(last_focused)) { + DLOG("Moving a floating client\n"); + if (STARTS_WITH(command, "left")) { + command += strlen("left"); + last_focused->rect.x -= atoi(command); + } else if (STARTS_WITH(command, "right")) { + command += strlen("right"); + last_focused->rect.x += atoi(command); + } else if (STARTS_WITH(command, "top")) { + command += strlen("top"); + last_focused->rect.y -= atoi(command); + } else if (STARTS_WITH(command, "bottom")) { + command += strlen("bottom"); + last_focused->rect.y += atoi(command); + } else { + ELOG("Syntax: move \n"); + return; + } + /* resize_client flushes */ + resize_client(conn, last_focused); + return; + } +} + static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, const char *command) { int first, second; resize_orientation_t orientation = O_VERTICAL; @@ -1030,6 +1059,14 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } + if (STARTS_WITH(command, "move ")) { + if (last_focused == NULL) + return; + const char *rest = command + strlen("move "); + parse_move_command(conn, last_focused, rest); + return; + } + if (STARTS_WITH(command, "mode ")) { const char *rest = command + strlen("mode "); switch_mode(conn, rest); From a99fff03c3144792d06d64a14221ebb5384f230c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Apr 2010 23:18:04 +0200 Subject: [PATCH 050/867] style fixes for the last commit --- src/commands.c | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/commands.c b/src/commands.c index 0f970e9d..6742a469 100644 --- a/src/commands.c +++ b/src/commands.c @@ -867,32 +867,30 @@ static void next_previous_workspace(xcb_connection_t *conn, int direction) { } static void parse_move_command(xcb_connection_t *conn, Client *last_focused, const char *command) { - int first, second; - resize_orientation_t orientation = O_VERTICAL; - Container *con = last_focused->container; - Workspace *ws = last_focused->workspace; - if (client_is_floating(last_focused)) { - DLOG("Moving a floating client\n"); - if (STARTS_WITH(command, "left")) { - command += strlen("left"); - last_focused->rect.x -= atoi(command); - } else if (STARTS_WITH(command, "right")) { - command += strlen("right"); - last_focused->rect.x += atoi(command); - } else if (STARTS_WITH(command, "top")) { - command += strlen("top"); - last_focused->rect.y -= atoi(command); - } else if (STARTS_WITH(command, "bottom")) { - command += strlen("bottom"); - last_focused->rect.y += atoi(command); - } else { - ELOG("Syntax: move \n"); - return; - } - /* resize_client flushes */ - resize_client(conn, last_focused); + if (!client_is_floating(last_focused)) { + LOG("You can only move floating clients with the \"move\" command\n"); return; } + + DLOG("Moving a floating client\n"); + if (STARTS_WITH(command, "left")) { + command += strlen("left"); + last_focused->rect.x -= atoi(command); + } else if (STARTS_WITH(command, "right")) { + command += strlen("right"); + last_focused->rect.x += atoi(command); + } else if (STARTS_WITH(command, "top")) { + command += strlen("top"); + last_focused->rect.y -= atoi(command); + } else if (STARTS_WITH(command, "bottom")) { + command += strlen("bottom"); + last_focused->rect.y += atoi(command); + } else { + ELOG("Syntax: move \n"); + return; + } + /* resize_client flushes */ + resize_client(conn, last_focused); } static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, const char *command) { From aeee7d04021c955ca4bff85504c3702b475d4588 Mon Sep 17 00:00:00 2001 From: Blekos EelVex Kostas Date: Sun, 11 Apr 2010 15:19:13 +0300 Subject: [PATCH 051/867] bring_window_here function and command --- src/commands.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/commands.c b/src/commands.c index 6742a469..9d0d0f65 100644 --- a/src/commands.c +++ b/src/commands.c @@ -703,6 +703,41 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa } } +/* + * Brings the given window class / title to the current workspace. + * + */ +static void bring_window_here(xcb_connection_t *conn, const char *arguments) { + char *classtitle; + Client *client; + + /* The first character is a quote, this was checked before */ + classtitle = sstrdup(arguments+1); + /* The last character is a quote, we just set it to NULL */ + classtitle[strlen(classtitle)-1] = '\0'; + + if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) { + free(classtitle); + ELOG("No matching client found.\n"); + return; + } + + /* This is the workspace num that we are on */ + int current_workspace_num = c_ws->output->current_workspace->num + 1; + /* This is the workspace num that the client is on */ + int clients_workspace_num = client->workspace->num + 1; + + free(classtitle); + workspace_show(conn, clients_workspace_num); + set_focus(conn, client, true); + if (client_is_floating(client)) + move_floating_window_to_workspace(conn, client, current_workspace_num); + else move_current_window_to_workspace(conn, current_workspace_num); + workspace_show(conn, current_workspace_num); + set_focus(conn, client, true); + +} + /* * Jumps to the given window class / title. * Title is matched using strstr, that is, matches if it appears anywhere @@ -1104,6 +1139,13 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } + if (STARTS_WITH(command, "bring ")) { + const char *arguments = command + strlen("bring "); + if (arguments[0] == '"') + bring_window_here(conn, arguments); + return; + } + /* Is it a jump to a specified workspace, row, col? */ if (STARTS_WITH(command, "jump ")) { const char *arguments = command + strlen("jump "); From 2f6beb518e7ff560364a8a88c7b79a23967acc2a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 May 2010 20:27:52 +0200 Subject: [PATCH 052/867] =?UTF-8?q?Don=E2=80=99t=20pretend=20like=20the=20?= =?UTF-8?q?global=20fullscreen=20mode=20would=20be=20configured=20in=20the?= =?UTF-8?q?=20default=20config=20(Thanks=20Mirko)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/userguide | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index 2d6e50f1..6c22eef7 100644 --- a/docs/userguide +++ b/docs/userguide @@ -88,7 +88,7 @@ To display a window fullscreen or to go out of fullscreen mode again, press +Mod1+f+. There is also a global fullscreen mode in i3 in which the client will use all -available outputs. To use it, or to get out of it again, press +Mod1+Shift+f+. +available outputs. === Opening other applications From 0a04ed618bcbe3ed76b993877d828bb674f17a26 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 May 2010 22:45:21 +0200 Subject: [PATCH 053/867] Makefile: only enable when -freorder-blocks-and-partition when DEBUG != 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …otherwise you don’t see variables in gdb --- common.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.mk b/common.mk index dcba6976..6887470f 100644 --- a/common.mk +++ b/common.mk @@ -19,7 +19,6 @@ CFLAGS += -Wunused-value CFLAGS += -Iinclude CFLAGS += -I/usr/local/include CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" -CFLAGS += -freorder-blocks-and-partition LDFLAGS += -lm LDFLAGS += -lxcb-event @@ -62,6 +61,7 @@ CFLAGS += -gdwarf-2 CFLAGS += -g3 else CFLAGS += -O2 +CFLAGS += -freorder-blocks-and-partition endif # Don’t print command lines which are run From 65e9036837f18f39135d4f8dcaa1104b8ea46d79 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 May 2010 23:20:49 +0200 Subject: [PATCH 054/867] =?UTF-8?q?bugfix:=20don=E2=80=99t=20clean=20up=20?= =?UTF-8?q?workspace=20when=20switching=20to=20the=20same=20workspace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/workspace.c | 2 ++ testcases/t/17-workspace.t | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/workspace.c b/src/workspace.c index 9b5593d6..1829370c 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -101,6 +101,8 @@ void workspace_show(const char *num) { old = con_get_workspace(focused); workspace = workspace_get(num); + if (workspace == old) + return; workspace->fullscreen_mode = CF_OUTPUT; /* disable fullscreen */ TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) diff --git a/testcases/t/17-workspace.t b/testcases/t/17-workspace.t index c350d9d9..bb30bd02 100644 --- a/testcases/t/17-workspace.t +++ b/testcases/t/17-workspace.t @@ -4,7 +4,7 @@ # Tests whether we can switch to a non-existant workspace # (necessary for further tests) # -use i3test tests => 2; +use i3test tests => 3; use v5.10; my $i3 = i3("/tmp/nestedcons"); @@ -33,4 +33,9 @@ diag("Other temporary workspace name: $otmp\n"); $i3->command("workspace $otmp")->recv; ok(!workspace_exists($tmp), 'old workspace cleaned up'); +# Switch to the same workspace again to make sure it doesn’t get cleaned up +$i3->command("workspace $otmp")->recv; +$i3->command("workspace $otmp")->recv; +ok(workspace_exists($otmp), 'other workspace still exists'); + diag( "Testing i3, Perl $], $^X" ); From 09523f36f5916e6c1079f4055bd0da3ea578be02 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 May 2010 23:24:03 +0200 Subject: [PATCH 055/867] also re-render the tree for commands using the new parser --- src/ipc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ipc.c b/src/ipc.c index 704c0d47..81429abd 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -115,6 +115,7 @@ IPC_HANDLER(command) { char *command = scalloc(message_size); strncpy(command, (const char*)message, message_size); parse_cmd((const char*)command); + tree_render(); free(command); /* For now, every command gets a positive acknowledge From f10a3d9b7571eb4e87e59609a062a9bb20e2b198 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 May 2010 00:00:43 +0200 Subject: [PATCH 056/867] bugfix: really return focus list in IPC tree dump (instead of nodes list) --- src/ipc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index 81429abd..c92f4480 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -161,16 +161,16 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("nodes"); y(array_open); - Con *leaf; - TAILQ_FOREACH(leaf, &(con->nodes_head), nodes) { - dump_node(gen, leaf, inplace_restart); + Con *node; + TAILQ_FOREACH(node, &(con->nodes_head), nodes) { + dump_node(gen, node, inplace_restart); } y(array_close); ystr("focus"); y(array_open); - TAILQ_FOREACH(leaf, &(con->nodes_head), nodes) { - y(integer, (long int)leaf); + TAILQ_FOREACH(node, &(con->focus_head), nodes) { + y(integer, (long int)node); } y(array_close); From d8307f4b4a0e8731df13a95279efbe951111370f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 May 2010 00:06:24 +0200 Subject: [PATCH 057/867] implement 'next' in the new command parser (testcase unfinished) --- src/cmdparse.l | 1 + src/cmdparse.y | 18 +++++++++++++ testcases/Makefile | 2 +- testcases/t/21-next-prev.t | 52 ++++++++++++++++++++++++++++++++++++++ testcases/t/lib/i3test.pm | 7 ++--- 5 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 testcases/t/21-next-prev.t diff --git a/src/cmdparse.l b/src/cmdparse.l index 079aa8f9..318da934 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -99,6 +99,7 @@ workspace { BEGIN(WANT_WS_STRING); return TOK_WORKSPACE; } focus { return TOK_FOCUS; } move { return TOK_MOVE; } open { return TOK_OPEN; } +next { return TOK_NEXT; } class { BEGIN(WANT_QSTRING); return TOK_CLASS; } id { BEGIN(WANT_QSTRING); return TOK_ID; } diff --git a/src/cmdparse.y b/src/cmdparse.y index e9b2538c..c3b19686 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -86,6 +86,7 @@ void parse_cmd(const char *new) { %union { char *string; + char chr; } %token TOK_ATTACH "attach" @@ -110,6 +111,7 @@ void parse_cmd(const char *new) { %token TOK_FOCUS "focus" %token TOK_MOVE "move" %token TOK_OPEN "open" +%token TOK_NEXT "next" %token TOK_CLASS "class" %token TOK_ID "id" @@ -249,6 +251,7 @@ operation: | kill | open | fullscreen + | next ; exec: @@ -333,3 +336,18 @@ fullscreen: } ; + +next: + TOK_NEXT WHITESPACE direction + { + printf("should select next window in direction %c\n", $3); + tree_next('n', ($3 == 'v' ? VERT : HORIZ)); + } + ; + +direction: + 'h' { $$ = 'h'; } + | 'horizontal' { $$ = 'h'; } + | 'v' { $$ = 'v'; } + | 'vertical' { $$ = 'v'; } + ; diff --git a/testcases/Makefile b/testcases/Makefile index 0e78f6b2..f433546f 100644 --- a/testcases/Makefile +++ b/testcases/Makefile @@ -1,5 +1,5 @@ test: - PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/03* t/05* + PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/21* all: test diff --git a/testcases/t/21-next-prev.t b/testcases/t/21-next-prev.t new file mode 100644 index 00000000..3759e256 --- /dev/null +++ b/testcases/t/21-next-prev.t @@ -0,0 +1,52 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Tests focus switching (next/prev) +# +use i3test tests => 4; +use X11::XCB qw(:all); +use v5.10; + +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +###################################################################### +# Open one container, verify that 'next v' and 'next h' do nothing +###################################################################### +$i3->command('open')->recv; + +my ($nodes, $focus) = get_ws_content($tmp); +my $old_focused = $focus->[0]; + +$i3->command('next v')->recv; +($nodes, $focus) = get_ws_content($tmp); +is($focus->[0], $old_focused, 'focus did not change with only one con'); + +$i3->command('next h')->recv; +($nodes, $focus) = get_ws_content($tmp); +is($focus->[0], $old_focused, 'focus did not change with only one con'); + +###################################################################### +# Open another container, verify that 'next h' switches +###################################################################### +$i3->command('open')->recv; + +($nodes, $focus) = get_ws_content($tmp); +isnt($old_focused, $focus->[0], 'new container is focused'); +$old_focused = $focus->[0]; + +$i3->command('next h')->recv; + +($nodes, $focus) = get_ws_content($tmp); +isnt($focus->[0], $old_focused, 'focus did change'); + +# +# TODO: extend this test-case: +# - implement prev +# - wrapping (no horizontal switch possible, goes level-up) +# - going level-up "manually" +# - different synonyms (horizontal/vertical) + +diag( "Testing i3, Perl $], $^X" ); diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 68455bd2..5ca84231 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -62,17 +62,18 @@ sub get_unused_workspace { # # returns the content (== tree, starting from the node of a workspace) -# of a workspace +# of a workspace. If called in array context, also includes the focus +# stack of the workspace # sub get_ws_content { my ($name) = @_; my $i3 = i3("/tmp/nestedcons"); my $tree = $i3->get_workspaces->recv; my @ws = map { @{$_->{nodes}} } @{$tree->{nodes}}; - my @cons = map { $_->{nodes} } grep { $_->{name} eq $name } @ws; + my @cons = grep { $_->{name} eq $name } @ws; # as there can only be one workspace with this name, we can safely # return the first entry - return $cons[0]; + return wantarray ? ($cons[0]->{nodes}, $cons[0]->{focus}) : $cons[0]->{nodes}; } 1 From aa52986cfe9469a80d6dec9b58446bcbb2e4bd39 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 May 2010 08:43:13 +0200 Subject: [PATCH 058/867] document border styles in "Border style for new windows" section (Thanks Dirkson) --- docs/userguide | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index 6c22eef7..78279565 100644 --- a/docs/userguide +++ b/docs/userguide @@ -326,7 +326,9 @@ new_container tabbed === Border style for new windows -This option determines which border style new windows will have. +This option determines which border style new windows will have: +bn+ for +the normal border (including window title), +bp+ for a 1-pixel border (no +window title) and +bb+ to make the window borderless. *Syntax*: --------------------------------------------- From 145ebc7584cb3c41c19c02bc6053774aa9dc651f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 May 2010 09:08:31 +0200 Subject: [PATCH 059/867] Implement 'prev', extend testcase --- src/cmdparse.l | 3 +++ src/cmdparse.y | 22 ++++++++++++---- testcases/t/21-next-prev.t | 54 ++++++++++++++++++++++++++++++++------ 3 files changed, 66 insertions(+), 13 deletions(-) diff --git a/src/cmdparse.l b/src/cmdparse.l index 318da934..ee73c7b9 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -100,6 +100,9 @@ focus { return TOK_FOCUS; } move { return TOK_MOVE; } open { return TOK_OPEN; } next { return TOK_NEXT; } +prev { return TOK_PREV; } +horizontal { return TOK_HORIZONTAL; } +vertical { return TOK_VERTICAL; } class { BEGIN(WANT_QSTRING); return TOK_CLASS; } id { BEGIN(WANT_QSTRING); return TOK_ID; } diff --git a/src/cmdparse.y b/src/cmdparse.y index c3b19686..d77fa4ca 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -112,6 +112,9 @@ void parse_cmd(const char *new) { %token TOK_MOVE "move" %token TOK_OPEN "open" %token TOK_NEXT "next" +%token TOK_PREV "prev" +%token TOK_HORIZONTAL "horizontal" +%token TOK_VERTICAL "vertical" %token TOK_CLASS "class" %token TOK_ID "id" @@ -252,6 +255,7 @@ operation: | open | fullscreen | next + | prev ; exec: @@ -345,9 +349,17 @@ next: } ; -direction: - 'h' { $$ = 'h'; } - | 'horizontal' { $$ = 'h'; } - | 'v' { $$ = 'v'; } - | 'vertical' { $$ = 'v'; } +prev: + TOK_PREV WHITESPACE direction + { + printf("should select prev window in direction %c\n", $3); + tree_next('p', ($3 == 'v' ? VERT : HORIZ)); + } + ; + +direction: + TOK_HORIZONTAL { $$ = 'h'; } + | 'h' { $$ = 'h'; } + | TOK_VERTICAL { $$ = 'v'; } + | 'v' { $$ = 'v'; } ; diff --git a/testcases/t/21-next-prev.t b/testcases/t/21-next-prev.t index 3759e256..8ed3ada7 100644 --- a/testcases/t/21-next-prev.t +++ b/testcases/t/21-next-prev.t @@ -3,7 +3,7 @@ # # Tests focus switching (next/prev) # -use i3test tests => 4; +use i3test tests => 13; use X11::XCB qw(:all); use v5.10; @@ -31,22 +31,60 @@ is($focus->[0], $old_focused, 'focus did not change with only one con'); ###################################################################### # Open another container, verify that 'next h' switches ###################################################################### -$i3->command('open')->recv; +my $left = $old_focused; +$i3->command('open')->recv; ($nodes, $focus) = get_ws_content($tmp); isnt($old_focused, $focus->[0], 'new container is focused'); -$old_focused = $focus->[0]; +my $mid = $focus->[0]; + +$i3->command('open')->recv; +($nodes, $focus) = get_ws_content($tmp); +isnt($old_focused, $focus->[0], 'new container is focused'); +my $right = $focus->[0]; $i3->command('next h')->recv; - ($nodes, $focus) = get_ws_content($tmp); -isnt($focus->[0], $old_focused, 'focus did change'); +isnt($focus->[0], $right, 'focus did change'); +is($focus->[0], $left, 'left container focused (wrapping)'); + +$i3->command('next h')->recv; +($nodes, $focus) = get_ws_content($tmp); +is($focus->[0], $mid, 'middle container focused'); + +$i3->command('next h')->recv; +($nodes, $focus) = get_ws_content($tmp); +is($focus->[0], $right, 'right container focused'); + +$i3->command('prev h')->recv; +($nodes, $focus) = get_ws_content($tmp); +is($focus->[0], $mid, 'middle container focused'); + +$i3->command('prev h')->recv; +($nodes, $focus) = get_ws_content($tmp); +is($focus->[0], $left, 'left container focused'); + +$i3->command('prev h')->recv; +($nodes, $focus) = get_ws_content($tmp); +is($focus->[0], $right, 'right container focused'); + + +###################################################################### +# Test synonyms (horizontal/vertical instead of h/v) +###################################################################### + +$i3->command('prev horizontal')->recv; +($nodes, $focus) = get_ws_content($tmp); +is($focus->[0], $mid, 'middle container focused'); + +$i3->command('next horizontal')->recv; +($nodes, $focus) = get_ws_content($tmp); +is($focus->[0], $right, 'right container focused'); + # -# TODO: extend this test-case: -# - implement prev +# TODO: extend this test-case (as soon as splitting is implemented): # - wrapping (no horizontal switch possible, goes level-up) # - going level-up "manually" -# - different synonyms (horizontal/vertical) diag( "Testing i3, Perl $], $^X" ); From 6a1c34d2c584271d26f4dcc214798421c097a1ac Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 May 2010 09:33:10 +0200 Subject: [PATCH 060/867] Implement 'split' --- src/cmdparse.l | 1 + src/cmdparse.y | 10 +++++++ src/tree.c | 5 ++++ testcases/Makefile | 2 +- testcases/t/21-next-prev.t | 6 ---- testcases/t/22-split.t | 58 ++++++++++++++++++++++++++++++++++++++ testcases/t/lib/i3test.pm | 12 +++++++- 7 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 testcases/t/22-split.t diff --git a/src/cmdparse.l b/src/cmdparse.l index ee73c7b9..4a8e01fc 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -101,6 +101,7 @@ move { return TOK_MOVE; } open { return TOK_OPEN; } next { return TOK_NEXT; } prev { return TOK_PREV; } +split { return TOK_SPLIT; } horizontal { return TOK_HORIZONTAL; } vertical { return TOK_VERTICAL; } diff --git a/src/cmdparse.y b/src/cmdparse.y index d77fa4ca..abb30ebe 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -113,6 +113,7 @@ void parse_cmd(const char *new) { %token TOK_OPEN "open" %token TOK_NEXT "next" %token TOK_PREV "prev" +%token TOK_SPLIT "split" %token TOK_HORIZONTAL "horizontal" %token TOK_VERTICAL "vertical" @@ -256,6 +257,7 @@ operation: | fullscreen | next | prev + | split ; exec: @@ -357,6 +359,14 @@ prev: } ; +split: + TOK_SPLIT WHITESPACE direction + { + printf("splitting in direction %c\n", $3); + tree_split(focused, ($3 == 'v' ? VERT : HORIZ)); + } + ; + direction: TOK_HORIZONTAL { $$ = 'h'; } | 'h' { $$ = 'h'; } diff --git a/src/tree.c b/src/tree.c index 3a657983..72e8645c 100644 --- a/src/tree.c +++ b/src/tree.c @@ -167,6 +167,11 @@ void tree_close_con() { * */ void tree_split(Con *con, orientation_t orientation) { + /* for a workspace, we just need to change orientation */ + if (con->parent->type == CT_OUTPUT) { + con->orientation = orientation; + return; + } /* 2: replace it with a new Con */ Con *new = con_new(NULL); Con *parent = con->parent; diff --git a/testcases/Makefile b/testcases/Makefile index f433546f..9741c24c 100644 --- a/testcases/Makefile +++ b/testcases/Makefile @@ -1,5 +1,5 @@ test: - PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/21* + PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/22* all: test diff --git a/testcases/t/21-next-prev.t b/testcases/t/21-next-prev.t index 8ed3ada7..bc0e6025 100644 --- a/testcases/t/21-next-prev.t +++ b/testcases/t/21-next-prev.t @@ -81,10 +81,4 @@ $i3->command('next horizontal')->recv; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $right, 'right container focused'); - -# -# TODO: extend this test-case (as soon as splitting is implemented): -# - wrapping (no horizontal switch possible, goes level-up) -# - going level-up "manually" - diag( "Testing i3, Perl $], $^X" ); diff --git a/testcases/t/22-split.t b/testcases/t/22-split.t new file mode 100644 index 00000000..95e387d1 --- /dev/null +++ b/testcases/t/22-split.t @@ -0,0 +1,58 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Tests splitting +# +use i3test tests => 11; +use X11::XCB qw(:all); +use v5.10; + +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +my $ws = get_ws($tmp); +is($ws->{orientation}, 0, 'orientation horizontal by default'); +$i3->command('split v')->recv; +$ws = get_ws($tmp); +is($ws->{orientation}, 1, 'split v changes workspace orientation'); + +###################################################################### +# Open two containers, split, open another container. Then verify +# the layout is like we expect it to be +###################################################################### +$i3->command('open')->recv; +$i3->command('open')->recv; +my $content = get_ws_content($tmp); + +is(@{$content}, 2, 'two containers on workspace level'); +my $first = $content->[0]; +my $second = $content->[1]; + +is(@{$first->{nodes}}, 0, 'first container has no children'); +is(@{$second->{nodes}}, 0, 'second container has no children (yet)'); +my $old_name = $second->{name}; + + +$i3->command('split h')->recv; +$i3->command('open')->recv; + +$content = get_ws_content($tmp); + +is(@{$content}, 2, 'two containers on workspace level'); +$first = $content->[0]; +$second = $content->[1]; + +is(@{$first->{nodes}}, 0, 'first container has no children'); +isnt($second->{name}, $old_name, 'second container was replaced'); +is($second->{orientation}, 0, 'orientation is horizontal'); +is(@{$second->{nodes}}, 2, 'second container has 2 children'); +is($second->{nodes}->[0]->{name}, $old_name, 'found old second container'); + +# TODO: extend this test-case (test next/prev) +# - wrapping (no horizontal switch possible, goes level-up) +# - going level-up "manually" + + +diag( "Testing i3, Perl $], $^X" ); diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 5ca84231..4b76b7f4 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -19,7 +19,7 @@ use AnyEvent::I3; #use Exporter qw(import); use base 'Exporter'; -our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content); +our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content get_ws); BEGIN { my $window_count = 0; @@ -76,4 +76,14 @@ sub get_ws_content { return wantarray ? ($cons[0]->{nodes}, $cons[0]->{focus}) : $cons[0]->{nodes}; } +sub get_ws { + my ($name) = @_; + my $i3 = i3("/tmp/nestedcons"); + my $tree = $i3->get_workspaces->recv; + my @ws = map { @{$_->{nodes}} } @{$tree->{nodes}}; + my @cons = grep { $_->{name} eq $name } @ws; + return $cons[0]; +} + + 1 From 98dbe63e35a6917020b149f8349c79da3306f07d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 May 2010 10:12:35 +0200 Subject: [PATCH 061/867] Implement exec, exit, level, restart (without testcases for now) --- src/cmdparse.l | 3 +++ src/cmdparse.y | 51 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/cmdparse.l b/src/cmdparse.l index 4a8e01fc..f96d5578 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -104,6 +104,9 @@ prev { return TOK_PREV; } split { return TOK_SPLIT; } horizontal { return TOK_HORIZONTAL; } vertical { return TOK_VERTICAL; } +level { return TOK_LEVEL; } +up { return TOK_UP; } +down { return TOK_DOWN; } class { BEGIN(WANT_QSTRING); return TOK_CLASS; } id { BEGIN(WANT_QSTRING); return TOK_ID; } diff --git a/src/cmdparse.y b/src/cmdparse.y index abb30ebe..f42bd072 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -87,6 +87,7 @@ void parse_cmd(const char *new) { %union { char *string; char chr; + int number; } %token TOK_ATTACH "attach" @@ -116,6 +117,9 @@ void parse_cmd(const char *new) { %token TOK_SPLIT "split" %token TOK_HORIZONTAL "horizontal" %token TOK_VERTICAL "vertical" +%token TOK_LEVEL "level" +%token TOK_UP "up" +%token TOK_DOWN "down" %token TOK_CLASS "class" %token TOK_ID "id" @@ -241,13 +245,11 @@ operations: operation: exec | exit - /*| reload | restart + /*| reload | mark | layout | border - | mode - | workspace | move*/ | workspace | attach @@ -258,12 +260,15 @@ operation: | next | prev | split + | mode + | level ; exec: TOK_EXEC WHITESPACE STR { printf("should execute %s\n", $3); + start_application($3); } ; @@ -271,6 +276,15 @@ exit: TOK_EXIT { printf("exit, bye bye\n"); + exit(0); + } + ; + +restart: + TOK_RESTART + { + printf("restarting i3\n"); + i3_restart(); } ; @@ -346,6 +360,7 @@ fullscreen: next: TOK_NEXT WHITESPACE direction { + /* TODO: use matches */ printf("should select next window in direction %c\n", $3); tree_next('n', ($3 == 'v' ? VERT : HORIZ)); } @@ -354,6 +369,7 @@ next: prev: TOK_PREV WHITESPACE direction { + /* TODO: use matches */ printf("should select prev window in direction %c\n", $3); tree_next('p', ($3 == 'v' ? VERT : HORIZ)); } @@ -362,6 +378,7 @@ prev: split: TOK_SPLIT WHITESPACE direction { + /* TODO: use matches */ printf("splitting in direction %c\n", $3); tree_split(focused, ($3 == 'v' ? VERT : HORIZ)); } @@ -373,3 +390,31 @@ direction: | TOK_VERTICAL { $$ = 'v'; } | 'v' { $$ = 'v'; } ; + +mode: + TOK_MODE WHITESPACE layout_mode + { + printf("should switch mode to %s\n", ($3 == TOK_FLOATING ? "floating" : "tiling")); + /* TODO: actually switch mode (not toggle) */ + } + ; + +layout_mode: + TOK_FLOATING { $$ = TOK_FLOATING; } + | TOK_TILING { $$ = TOK_TILING; } + ; + +level: + TOK_LEVEL WHITESPACE level_direction + { + printf("level %c\n", $3); + if ($3 == 'u') + level_up(); + else level_down(); + } + ; + +level_direction: + TOK_UP { $$ = 'u'; } + | TOK_DOWN { $$ = 'd'; } + ; From a0e33c1d683ffe3b67a3967980f61f828b41fef2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 11 May 2010 22:46:49 +0200 Subject: [PATCH 062/867] implement 'move' command in the new parser --- src/cmdparse.l | 2 ++ src/cmdparse.y | 21 +++++++++++++++++++-- src/tree.c | 2 ++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/cmdparse.l b/src/cmdparse.l index f96d5578..104926dc 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -107,6 +107,8 @@ vertical { return TOK_VERTICAL; } level { return TOK_LEVEL; } up { return TOK_UP; } down { return TOK_DOWN; } +before { return TOK_BEFORE; } +after { return TOK_AFTER; } class { BEGIN(WANT_QSTRING); return TOK_CLASS; } id { BEGIN(WANT_QSTRING); return TOK_ID; } diff --git a/src/cmdparse.y b/src/cmdparse.y index f42bd072..016f19e2 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -120,6 +120,8 @@ void parse_cmd(const char *new) { %token TOK_LEVEL "level" %token TOK_UP "up" %token TOK_DOWN "down" +%token TOK_AFTER "after" +%token TOK_BEFORE "before" %token TOK_CLASS "class" %token TOK_ID "id" @@ -249,8 +251,8 @@ operation: /*| reload | mark | layout - | border - | move*/ + | border */ + | move | workspace | attach | focus @@ -418,3 +420,18 @@ level_direction: TOK_UP { $$ = 'u'; } | TOK_DOWN { $$ = 'd'; } ; + +move: + TOK_MOVE WHITESPACE before_after WHITESPACE direction + { + printf("moving: %s and %c\n", ($3 == TOK_BEFORE ? "before" : "after"), $5); + /* TODO: change API for the next call, we need to convert in both directions while ideally + * we should not need any of both */ + tree_move(($3 == TOK_BEFORE ? 'p' : 'n'), ($5 == 'v' ? VERT : HORIZ)); + } + ; + +before_after: + TOK_BEFORE { $$ = TOK_BEFORE; } + | TOK_AFTER { $$ = TOK_AFTER; } + ; diff --git a/src/tree.c b/src/tree.c index 72e8645c..8a3bdf2d 100644 --- a/src/tree.c +++ b/src/tree.c @@ -271,6 +271,8 @@ void tree_next(char way, orientation_t orientation) { void tree_move(char way, orientation_t orientation) { /* 1: get the first parent with the same orientation */ Con *parent = focused->parent; + if (parent->type == CT_OUTPUT) + return; bool level_changed = false; while (parent->orientation != orientation) { LOG("need to go one level further up\n"); From c75a6732bf2a20823670a637e0108ae97bb9cfc3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 11 May 2010 23:00:31 +0200 Subject: [PATCH 063/867] Implement 'restore' in new parser --- src/cmdparse.l | 1 + src/cmdparse.y | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/cmdparse.l b/src/cmdparse.l index 104926dc..55b1d28f 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -109,6 +109,7 @@ up { return TOK_UP; } down { return TOK_DOWN; } before { return TOK_BEFORE; } after { return TOK_AFTER; } +restore { BEGIN(WANT_WS_STRING); return TOK_RESTORE; } class { BEGIN(WANT_QSTRING); return TOK_CLASS; } id { BEGIN(WANT_QSTRING); return TOK_ID; } diff --git a/src/cmdparse.y b/src/cmdparse.y index 016f19e2..c1b30294 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -122,6 +122,7 @@ void parse_cmd(const char *new) { %token TOK_DOWN "down" %token TOK_AFTER "after" %token TOK_BEFORE "before" +%token TOK_RESTORE "restore" %token TOK_CLASS "class" %token TOK_ID "id" @@ -252,6 +253,7 @@ operation: | mark | layout | border */ + | restore | move | workspace | attach @@ -435,3 +437,11 @@ before_after: TOK_BEFORE { $$ = TOK_BEFORE; } | TOK_AFTER { $$ = TOK_AFTER; } ; + +restore: + TOK_RESTORE WHITESPACE STR + { + printf("restoring \"%s\"\n", $3); + tree_append_json($3); + } + ; From 5eae70642777d9fdc961ff1d01c0927587ff39d2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 11 May 2010 23:04:21 +0200 Subject: [PATCH 064/867] grammar: s/layout_mode/window_mode --- src/cmdparse.y | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index c1b30294..47faa7f7 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -396,14 +396,14 @@ direction: ; mode: - TOK_MODE WHITESPACE layout_mode + TOK_MODE WHITESPACE window_mode { printf("should switch mode to %s\n", ($3 == TOK_FLOATING ? "floating" : "tiling")); /* TODO: actually switch mode (not toggle) */ } ; -layout_mode: +window_mode: TOK_FLOATING { $$ = TOK_FLOATING; } | TOK_TILING { $$ = TOK_TILING; } ; From 7adf921bc32cf5f167fd36d5bf4cfe0b3587fa7a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 11 May 2010 23:04:47 +0200 Subject: [PATCH 065/867] use the new parser for handling keybindings --- src/handlers.c | 2 +- src/nc.c | 66 -------------------------------------------------- 2 files changed, 1 insertion(+), 67 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 535d963e..4921fdea 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -97,7 +97,7 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ } } - parse_command(bind->command); + parse_cmd(bind->command); return 1; } diff --git a/src/nc.c b/src/nc.c index 8cd325a8..cb3569a0 100644 --- a/src/nc.c +++ b/src/nc.c @@ -62,72 +62,6 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { } } -void parse_command(const char *command) { - printf("received command: %s\n", command); - - if (strcasecmp(command, "open") == 0) - tree_open_con(NULL); - else if (strcasecmp(command, "close") == 0) - tree_close_con(); - else if (strcasecmp(command, "split h") == 0) - tree_split(focused, HORIZ); - else if (strcasecmp(command, "split v") == 0) - tree_split(focused, VERT); - else if (strcasecmp(command, "level up") == 0) - level_up(); - else if (strcasecmp(command, "level down") == 0) - level_down(); - else if (strcasecmp(command, "prev h") == 0) - tree_next('p', HORIZ); - else if (strcasecmp(command, "prev v") == 0) - tree_next('p', VERT); - else if (strcasecmp(command, "next h") == 0) - tree_next('n', HORIZ); - else if (strcasecmp(command, "next v") == 0) - tree_next('n', VERT); - else if (strncasecmp(command, "workspace ", strlen("workspace ")) == 0) - workspace_show(command + strlen("workspace ")); - else if (strcasecmp(command, "stack") == 0) { - focused->layout = L_STACKED; - } - else if (strcasecmp(command, "fullscreen") == 0) { - if (focused->fullscreen_mode == CF_NONE) - focused->fullscreen_mode = CF_OUTPUT; - else focused->fullscreen_mode = CF_NONE; - } - else if (strcasecmp(command, "move before h") == 0) - tree_move('p', HORIZ); - else if (strcasecmp(command, "move before v") == 0) - tree_move('p', VERT); - else if (strcasecmp(command, "move after h") == 0) - tree_move('n', HORIZ); - else if (strcasecmp(command, "move after v") == 0) - tree_move('n', VERT); - else if (strncasecmp(command, "restore", strlen("restore")) == 0) - tree_append_json(command + strlen("restore ")); - else if (strncasecmp(command, "exec", strlen("exec")) == 0) - start_application(command + strlen("exec ")); - else if (strcasecmp(command, "restart") == 0) - i3_restart(); - else if (strcasecmp(command, "floating") == 0) { - //toggle_floating_mode(focused, false); - parse_cmd("exit"); - parse_cmd("exec /usr/bin/bleh"); - parse_cmd("exec kill -9 33"); - parse_cmd("kill"); - parse_cmd("[ class=\"Xpdf\" ] kill"); - parse_cmd("[ class=\"firefox\" ] kill"); - - } - - tree_render(); - -#if 0 - if (strcasecmp(command, "prev") == 0) - tree_prev(O_CURRENT); -#endif -} - int main(int argc, char *argv[]) { //parse_cmd("[ foo ] attach, attach ; focus"); int screens; From 8c5d824fa8f321606560dee0be75894d46901328 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 14 May 2010 23:37:56 +0200 Subject: [PATCH 066/867] Ignore UnmapNotify events generated by reparenting --- src/handlers.c | 6 ++++++ src/manage.c | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/handlers.c b/src/handlers.c index 4921fdea..ecaba219 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -451,11 +451,17 @@ int handle_screen_change(void *prophs, xcb_connection_t *conn, */ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event) { + bool ignored = event_is_ignored(event->sequence); + /* we need to ignore EnterNotify events which will be generated because a * different window is visible now */ add_ignore_event(event->sequence); DLOG("UnmapNotify for 0x%08x (received from 0x%08x), serial %d\n", event->window, event->event, event->sequence); + if (ignored) { + DLOG("Ignoring UnmapNotify (generated by reparenting)\n"); + return 1; + } Con *con = con_by_window_id(event->window); if (con == NULL) { LOG("Not a managed window, ignoring\n"); diff --git a/src/manage.c b/src/manage.c index b1b9e9d6..7983baac 100644 --- a/src/manage.c +++ b/src/manage.c @@ -171,6 +171,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki goto out; } + LOG("ignoring sequence %d for reparenting!\n", rcookie.sequence); + add_ignore_event(rcookie.sequence); + xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, state_cookie, NULL); if (xcb_reply_contains_atom(reply, atoms[_NET_WM_STATE_FULLSCREEN])) con_toggle_fullscreen(nc); From def41582d1fbf070110f8a58c2f7e00825da0bca Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 14 May 2010 23:41:17 +0200 Subject: [PATCH 067/867] re-render the three after calling parse_cmd --- src/handlers.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/handlers.c b/src/handlers.c index ecaba219..bca297ee 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -98,6 +98,7 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ } parse_cmd(bind->command); + tree_render(); return 1; } From 2d52ecf071110ee8a84a19ac09080eb07c247dbd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 15 May 2010 00:16:59 +0200 Subject: [PATCH 068/867] Add parameter to reparent windows instead of killing them when closing a container Necessary because when windows are unmapped, they are not necessary to be killed (an application can unmap it temporarily). --- include/tree.h | 2 +- src/cmdparse.y | 4 ++-- src/floating.c | 2 +- src/handlers.c | 2 +- src/tree.c | 22 +++++++++++++++------- src/workspace.c | 2 +- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/include/tree.h b/include/tree.h index 029bbe34..fd393d8b 100644 --- a/include/tree.h +++ b/include/tree.h @@ -21,7 +21,7 @@ void tree_render(); void tree_close_con(); void tree_next(char way, orientation_t orientation); void tree_move(char way, orientation_t orientation); -void tree_close(Con *con); +void tree_close(Con *con, bool kill_window); bool tree_restore(); #endif diff --git a/src/cmdparse.y b/src/cmdparse.y index 47faa7f7..31d7535b 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -314,11 +314,11 @@ kill: printf("killing!\n"); /* check if the match is empty, not if the result is empty */ if (match_is_empty(¤t_match)) - tree_close(focused); + tree_close(focused, true); else { TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); - tree_close(current->con); + tree_close(current->con, true); } } diff --git a/src/floating.c b/src/floating.c index cd387ba8..2afe1b82 100644 --- a/src/floating.c +++ b/src/floating.c @@ -37,7 +37,7 @@ void toggle_floating_mode(Con *con, bool automatic) { /* 2: kill parent container */ TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows); - tree_close(con->parent); + tree_close(con->parent, false); /* 3: re-attach to previous parent */ con->parent = con->old_parent; diff --git a/src/handlers.c b/src/handlers.c index bca297ee..e351aea0 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -469,7 +469,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti return 1; } - tree_close(con); + tree_close(con, false); tree_render(); x_push_changes(croot); return 1; diff --git a/src/tree.c b/src/tree.c index 8a3bdf2d..d8fc9f4a 100644 --- a/src/tree.c +++ b/src/tree.c @@ -108,7 +108,7 @@ Con *tree_open_con(Con *con) { * Closes the given container including all children * */ -void tree_close(Con *con) { +void tree_close(Con *con, bool kill_window) { /* TODO: check floating clients and adjust old_parent if necessary */ /* Get the container which is next focused */ @@ -129,7 +129,19 @@ void tree_close(Con *con) { * in their parent’s nodes_head */ while (!TAILQ_EMPTY(&(con->nodes_head))) { child = TAILQ_FIRST(&(con->nodes_head)); - tree_close(child); + tree_close(child, kill_window); + } + + if (con->window != NULL) { + if (kill_window) + x_window_kill(con->window->id); + else { + /* un-parent the window */ + xcb_reparent_window(conn, con->window->id, root, 0, 0); + /* TODO: client_unmap to set state to withdrawn */ + + } + free(con->window); } /* kill the X11 part of this container */ @@ -138,10 +150,6 @@ void tree_close(Con *con) { con_detach(con); con_fix_percent(con->parent, WINDOW_REMOVE); - if (con->window != NULL) { - x_window_kill(con->window->id); - free(con->window); - } free(con->name); TAILQ_REMOVE(&all_cons, con, all_cons); free(con); @@ -158,7 +166,7 @@ void tree_close_con() { } /* Kill con */ - tree_close(focused); + tree_close(focused, true); } /* diff --git a/src/workspace.c b/src/workspace.c index 1829370c..6404cff0 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -117,7 +117,7 @@ void workspace_show(const char *num) { if (TAILQ_EMPTY(&(old->nodes_head))) { LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); - tree_close(old); + tree_close(old, false); } con_focus(next); From be357a1aaa1542f83f57af01c7c83b01e339e547 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 15 May 2010 00:28:10 +0200 Subject: [PATCH 069/867] Set up _NET_WM_SUPPORTED and window manager name again This fixes MPlayer fullscreen mode --- src/nc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/nc.c b/src/nc.c index cb3569a0..ac8590af 100644 --- a/src/nc.c +++ b/src/nc.c @@ -257,6 +257,11 @@ int main(int argc, char *argv[]) { /* Watch WM_NAME (title of the window encoded in COMPOUND_TEXT) */ xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL); + /* Set up the atoms we support */ + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 7, atoms); + /* Set up the window manager’s name */ + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3"); keysyms = xcb_key_symbols_alloc(conn); From 0c60ae19cb7a6a16e8287090eaa629749cfed957 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 24 May 2010 00:06:26 +0200 Subject: [PATCH 070/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20leak=20file?= =?UTF-8?q?=20descriptors=20(Thanks=20InfraRed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cfgparse.y | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cfgparse.y b/src/cfgparse.y index 2774f05c..234e8fa7 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -184,6 +184,8 @@ void parse_file(const char *f) { SLIST_REMOVE_HEAD(&variables, variables); FREE(current); } + fclose(fstr); + close(fd); } %} From d0baa8c6527246057b4365ec1d7e483991eb8e47 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 24 May 2010 19:20:32 +0200 Subject: [PATCH 071/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20invert=20dire?= =?UTF-8?q?ctions=20when=20resizing=20floating=20clients=20(top/left)=20(T?= =?UTF-8?q?hanks=20Jo=C3=A3o)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands.c b/src/commands.c index 9d0d0f65..18181a61 100644 --- a/src/commands.c +++ b/src/commands.c @@ -938,15 +938,15 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c DLOG("Resizing a floating client\n"); if (STARTS_WITH(command, "left")) { command += strlen("left"); - last_focused->rect.width += atoi(command); - last_focused->rect.x -= atoi(command); + last_focused->rect.width -= atoi(command); + last_focused->rect.x += atoi(command); } else if (STARTS_WITH(command, "right")) { command += strlen("right"); last_focused->rect.width += atoi(command); } else if (STARTS_WITH(command, "top")) { command += strlen("top"); - last_focused->rect.height += atoi(command); - last_focused->rect.y -= atoi(command); + last_focused->rect.height -= atoi(command); + last_focused->rect.y += atoi(command); } else if (STARTS_WITH(command, "bottom")) { command += strlen("bottom"); last_focused->rect.height += atoi(command); From 30b275d27ff9338d7d483d8e77d6a11aaaefcb87 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 26 May 2010 23:11:42 +0200 Subject: [PATCH 072/867] Bugfix: Update _NET_WM_STATE when clients request changes via ClientMessage This fixes problems with Chromium fullscreen mode --- src/client.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/client.c b/src/client.c index 9c136ca6..e2868faf 100644 --- a/src/client.c +++ b/src/client.c @@ -228,6 +228,10 @@ void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global uint32_t values[] = { XCB_STACK_MODE_ABOVE }; xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); + /* Update _NET_WM_STATE */ + values[0] = atoms[_NET_WM_STATE_FULLSCREEN]; + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[_NET_WM_STATE], ATOM, 32, 1, values); + fake_configure_notify(conn, r, client->child); xcb_flush(conn); @@ -262,6 +266,9 @@ void client_leave_fullscreen(xcb_connection_t *conn, Client *client) { render_layout(conn); } + /* Update _NET_WM_STATE */ + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[_NET_WM_STATE], ATOM, 32, 0, NULL); + xcb_flush(conn); } From a45dc6b3ad5863a67b84094f61e22e59ec5b5693 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 26 May 2010 23:21:37 +0200 Subject: [PATCH 073/867] Update _NET_WM_STATE after fullscreen state changes --- src/con.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/con.c b/src/con.c index 83e89631..36070849 100644 --- a/src/con.c +++ b/src/con.c @@ -289,4 +289,19 @@ void con_toggle_fullscreen(Con *con) { con->fullscreen_mode = CF_NONE; } LOG("mode now: %d\n", con->fullscreen_mode); + + /* update _NET_WM_STATE if this container has a window */ + /* TODO: when a window is assigned to a container which is already + * fullscreened, this state needs to be pushed to the client, too */ + if (con->window == NULL) + return; + + uint32_t values[1]; + unsigned int num = 0; + + if (con->fullscreen_mode != CF_NONE) + values[num++] = atoms[_NET_WM_STATE_FULLSCREEN]; + + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, + atoms[_NET_WM_STATE], ATOM, 32, num, values); } From f6227bec6c60c83d53e8cae005ef1102b135136f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 26 May 2010 23:40:05 +0200 Subject: [PATCH 074/867] wsbar: print an error and exit after not being able to talk to i3 for 2 seconds (Thanks badboy) --- i3-wsbar | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/i3-wsbar b/i3-wsbar index 2f62edb4..3c76864e 100755 --- a/i3-wsbar +++ b/i3-wsbar @@ -15,6 +15,13 @@ my $stdin; my $socket_path = undef; my ($workspaces, $outputs) = ([], {}); my $last_line = ""; +my $w = AnyEvent->timer( + after => 2, + cb => sub { + say "Connection to i3 timed out. Verify socket path ($socket_path)"; + exit 1; + } +); my $command = ""; my $input_on = ""; @@ -47,6 +54,16 @@ $| = 1; # Wait a short amount of time and try to connect to i3 again sub reconnect { my $timer; + if (!defined($w)) { + $w = AnyEvent->timer( + after => 2, + cb => sub { + say "Connection to i3 timed out. Verify socket path ($socket_path)"; + exit 1; + } + ); + } + my $c = sub { $timer = AnyEvent->timer( after => 0.01, @@ -65,6 +82,8 @@ sub connected { return; } + $w = undef; + $i3->subscribe({ workspace => \&ws_change, output => \&output_change, From 7adfd37a124e991ee2b22873ea3ba818241b8724 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 27 May 2010 20:21:17 +0200 Subject: [PATCH 075/867] Add documentation for external workspace bars --- docs/Makefile | 5 ++- docs/wsbar | 94 +++++++++++++++++++++++++++++++++++++++++++++++++ docs/wsbar.dia | Bin 0 -> 1899 bytes docs/wsbar.png | Bin 0 -> 14339 bytes 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 docs/wsbar create mode 100644 docs/wsbar.dia create mode 100644 docs/wsbar.png diff --git a/docs/Makefile b/docs/Makefile index b17413ca..79e5019a 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,5 +1,5 @@ -all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html +all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html hacking-howto.html: hacking-howto asciidoc -a toc -n $< @@ -16,6 +16,9 @@ ipc.html: ipc multi-monitor.html: multi-monitor asciidoc -a toc -n $< +wsbar.html: wsbar + asciidoc -a toc -n $< + clean: rm -f */*.{aux,log,toc,bm,pdf,dvi} rm -f *.log *.html diff --git a/docs/wsbar b/docs/wsbar new file mode 100644 index 00000000..9e379dd9 --- /dev/null +++ b/docs/wsbar @@ -0,0 +1,94 @@ +External workspace bars +======================= +Michael Stapelberg +May 2010 + +This document describes why the internal workspace bar is minimal and how an +external workspace bar can be used. It explains the concepts using +i3-wsbar+ +as the reference implementation. + +== Internal and external bars + +The internal workspace bar of i3 is meant to be a reasonable default so that +you can use i3 without having too much hassle when setting it up. It is quite +simple and intended to stay this way. So, there is no way to display your own +information in this bar (unlike dwm, wmii, awesome, …). + +We chose not to implement such a mechanism because that would be duplicating +already existing functionality of tools such as dzen2, xmobar and similar. +Instead, you should disable the internal bar and use an external workspace bar +(which communicates with i3 through its IPC interface). + +== dock mode + +You typically want to see the same workspace bar on every workspace on a +specific screen. Also, you don’t want to place the workspace bar somewhere +in your layout by hand. This is where dock mode comes in: When a program sets +the appropriate hint (_NET_WM_WINDOW_TYPE_DOCK), it will be managed in dock +mode by i3. That means it will be placed at the bottom of the screen (while +other edges of the screen are possible in the NetWM standard, this is not yet +implemented in i3), it will not overlap any other window and it will be on +every workspace for the specific screen it was placed on initially. + +== The IPC interface + +In the context of using an external workspace bar, the IPC interface needs to +provide the bar program with the current workspaces and output (as in VGA-1, +LVDS-1, …) configuration. In the other direction, the program has to be able +to switch to specific workspaces. + +By default, the IPC interface is enabled and places its UNIX socket in ++~/.i3/ipc.sock+. + +To learn more about the protocol which is used for IPC, see +docs/ipc+. + +== Output changes (on-the-fly) + +i3 implements the RandR API and can handle changing outputs quite well. So, an +external workspace bar implementation needs to make sure that when you change +the resolution of any of your screens (or enable/disable an output), the bars +will be adjusted properly. + +== i3-wsbar, the reference implementation + +Please keep in mind that +i3-wsbar+ is just a reference implementation. It is +shipped with i3 to have a reasonable default. Thus, +i3-wsbar+ is designed to +work well with dzen2 and there are no plans to make it more generic. + +=== The big picture + +The most common reason to use an external workspace bar is to integrate system +information such as what +i3status+ provides into the workspace bar (to save +screen space). So, we have +i3status+ or a similar program, which only provides +text output (formatted in some way). To display this text nicely on the screen, +there are programs such as dzen2, xmobar and similar. We will stick to dzen2 +from here on. So, we have the output of i3status, which needs to go into dzen2 +somehow. But we also want to display the list of workspaces. +i3-wsbar+ takes +input on stdin, combines it with a formatted workspace list and pipes it to +dzen2. + +Please note that +i3-wsbar+ does not print its output to stdout. Instead, it +launches the dzen2 instances on its own. This is necessary to handle changes +in the available outputs (to place a new dzen2 on a new screen for example). + +image:wsbar.png["Overview",link="wsbar.png"] + +=== Running i3-wsbar + +The most simple usage of i3-wsbar looks like this: +------------------------------- +i3-wsbar -c "dzen2 -x %x -dock" +------------------------------- + +The +%x+ in the command name will be replaced by the X position of the output +for which this workspace bar is running. i3 will automatically place the +workspace bar on the correct output when dzen2 is started in dock mode. The +bar which you will see should look exactly like the internal bar of i3. + +To actually get a benefit, you want to give +i3-wsbar+ some input: +------------------------------------------ +i3status | i3-wsbar -c "dzen2 -x %x -dock" +------------------------------------------ + +It is recommended to place the above command in your i3 configuration file +to start it automatically with i3. diff --git a/docs/wsbar.dia b/docs/wsbar.dia new file mode 100644 index 0000000000000000000000000000000000000000..d117ce075a4400334f757e706e5fb7853049d6d6 GIT binary patch literal 1899 zcmV-x2bB09iwFP!000021MOVfZsRr(ea}}I-j^0gXkJ8#T(7g}w&(>|papijuLf-~ zwpv*-B<9wKetSttyS8O1zQ}Z9)ypnP;mmn;1;BE z@UI9%G0=?${p*WfZ(V>dlrZ;-yF#fXnA|Gai^M{X`V%qzdzQqvk>Af-744>R7$?0u z5svzAZnCRCD8vjlw+Q!oJ6XuhF0VUjAE@G5G?2)hYayZ=N zyl~-p;ga*h1?hMhCn^zv+J>COaVSNUqpIXq9*>hwMW|KmsbQhxrQZZf#XI_cBf@l_ z0pz#8S=^!4J4*upY2)2!Q6uF(@YQ_$U}?H?>5rDA-vwzf31wC9gGilB{NriFmw%m% z!B>wr)P`JI?GoWLNhLjeiJ096zD#$h*=Cy>N%NxFVArgd+wD6s`K{7|ok4`+Q6`1> z538lRSNy;QUDJpd8SLir5>QjG!WkRX6BJ%H$Oa59vO z@ib1NZ7<4H9u8}~1iXk9}Lxfy} zkAn*${iBRGd0{6r90rlj@@5Yas^cv= zPSs;5%aWzWYb09F-B=_^d|xjap|hdA`t74-0aY&j=Y z-Vkt*xgd2JbX@8+gWS}`ef9Aeq_T5Tn{I}%8ITBrS+tPVKWXKv@pOWvDo1dbaolBH zs$XWv`6!f~i3Xh$&|v0|kYmG_eR+^z<{3|nWkkp@(lL_*P)nHb>OP%_q+`M}G2ymK zc2w9JDn!pih4cheNa-cl3}urE3lQkyj0g$Si#~$dKYaN#xD1x^87Xj^0nb5xkTOCD z=GcYA;rJZvx21)zf&5^5$nPXcY`1X|o0=US3~eT{HT7`7H4$OJFEMGBU*4U=ZZx8O za@g9j+)OKbR&&^luV2i4O*!lv$DvLuHgXU_i|g0nLJ5?j*M-g8K5y4_yFt zh5)KU8N2xDCC5)SMcN3Tsu+mc-2+EV%l90E<1y%(I&I`Qj`7%_%N_f5+7*MW&9A~2 zRHfaOQlGv&<)C6`*K)CQon@1jShj(IloXathQ`C@`DPY$xc5RVyYTxPBvU%;evQ`6 zpXVq8YF@<4OwADTfMYkWVnAH&pk`-|wLR;PtR;Ig3lWK#OfQy6Je6tMMHSYgiVB_V z!itw1R@BiGJxSqArVGTgekMDkD31+IRuAHwp|IC6RL4+zs%1w~?b|y>jjL^m^YwiW z775CL7z{>-j(OXO^|wYe9o2rD#--P`5WDY<*Z@Q5HEkM`sV~XoHEkL^S@w*p^|9MDZqM;V+l9AvN!-Sjw*;9= zN6SaGH3;O~GcXjfAu$3Nw%&Hr zCAm+Z*N$A38r2E?4uCZxzBnX?Bk-wXWhj~}}O l+O|EwDmCnqM$eiw%0Fi>3BJDAc#H7$#eco*%P2z58ta?0xnPQBjh4iu)WF1qJ0PL{?G_1qBU+f`Xcf zjR}mrCv}|#-Y`w%Wh7A^9{)0$^I}j?UZ6lEB{bYq_7*(6=b@>WN7_kM5YLlG(e-GbgA0X?o79VolRFx!d`3w%kY6@Ibo4e# zc4@IpA^JBa$ijQALi9~JEi&?4VGPI~H$NCWK`*-yzzjk#v5~U;zjXXiK=6;j-1zNn z(3a-s&p2Z{lf}!QEz_3w1eeSfW~+BOUu6~+hR!cGQ#t)6QUGti9Be#%H<%)l5FQ@R z{XT&S%3ZbsVnmDZRhjS{9dYzB?Sm zG#zah_1_#tse`^SZ9ThmmkQRjft{)6DR10{ajpc#D4G1qhMXQOr5_(Vi|f>cr{7Ka z9xXN=bWOq6dswfc^c=+I>E#gwBw@rNU!t^=oL)weyX{Z6Z*_wx(g%BcR{{e9f}mPu z`aK8x^})4E-Z80jwd$G%18ow4-@X4vr@XZ!*hupgkI7ZA5=dJgk8-(dfHqyuyEIjr z;Q*`uxDg4`h)w9;N$pSJ59<@Z`CyUi_%7@~EYXzt#G|@ONe@i;#Jh>%a|cPW>37#R zt15^{JH1-x^$^ysJXYn@UZZqhaq+uZ_OipAqEdi)g`Ia*r&){9bNV-HTrM30g0Ri< zS%hoV#)(Q>_Iuo5sPK5f_~fKolb0Zu_yvto%k_4U`4oJO9cS#szOiJ2Zkqemy%5r-QN0;iQNx-)JVxvKuU)nwOVQg}Ex@Et0!Op$VI2-;FM?U8{h&3SZ)4EzqI`R2B+KpsWA93oiA{k8v^~zt%S#FVh(^MqXkGPML?pSlxA*e2 z0=D=OnIRFL{x-T`ERPR*?b_nLR~<~#!RULVm*tU|n5dA<6F9d%ATKKnh;{c{)Rf^z zI4xq_P`q`Qa&qgFbOr3HWWx7P&%l~;lvvkkR%GZ<# zA1$Zr-56Q;koO;UvlYL3ZKnB=z7G#q=XAgE_CoA}sjA{77rW_Vluzu>fq~lj(fm%? z{&Tyz%f9r7esc=_Hg>L->HRtZGE&Ep50*}I7p+4p35F(mAL-ZD))ZN2P$niO_QbV# zt#zk;DFN|-aOukO)qDDTO*Vgi^xj>i+t^Ix6S6tqy{v%6zlP|*4u^k;9$v0CUF}NAZ5i-vsk@JNABXK+1HbrPeNowZ(oU)A<)3IX z3kx-MgMsjJ)4A${$?@@V6?OIZ78VxXivZP02H^0a_XvW|kYM4GFC~K3*49b-4(r7q z0F`DJZ(i;o6nZ!vTOBK+*7zB|*XUy<#YAjXQT=BpjDl6c_e5lgUPMIXUmdPG=0t$W z|FJ(`+!?B|`xz zfxPhe*4h48Fx{e)%)4LF#i;05&SM<4Z<2g910%bWTvlN%?^}KDk__&1FhTMB%rmB_ z`tSuPV4y8M=2TQv-g{ltF^k&jNjHEc;ai-6IuMZ-RT`t^_Gi{Erp zgQ*&tn$a=Iec$Ncf9Lk#WleW`PGR85hK}{ry6ONpW$m+N`r4l0qVZl5|L$yk%=WhR z_32i}G!97sOQyEbz_*VbN#T22t@m-@+jYTW!+Jy@7Qq|pjr9ODA0Y?YFeA^9l@*ig zi-VzX)5v8Pqgw{c!PI1pm}#@9CqMJ*z8l@{S~eZ-R+8>bOFT4*D!tuedbDO5*hVo;(Y`FOjn%MZ~Tv2m{|D?>Q9z`|baH_J2mQ zCp%vu;&a~MLgZWk{_dD4TCl4>|ICyl6hi<(kmCMd9Sfj$S-$dW|2?Y!N;T3N9V<-_ zu^P;N4-oo4dWuyl&$}Fn%JNWBLEt}`wxzJL>1dzyZ{Jd z`rLycD1au8v`%gTAShT>2UETA)XYfI@3&DzIbONs>zo{m4$aE`FE`K%2|MU-e~$D7MMXjPfcHL=_q%%jec zOFcv~eL7ErIC5G_8l_DkgqgXN)cTw=ef#Fh_j(j>Zzc$)-F!|DHfD)pm>}EP$Jn$V zcI`R@%DZV!{6;W1R2CWCNu8ZZcB-LssBMiHIb07)!%YHsl&IdAI{&5G=_N!){P4Ak z^l-}AsKunsP>m1Zj#$h-{@50Dhc1K6?u>h==}H>9XU`BJRb$ zjlN_mz~kQfrb|w}Tf&l~Dyv3;96nbA92)%&Xy5REs11m0&cAXm&~AAi^`YTW9{|0* zFdn6Ul8frGH*Ie#gT(+bh=T-^zC_hRTSB{LDZjUBTk;Phuv7sAY;#Q}aTkGo2z`;z z1r;7Ft{r4~A#%$Nh^v8G#^4K_PV96GPuhtvJ)1-tnO1R&1zxyt(!NVlarMG_zZYue1MpjEi7 zH75iiJgkrr-v#N;DZrNl6i6{D=DTAPx>@!cz=4f3r~YFy>h#gNBO18D3~oS>sd6ED zbhfKyCAh$R3atz!Ay4>O0ZB-0|ku! zW+x#>X90m$)O`iE5=Lik`#6x_ei^gM{EyIntS(U!?LQo&NGOUsNnJw-R0bW49xpRh zgffZViluaL`7P(1XX{(`AP3`h67DXl58ka_^V$X)9SQ~N5>^)q+P%l5sRtj>Ce}RW zuC+z-905#4tpf0IZ1C`?`vu}VFT>LVKDMt?)_jlW6JE6$jQBn6UC4=T)C9uY0_-+q{fH>l9KeX4>(ppXxYQLPR z2ndCX8(u{RRd2g>jn-@cYkJ++di^(8W^(kM(3*S%55ey{S+FhzGqTTxrB{|1gH zb1IHE-X*C(^4_yD1)UUbI3PmeT=}VZwOf6joA_9jn$p-_15L?7hSNWL66b$xuLn0f zzTti0-rSYo0KeJ!r65dN_(ZV(le3TJ2OLekc=ZZ%p=9!fpWD}mqz$)g6rl^ybyBkXQN zxHg_wO!na$@03xMbSpUJ@e%!Bcs976#HD9DUCPdb11lb6yt?;f*Qf#9vR|49<6HJ3 zA%Oi!jRY{JhRqqp;8oA4^qkq+x#?y&U*btb>)|-`>gXztdEjh&zQXd}ju)$Ds+v}G zH9mog_pIvj)MZID9{%J2mD_l}Jmqe+fnmge-Do}MAe|_EU&Mr%?&hExWp}PByjpp{ z^a2nqJ7;!HZs@Mh`%SPl=KI`qiThpoEpXvhx6IQ${Te^$&~3m9f0?RgL)5&)Rk-MK zj6(D2oY@66;^+STFHKvo4TxKrp+ZkQS?s}#oTB-MCffdto=Pg%BtO3R@n4pxiSdV? zFBJRc9RZCqMsja(v53S4 z5gizGMauklF&@hFRr|^7mOV7%Ni;ojxCoX}XS4W_g|L3Aw@SrlC?K zHE}EPVZJ{Qqic!#HZN&yGhKOK(g4+NM*OsUy`^*CHV{D^d2apPaa_B_lDcfciHASV z+-~t;yW6+FruJcW+w+Ws0Q;Vh34~cK5h8tr*I9f!+8ZR5)hb$;7||dgFC0t!H0*X~ zN5q+zkMn^(Yu!87;D(lWHOtK6A~!da)Nf^?>>U#t6%}b2ym{oh)5gg1Pt_Pb@elU_ zo1?45nPw{CyDXI52=T}ep|vhnDuEMyh6zoY>6Z9Zx~8^VsS8#zBP}<&$maC~nxkm> zKYupcCu?ap7L1H!pjW6U1h#s8U7uUcSGrs9+uCqgx!0?Pc-*%OPN~GB)9UXEN=nTh zCMSx0PFtssuRD(54(|JD8TC$hyk6_vC)b`^rs2or#i9BEqUPj^#*#qWYd>;P~}@8 z(=?4D1tbwVg?S=}tF7YUsO$~yut<@LN}D-?PikrYP9Q7u3N*Y69(MRkdfYY0f4|;$%%>&m3 zGZW}c-Y$vnjt^{nJ2S;XR+LQ;i(0?x@+Wm#jIP8Fu;OIdpuGc8sgdK)-lte)fLRv1 z?O6aCx&pz%I1|2(N+MF`ek6goz}%?a>qs)YUWOfoF;XmjpiG=H-B9R3OKY%|%x}Iu zi_+&*pC#>~44-nUj^V@mt4i>M`=Sh|qO9<_oi6zU4Dgaq@O=BuVd)kRlbRZ_q^jzg zw6TkCRO)?UMcs7Ju>JFX_1~+sbX7qh}hu&RIaAkKKYEq)hz)w`@rmcE>?jx{JA2M3q&bTyNp^Ll!(zpvg zrZ=mYU^e1*<>?qv*;K%A_0yysObJ5C8;S$!L z;VsK&1tlLB#w5Tm09#RhYuI{gbxJENAfB2km=d-4eAl$wYe=nP3x4ikpa@yUW#E@^ z8Ix7yXQ#dQDT2sIL5PIVrI`lj(x*k=YE7BU95~3zzGX>+iOCpVK6S>5V#tzViB7V< zcQzl&22=Qj(=L=z{3aL=%$Pd@%j7wrhpJNy(zB!cpxp{OGE2a=Ytat!c=Jmc82JJX zJ3M**-nO5LrG9Zh*>>oa8&6X2Ptj9*sjxJ$QqBtv%zK^{lb zna9#}Pum99xS0CE*M)OEyNM*}i-2P-p;ZPkpx0qsic2J{@_^+bZ$LY=0omxtF$~Cr zDMpXX*qvw5-g6Cz4M0lc&z|CUmB=qtuYC{ac6F{{axlBhn!*5CyQgF~ZYq~u-sDSe zKEJNfy9PzGSNRoaOvpHIqLL)ad;)v+!LdG95_FV$hF&i}E_@lVPj7W1FQU64YMg44 z>%2Dr{AfNWc=*yuo;YwEe%MVtQ?=<+*mtgO;Nv$CMu!Xp!`VNk-p(YTMUa$DL@h|K z`I}h3>U-&JDlYGHbfp!Q_a1?&kJ0qBpOt61j^vXOp?G7&3S7sRf(0PagBRg^`74zu#u%;3J@JzY|J;n8k5^@`C`1D$ygY4sOW# zXGg3&C(6s8+Mnla-5e$TYfBmnr9!DwvJ2{OELZu-&7Ix6_uHfu&eyyrcrf1MAZ40S zA5XJ0lWA4UN@$NM&PnjNyzmzVk<_!Hh18$k(<2+hjFon0Oemgb)cyTj{Wj2Ms|0xM z%x`)Thqo>*N1vL4$4$k3vA9a%Vus~aKiDXies6cvR5t|`eTc6F=2A_n>br)>)V^zO+l5yepX^aBp`B2Jo2GMV>o ztd`rXx1jjuW{544g;WSV3i*{pynS501-Q-6%Z57{y3QG}XK>t!Z(F4KkeaHRWZ5D* zCHJyBi_dOfxad7YOk`v=_FMjtvwNZIZF?3HCRS@?oux&&mjn;9L)^dcHuIHrk#$dh zPxi*Qw9~ZlWYoOBJf|x^PHCpklaoprWaW+*aGO5N*T~U&7aw&3i=z1eCHJT_cK13oM$aN#k(FD=fFmO6Ok>M7T&BW8x1`h0w_LQhV_2N{J%02EyNdA+3 z%mJ>3 zP1VYnUpYd{N~|FefnwgKT|Sco?6etT9x`VHy?{&;cxR%Y&tcUCH!!VJ8Jl74Y6BIY zAKpl-ZQ{V-&C5wv3UsNFyMt^~;^z=VtaJ;?F1;*fsNWDxmuUpk%dM}>f)9A7;^Eyu z6(_aL5$Uu|?AM)jLQsnz&;T+YSuI72BwVfo7WqV6aUmMwU^?CYcah^`X`ZkEqSvWRSgL!1C|HlhIXS4Np9Z;ZqeGO)(5 zCGHUHYSfWT5bG$>fEEFh9BMA&)6S6@e0s`CX7UX>zk9X_Sp*Za9jAmtCQ(Lg2AZX1 znV=I#`=@FmV{}Kke@hn}TUfV*P$;t@dT#EU%o-tL_m~&w)<~@v>^Ve0bwqmb1tpHa zJ8s_H2n^F7*~w{bzQjx*BIHwJH91D0Fog1#xC*0y{xaGJ>+P!?B53%SUj)X171&Wx zE-RX;AUXMIG1KM8QZ#$|mu#D_j{U%D+C8q~5+_DLWfEbFl)(s*#&AzBU1;2h!$?h( z@_UC&8UMlv)Q7mkUi5s`thptUARe8Fcw)%nzEs_&yhVGUhsYi_M_ppx6c$A*MqST- zlX(c}jrs*u6IQ>hl_XH7;oQzZ{JYG^!_|J)>p{HW=4ce$4PS3 zLZL&+Lo;HkdtZjJ$V_?5Zh>})AtC?S8EIv#69_ky8P24yxhuDu7N@ubBVS0=BA3GV z`B+Hmo=!uCe@q5q+!_8(p@Y-KjZof~pG7TyGbern=ZNZRNXxJ)&LDIogvKePV@dpJK&6b9S~+p>&WWp8weN2{!4O;^iIfNEHJBW zMR&)id=3|rSdqFAX{&5|84$JyZ!6p8;6f9ZQi(FgcXUuom%LHLG|VY=U9~6n#UQQw z-Vp5qBM#?}vHtgdM8wGg=k^wcns_&xiieD9zUhGkA8$A?V1Ya_vQ3-z7`fxgv`UUL zl$AJjX~<0Px8~18Ip0!7IN&)heiXN8poVZe+ZT_o`a#6v!Gwp99 zra?Y3c1DUOgsC5%)|3_28mP~u%R0j9@GhU#<%hTvOt>RIdv-sFaUXKi9btH88(;G zZ7?q4@%PwaOTG|X)wdAlX%rNS;>TV9FU|%E64E&>^d}f+&u5)S?Cfq9+vu>Z*AFsE zTZ6yTwkeKR6S$`%2YJ?U0~l^z1?b+=*)XKlG~m9B?5Y>>#D}K1_?KX$AJG1~@xHs4 zM-VG8e;WOc+kvA?{98_tmNg&0AW|?0NdP|o@ZgUec@InIJ!l0A<0sg!EFxdLl)L|c zYjXJpswjGaW+W?tzc5lgu30gUl7g6ZRLkV^eqb(`yA%wdyS>B6N;rRdG#!YY7S_FA zm(ILdDX}bs$Z!b5Za4j^hqKM)N>OkN5b(~K9ty=Hcy_8~Rl86yLUO4bkj;qJBr%M5 zkI1e9+;L>XXaoh$^@_@Nsom0-+%3R>sdeZ=FN+D!zRe0pe3Uq-FTV9@AUDc zgRJFKDnOmIS{%bv>-Y6n_tcCWKjAWVoL6Vk*uo>itkui1i17wKKPU#+a18@a@0u4H zx@G(NkQch(`w*Lv#Q{u?(+^nx%8vooJwTDkdBadG!vamUkn+3mqk42-3}UL)+?WbR27|SFPOSozsu3fgmIuVRm89(MjWKV# zfx+FH%$w<4?gqpDXbeWnF5AdKn_g*Sx#q29494m%TeMQvx|f3EqZRA@nf6R$)&X>} zDde>(4GrHLXw6`V`OQ{?yBjV@s$5XUA3>I|49Sf9<1dAJGr39Y?B-o^)Z4ArI~?F> zV~qg=j6f5E*uNEDD*O)AF8Y0+a_41kiz%d6#0{XD-v zkS#tL9+wiNxO}QY48LO$=E`MtWc^9~k|qC+ZVbIHzob&ww0um0YlQhp!(I-d9xm6rMF1~K?}36U-Lk%1SR zlvZ>Sw{!mT>d&J`d#c+|w-Hh5k72r?vX?c#j^d`>{VS3{%Q}qFU$9`Obxn)LO9r7a zq=`Xx!hjKXTKdTYS|BBc<4IV;a;elTVWO@BQ4@TF|6SQ&t1AX0^&bdm`Axti6Or+M zV{A@;9!?UUG_cP#QSem8K!f&BW}A5Kstp+{%>=<6co8u8%>;}#gq8)+(-6pxgP6mX z;L6&_7NCKQE}uJXUu(QYx#hE?F4Y$%HT6#@*!i@d$3y_*f^4H*li*K**cWsphYhMo zYZL-F`%KX>TmBPfvkCYTjJb=kRphvhwL6@6vwx;y7qeU%#uJ_T*c2o)N&2?yYLJdtp!0lg`xZ1`?$Hii9@6o*ZH;L^($COsyRy&Y zM(xf6_GZ)q3^5Fpw2Acd6VO?K&6=%Yny0TBFU7UdmVWgPA!42R{BtlUHl8EhUa$fn z2gHc=UB*Calfs{;0ACi-Q1lupf)OXl*!G-8THj!-ejNI#77L3pHd{pzD_&))Xw^Vx zS;=7T8B%roRpPyWMu!Mw^y4V{B_=d(R{+g2hxY}W*%Yzs5*@05@m3lWh@1qcfir4h zYatBgxt$h?vB>n6u+U$xobWlsT1&6NbMVC`?e&XxDK{YYE@R8dF^b{n+G>)dP&lB( zAm-hAmvQ9~!dW}A`-6bv6b%_F$D-}3%XW2n-fr53{gbYaCB*I?EnLK#9-_7pEi=4u z5~MZRh&e6e$g_Z|@A-WdF{=5f6NnQ)C&(v_ux`bJ>X_KpeBL>JOquIH_C`XsIWwYA zI)33O%Gqe6dEpEu11OATa!YEV9;)H~1EO}>r4DOv{1LLIjoLY~)>sZkD=6tg<5HhW zxy}2>t$9sG6-FRV<>?B*1Tb*sjcW5%2X<)CjuF1h7uwWuelz0mLKrBpg zswGjt$am&~KB2^h{S-zF9Bd4VcF>5&jch4HO73sz5=H^*4q((LGuM8i?iWk0wgTq*S>R`CpWp(vM@vy-sk z?3()6hDqb6Mh7IKi0JI9g!cwuw5LYQIv+HI=Pl5=hP_wlP+PtiBAE&p;bHMyZbSit zp@=<2RW;|_(bLt~TtjMz?HU8b%93(`VoTtx0iE(2LN=$=l=RSMTepnq=34hvKwNg@ z@iV)|O(|?o_U2b)?Q#ac@sX8n9Br?RMO5URH_`)N%CVVrcD?6AzsyK9=WnwxhM!jZ zXJB>4jcgfw#K`B|lM9Ha#x+9;^jmKGis`!(WqC?^D7iTT1^F`M({NRPz?4|$`aBSH zF-(SHss;*bNG)&6)xi338r%OeA0b>`ABe>#EGRhfF+c2PFP@`-K*Hc3(XF@Kwe{sa z*^y(g_5h9G;k9{wxNw$>vl{42L*0``xAOWX(TR^Q2hM;T4%jjE`hPy#{38cN0hJsC zJi!^mS|3zYWdf~sjH;3EE)Gz8T~w^Jp$a4eH5*?I5>SMoU*56nirrbhY+t5%8O7Ema9kJC65WXDivL=$F1%S1ID3RMfh~xM7@S2Mc{% z0(K@xX=1x2g(i}20u=b5-da{AGnJtV`Yo z^`aFd)qlg@6j)5@Xm0i@pK|;;I_Ir?g7{8Y?Qs%n54l zHdJQ@R04gQ181BlOX;FQ_XokX4wfICoVpWH@OR4xn?>~GHt@RRosPGo*9I>pt z0Q%B4PELecd><^9WZa@?=Xs#7(rLw00;{XN?bdpO@K%k&LV?M@wZtRZQlD} z?}`u)MYf`nwTc}152_C78~C*SO;%&XlX|*p2=2Si+gs_I>*;UwIAdk|Gh-{|U_KMc zU#>n%O21rPU|u{E(b$5;x}5_ylT7_|lSLE{Z#-SY=Hv>H_0)&+aig0@z4zPOL0j~S zqUfN}BJHA^f=J2BVDmnA_`%|3Q`YtQ+P!HceUXGz1^j8G$9z#)O~Zp7hiEM(tjJzwIzKn}wt@mQlM)`8@yxQWL5RjN*SL>--*436cZ(alK(1PT@&LcNF3wC<7 z@ss>jO7(crN0-#=DkeV0z~Csc_<9c4k=F(0X^jXIz%0>?Jpq>TSQ3C|e){Kyg-+|L zO0@PXSP)XSK}U|Lg)=QXt})MddyFDQA~#?M>+N=P?k-E>ZWPWrD~#{11EJ)z;6SQrDSmjb5Izd!2JbM>4NXPhIg6xD0xg1??4}l%pRl zN9oy^2`0x6`xKKe%NV1=hi=Z3_-|de$L$4C)_iF_n7~{wGm2{jCVt2UYwEIq7D3hh zjVw8VtZw^%|DvLQA8v$sFCL>~y{!%Wl4t~o`FDb0W>X^FF??d8-)E2iTuT%6XI2HG ztVFjnT-)#?(q)KeyOW~+Zz{~)VyaYnZn55p(PK!gJJs&(&^4#dGhhBU1uzGTjkTlk z!BohBvYd=nf~z}hT=*a^WG^-+@;SZ$6n*yZu~)6_ z^6w^5%+uYT$xz%u&lANj=i8l2k>Ipc%k+T?b){wbESVE(PS%hRAtdHDB)Eu{U zrsDIgEK`9yL4WGp3jek`qx7MMs_LM0F813q9LKg;3^?v$A<54&t z@06V^RNKsGen|YO2!Zl1={F})l{NkH+f1DxlTSD{adJ{S@|enYJ-3J8RAS+ORqk%- zJ8PXHF?5F!D;sQ*Jp3@&d?y2p8!6%&CKv)3nL#X3vpSdUhQ(ymXOH$ol#K} z?1q~BTY=mcT zyKy$f_L4o!;e@a+UX?85aQvvd@@BX4gXeLNx5ZyspDG`C(UCi_pxUg=faPY-&OFnl zjq9a7x?;WVno!($po{nb_)qTZtA{Q$1l_%6WBYitJG1Ms;{CS9*SBk9{n$Ma<92y( z4v0#=EK&}JX$#e1Lm($fHKzy96N^V|fl>+*O9B?GbPTGs)leb;V!W`Kk(yHTmI=x!RK$Hg(F`A_^cttwa{6d$`}62SI&Y|3>2%Bs9G_jW}AR zo@lNs%J3_j5;1~lYVj7gizmHOoLAB6nqy}6_WX^F!b!~&P%AGtt#e@UPVEFhOW3RX z`9eL5{QMgLNw15wNxeq>P#@*&usb)ke8$yeqh=|{Ad>v+_?UcZ=EH}cy7~Fb)4F+m zHvxAAAzqQjPI!ud;|qZHCK|Zy)%^a#qYO!ok5_i^_h%|ce3D-&Ho;CT0KCKd_iAIS z(HQl8ns1sxT-B zL<%v72d`D>7I7c%U&k(A@Ry?oM1>>R%1YLv=4#4s$JJMHdi?8Mu8`yE^}aik?pD@! z=62;~OX87b3D^(=)mdiH#|02SYhq*Wx@}p$v12oyg`pOCtwLHqLdfN%@Qp1kc>k@O zjrHYih4~Q_;FC7AefY<%d3PlF)YbS`B5#*7>(E#1Y=y;tm$XY(DmJqsYG{joW(Hu@ z*qLf;lDS|JD(T$sg^<)+0y<=K=P3{$rwV}JpSfQ)w&N7U0sP0hzQ$9tSp6hmr_br` zhOf0fLQzkw!4wOCpq{cf--H_h!}!%CVy$EYQyjQz{q=5;=1o4(q23k^zdi(%MW+eo z;N1dK+%6OsvZXBBTj;wU9krOcTFi#v_YJOZc7_Rg9+^n5|26({O{qEPVq!Y*=&rE!dixR^&2S#&W)(*6bg@1lN@UmUJhVQbFJi3`bMpX%Zru_U z-G}ub%MrhWb8?zmU|baZ`Al3;exQFiqr?OzlWxCk&*}y{1@l>;RNd_7=TO>zs4bmo z%)JI!sH3Ob`-sIyKqBt@ze!g62EBGTTHbiR$s?fogG>{f(Nb+sr51EH!D7OT0-g|J z?g*wk_Q~$ZfmyIjMNxGC$mkGR-i`J~uGGcr6sbLycT%)9mOL+`hd5%Fj(kLc$p+%W zta&r5C{3bXZk2tUl)_aLYN7EacRr{tkQE*`*N0&S1ps?|A#t3wmjjn^FQoWXx#;g!U^*M`Y`Z}zq`vXFOSSQ;M zR_55cM=1G-&|6FOd+xWbrOhl|&XMi6%im3BN%eM@%ENSbOSfue=oY@f*gqdoL*j|u zpyk=}+uK07#3EPIAq{3b-KKYQ?vW|58k!?7wpBj0mweJv=WkH-Izhh1yvh`E?~zin(!IWko&(hv;W^UtpBg$ zqZQPy5a|9~#b?#X=FE=Q!uz4FvMcB<5PFxk+V|Qh;SlJvZ8w@j#XSp1aBRyx2EN?m h0KsEtZP^3i&yhO~?`4Nw;Hf?oh?J6K={w_q{|!ju)j Date: Fri, 28 May 2010 00:05:38 +0200 Subject: [PATCH 076/867] update debian packaging --- debian/changelog | 15 +++++++++++++++ debian/control | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 2631dde7..6aa5a51f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,18 @@ +i3-wm (3.e-3) unstable; urgency=low + + * Bump debian policy version + * Add Recommends: libanyevent-i3-perl, libanyevent-perl, libipc-run-perl + which are necessary to use i3-wsbar (which is not core functionality, + thus no Depends:) (Closes: #577287) + + -- Michael Stapelberg Sat, 24 Apr 2010 11:20:19 +0200 + +i3-wm (3.e-2) unstable; urgency=low + + * Use x-terminal-emulator instead of hard-coded urxvt + + -- Michael Stapelberg Sun, 04 Apr 2010 19:30:46 +0200 + i3-wm (3.e-1) unstable; urgency=low * Implement RandR instead of Xinerama diff --git a/debian/control b/debian/control index 446f8a22..8b183d5d 100644 --- a/debian/control +++ b/debian/control @@ -4,7 +4,7 @@ Priority: extra Maintainer: Michael Stapelberg DM-Upload-Allowed: yes Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl -Standards-Version: 3.8.3 +Standards-Version: 3.8.4 Homepage: http://i3.zekjur.net/ Package: i3 @@ -25,7 +25,7 @@ Section: x11 Depends: ${shlibs:Depends}, ${misc:Depends}, x11-utils Provides: x-window-manager Suggests: rxvt-unicode | x-terminal-emulator -Recommends: xfonts-base +Recommends: xfonts-base, libanyevent-i3-perl, libanyevent-perl, libipc-run-perl Description: an improved dynamic tiling window manager Key features of i3 are good support of multi-monitor setups (workspaces are assigned to virtual screens, i3 does the right thing when attaching new From 169e541101777fd3a3cd32f0adb6b6329d5ba6d7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 May 2010 12:08:39 +0200 Subject: [PATCH 077/867] Bugfix: Correctly check asprintf() return value Fixes a crash when invalid multibyte window titles are set as _NET_WM_NAME --- src/handlers.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index a173777c..12e81f71 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -613,11 +613,19 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, /* Save the old pointer to make the update atomic */ char *new_name; int new_len; - asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)); + if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) { + perror("asprintf"); + LOG("Could not format _NET_WM_NAME, ignoring new hint\n"); + return 1; + } /* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */ char *ucs2_name = convert_utf8_to_ucs2(new_name, &new_len); LOG("_NET_WM_NAME changed to \"%s\"\n", new_name); free(new_name); + if (ucs2_name == NULL) { + LOG("Could not convert _NET_WM_NAME to UCS-2, ignoring new hint\n"); + return 1; + } /* Check if they are the same and don’t update if so. Note the use of new_len * 2 to check all bytes as each glyph takes 2 bytes. From e67c712f31a577958a1149272a4b7ffbed3ba94e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 31 May 2010 00:11:11 +0200 Subject: [PATCH 078/867] cleanup: introduce CT_WORKSPACE as type to avoid having to check parent->type --- include/data.h | 2 +- src/con.c | 4 ++-- src/tree.c | 14 ++++++++------ src/workspace.c | 1 + 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/include/data.h b/include/data.h index bdfcbcc3..22884aa7 100644 --- a/include/data.h +++ b/include/data.h @@ -256,7 +256,7 @@ struct Match { struct Con { bool mapped; - enum { CT_ROOT = 0, CT_OUTPUT = 1, CT_CON = 2, CT_FLOATING_CON = 3 } type; + enum { CT_ROOT = 0, CT_OUTPUT = 1, CT_CON = 2, CT_FLOATING_CON = 3, CT_WORKSPACE = 4 } type; orientation_t orientation; struct Con *parent; /* parent before setting it to floating */ diff --git a/src/con.c b/src/con.c index 36070849..efd0a82a 100644 --- a/src/con.c +++ b/src/con.c @@ -107,7 +107,7 @@ bool con_is_leaf(Con *con) { */ bool con_accepts_window(Con *con) { /* 1: workspaces never accept direct windows */ - if (con->parent->type == CT_OUTPUT) + if (con->type == CT_WORKSPACE) return false; /* TODO: if this is a swallowing container, we need to check its max_clients */ @@ -135,7 +135,7 @@ Con *con_get_output(Con *con) { */ Con *con_get_workspace(Con *con) { Con *result = con; - while (result != NULL && result->parent->type != CT_OUTPUT) + while (result != NULL && result->type != CT_WORKSPACE) result = result->parent; assert(result != NULL); return result; diff --git a/src/tree.c b/src/tree.c index d8fc9f4a..5a3a7613 100644 --- a/src/tree.c +++ b/src/tree.c @@ -71,6 +71,7 @@ void tree_init() { /* add a workspace to this output */ ws = con_new(oc); + ws->type = CT_WORKSPACE; ws->name = strdup("1"); ws->fullscreen_mode = CF_OUTPUT; } @@ -160,7 +161,7 @@ void tree_close(Con *con, bool kill_window) { void tree_close_con() { assert(focused != NULL); - if (focused->parent->type == CT_OUTPUT) { + if (focused->type == CT_WORKSPACE) { LOG("Cannot close workspace\n"); return; } @@ -176,7 +177,7 @@ void tree_close_con() { */ void tree_split(Con *con, orientation_t orientation) { /* for a workspace, we just need to change orientation */ - if (con->parent->type == CT_OUTPUT) { + if (con->type == CT_WORKSPACE) { con->orientation = orientation; return; } @@ -194,7 +195,8 @@ void tree_split(Con *con, orientation_t orientation) { void level_up() { /* We can focus up to the workspace, but not any higher in the tree */ - if (focused->parent->type != CT_CON) { + if (focused->parent->type != CT_CON && + focused->parent->type != CT_WORKSPACE) { printf("cannot go up\n"); return; } @@ -246,7 +248,7 @@ void tree_next(char way, orientation_t orientation) { LOG("need to go one level further up\n"); /* if the current parent is an output, we are at a workspace * and the orientation still does not match */ - if (parent->parent->type == CT_OUTPUT) + if (parent->type == CT_WORKSPACE) return; parent = parent->parent; } @@ -279,14 +281,14 @@ void tree_next(char way, orientation_t orientation) { void tree_move(char way, orientation_t orientation) { /* 1: get the first parent with the same orientation */ Con *parent = focused->parent; - if (parent->type == CT_OUTPUT) + if (focused->type == CT_WORKSPACE) return; bool level_changed = false; while (parent->orientation != orientation) { LOG("need to go one level further up\n"); /* if the current parent is an output, we are at a workspace * and the orientation still does not match */ - if (parent->parent->type == CT_OUTPUT) + if (parent->type == CT_WORKSPACE) return; parent = parent->parent; level_changed = true; diff --git a/src/workspace.c b/src/workspace.c index 6404cff0..457603ee 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -39,6 +39,7 @@ Con *workspace_get(const char *num) { output = con_get_output(focused); LOG("got output %p\n", output); workspace = con_new(output); + workspace->type = CT_WORKSPACE; workspace->name = strdup(num); ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); From 71e0e49c0e47285eaa17ecec6b1f431e21e148e9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 31 May 2010 22:48:28 +0200 Subject: [PATCH 079/867] Implement mode toggle --- src/cmdparse.l | 1 + src/cmdparse.y | 11 +++++++++-- src/floating.c | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/cmdparse.l b/src/cmdparse.l index 55b1d28f..0102d145 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -95,6 +95,7 @@ none { return TOK_NONE; } mode { return TOK_MODE; } tiling { return TOK_TILING; } floating { return TOK_FLOATING; } +toggle { return TOK_TOGGLE; } workspace { BEGIN(WANT_WS_STRING); return TOK_WORKSPACE; } focus { return TOK_FOCUS; } move { return TOK_MOVE; } diff --git a/src/cmdparse.y b/src/cmdparse.y index 31d7535b..cc710a49 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -109,6 +109,7 @@ void parse_cmd(const char *new) { %token TOK_TILING "tiling" %token TOK_FLOATING "floating" %token TOK_WORKSPACE "workspace" +%token TOK_TOGGLE "toggle" %token TOK_FOCUS "focus" %token TOK_MOVE "move" %token TOK_OPEN "open" @@ -398,14 +399,20 @@ direction: mode: TOK_MODE WHITESPACE window_mode { - printf("should switch mode to %s\n", ($3 == TOK_FLOATING ? "floating" : "tiling")); - /* TODO: actually switch mode (not toggle) */ + if ($3 == TOK_TOGGLE) { + printf("should toggle mode\n"); + toggle_floating_mode(focused, false); + } else { + printf("should switch mode to %s\n", ($3 == TOK_FLOATING ? "floating" : "tiling")); + /* TODO: actually switch mode (not toggle) */ + } } ; window_mode: TOK_FLOATING { $$ = TOK_FLOATING; } | TOK_TILING { $$ = TOK_TILING; } + | TOK_TOGGLE { $$ = TOK_TOGGLE; } ; level: diff --git a/src/floating.c b/src/floating.c index 2afe1b82..416873e5 100644 --- a/src/floating.c +++ b/src/floating.c @@ -37,6 +37,7 @@ void toggle_floating_mode(Con *con, bool automatic) { /* 2: kill parent container */ TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows); + TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused); tree_close(con->parent, false); /* 3: re-attach to previous parent */ @@ -44,6 +45,8 @@ void toggle_floating_mode(Con *con, bool automatic) { TAILQ_INSERT_TAIL(&(con->parent->nodes_head), con, nodes); TAILQ_INSERT_TAIL(&(con->parent->focus_head), con, focused); + con->floating = FLOATING_USER_OFF; + return; } @@ -72,6 +75,7 @@ void toggle_floating_mode(Con *con, bool automatic) { nc->orientation = NO_ORIENTATION; nc->type = CT_FLOATING_CON; TAILQ_INSERT_TAIL(&(nc->parent->floating_head), nc, floating_windows); + TAILQ_INSERT_TAIL(&(nc->parent->focus_head), nc, focused); /* 3: attach the child to the new parent container */ con->old_parent = con->parent; From 246d4627be4ced1375b8735828bb7e0d992f67fa Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 31 May 2010 23:00:36 +0200 Subject: [PATCH 080/867] re-enable sending fake configure notifies --- include/xcb.h | 4 +--- src/x.c | 8 ++++++++ src/xcb.c | 16 ++++++++-------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/include/xcb.h b/include/xcb.h index a7aeaf21..3f4b735c 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -127,14 +127,12 @@ void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, */ void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window); -#if 0 /** * Generates a configure_notify_event with absolute coordinates (relative to * the X root window, not to the client’s frame) for the given client. * */ -void fake_absolute_configure_notify(xcb_connection_t *conn, Client *client); -#endif +void fake_absolute_configure_notify(Con *con); /** * Finds out which modifier mask is the one for numlock, as the user may diff --git a/src/x.c b/src/x.c index f73fc846..5e0ce71c 100644 --- a/src/x.c +++ b/src/x.c @@ -239,11 +239,13 @@ static void x_push_node(Con *con) { state->mapped = con->mapped; } + bool fake_notify = false; /* set new position if rect changed */ if (memcmp(&(state->rect), &(con->rect), sizeof(Rect)) != 0) { LOG("setting rect (%d, %d, %d, %d)\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height); xcb_set_window_rect(conn, con->frame, con->rect); memcpy(&(state->rect), &(con->rect), sizeof(Rect)); + fake_notify = true; } /* dito, but for child windows */ @@ -252,6 +254,12 @@ static void x_push_node(Con *con) { con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height); xcb_set_window_rect(conn, con->window->id, con->window_rect); memcpy(&(state->rect), &(con->rect), sizeof(Rect)); + fake_notify = true; + } + + if (fake_notify) { + LOG("Sending fake configure notify\n"); + fake_absolute_configure_notify(con); } /* handle all children and floating windows of this node */ diff --git a/src/xcb.c b/src/xcb.c index 3da1081d..78b7a3b1 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -172,23 +172,23 @@ void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window) xcb_flush(conn); } -#if 0 /* * Generates a configure_notify_event with absolute coordinates (relative to the X root * window, not to the client’s frame) for the given client. * */ -void fake_absolute_configure_notify(xcb_connection_t *conn, Client *client) { +void fake_absolute_configure_notify(Con *con) { Rect absolute; + if (con->window == NULL) + return; - absolute.x = client->rect.x + client->child_rect.x; - absolute.y = client->rect.y + client->child_rect.y; - absolute.width = client->child_rect.width; - absolute.height = client->child_rect.height; + absolute.x = con->rect.x + con->window_rect.x; + absolute.y = con->rect.y + con->window_rect.y; + absolute.width = con->window_rect.width; + absolute.height = con->window_rect.height; - fake_configure_notify(conn, absolute, client->child); + fake_configure_notify(conn, absolute, con->window->id); } -#endif /* * Finds out which modifier mask is the one for numlock, as the user may change this. From 143622d2d797f820fea73a175fd867d0d17c9dbf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 31 May 2010 23:01:08 +0200 Subject: [PATCH 081/867] Reposition floating windows while dragging --- src/floating.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/floating.c b/src/floating.c index 416873e5..b82d5f17 100644 --- a/src/floating.c +++ b/src/floating.c @@ -314,11 +314,10 @@ DRAGGING_CB(drag_window_callback) { /* Reposition the client correctly while moving */ con->rect.x = old_rect->x + (new_x - event->root_x); con->rect.y = old_rect->y + (new_y - event->root_y); - //reposition_client(conn, con); - /* Because reposition_client does not send a faked configure event (only resize does), - * we need to initiate that on our own */ - //fake_absolute_configure_notify(conn, client); - /* fake_absolute_configure_notify flushes */ + /* TODO: don’t re-render the whole tree just because we change + * coordinates of a floating window */ + tree_render(); + x_push_changes(croot); } /* From b14fa457e7196cee7185a1ebe85a0955928fdcf5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 31 May 2010 23:08:16 +0200 Subject: [PATCH 082/867] fix: to always abort we need to assert(false) --- src/x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/x.c b/src/x.c index 5e0ce71c..f0a34d3b 100644 --- a/src/x.c +++ b/src/x.c @@ -48,7 +48,7 @@ static con_state *state_for_frame(xcb_window_t window) { /* TODO: better error handling? */ ELOG("No state found\n"); - assert(true); + assert(false); return NULL; } From 935b8e05ffa4964d80c6bf41b326b87138b32e9f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 31 May 2010 23:16:20 +0200 Subject: [PATCH 083/867] Implement correct removal of floating containers --- src/con.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/con.c b/src/con.c index efd0a82a..411c8df6 100644 --- a/src/con.c +++ b/src/con.c @@ -67,7 +67,8 @@ void con_attach(Con *con, Con *parent) { void con_detach(Con *con) { if (con->type == CT_FLOATING_CON) { - /* TODO: remove */ + TAILQ_REMOVE(&(con->parent->floating_head), con, floating_windows); + TAILQ_REMOVE(&(con->parent->focus_head), con, focused); } else { TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); TAILQ_REMOVE(&(con->parent->focus_head), con, focused); From de0c13ba78012eeb84f3a195eb23da50d6e2a1cb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 31 May 2010 23:17:02 +0200 Subject: [PATCH 084/867] Add testcase which ensures that floating windows can be closed See last commit --- testcases/t/26-regress-close.t | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 testcases/t/26-regress-close.t diff --git a/testcases/t/26-regress-close.t b/testcases/t/26-regress-close.t new file mode 100644 index 00000000..9df3aafe --- /dev/null +++ b/testcases/t/26-regress-close.t @@ -0,0 +1,26 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Regression: closing of floating clients did crash i3 when closing the +# container which contained this client. +# +use i3test tests => 1; +use X11::XCB qw(:all); +use v5.10; + +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +$i3->command('open')->recv; +$i3->command('mode toggle')->recv; +$i3->command('kill')->recv; +$i3->command('kill')->recv; + + +my $tree = $i3->get_workspaces->recv; +my @nodes = @{$tree->{nodes}}; +ok(@nodes > 0, 'i3 still lives'); + +diag( "Testing i3, Perl $], $^X" ); From 67a6bd5589cf1c86c38deb2af54668d875eb52a7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Jun 2010 18:46:14 +0200 Subject: [PATCH 085/867] bugfix: allocate one more zero-byte to definitely get a zero-terminated string --- src/ipc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ipc.c b/src/ipc.c index c92f4480..e757ed22 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -112,8 +112,9 @@ void ipc_shutdown() { IPC_HANDLER(command) { /* To get a properly terminated buffer, we copy * message_size bytes out of the buffer */ - char *command = scalloc(message_size); + char *command = scalloc(message_size + 1); strncpy(command, (const char*)message, message_size); + LOG("IPC: received: *%s*\n", command); parse_cmd((const char*)command); tree_render(); free(command); From a25dc3e988802037ccd248ef9c053d5c2d494b50 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Jun 2010 18:49:43 +0200 Subject: [PATCH 086/867] Implement focus command (and extend t/21-next-prev.t to test it) --- src/cmdparse.y | 14 ++++++++++++++ testcases/t/21-next-prev.t | 9 ++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index cc710a49..40fd65af 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -303,7 +303,21 @@ attach: focus: TOK_FOCUS { + owindow *current; + printf("should focus\n"); + if (match_is_empty(¤t_match)) { + /* TODO: better error message */ + LOG("Error: The foucs command requires you to use some criteria.\n"); + return; + } + + /* TODO: warning if the match contains more than one entry. does not + * make so much sense when focusing */ + TAILQ_FOREACH(current, &owindows, owindows) { + LOG("focusing %p / %s\n", current->con, current->con->name); + con_focus(current->con); + } } ; diff --git a/testcases/t/21-next-prev.t b/testcases/t/21-next-prev.t index bc0e6025..6d9a6e5c 100644 --- a/testcases/t/21-next-prev.t +++ b/testcases/t/21-next-prev.t @@ -3,7 +3,7 @@ # # Tests focus switching (next/prev) # -use i3test tests => 13; +use i3test tests => 14; use X11::XCB qw(:all); use v5.10; @@ -81,4 +81,11 @@ $i3->command('next horizontal')->recv; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $right, 'right container focused'); +# Test focus command + +$i3->command(qq|[con_id="$mid"] focus|)->recv; +($nodes, $focus) = get_ws_content($tmp); +is($focus->[0], $mid, 'middle container focused'); + + diag( "Testing i3, Perl $], $^X" ); From 712605e69ff9ed316bdd8bfb82e10ccc1d7ff94c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Jun 2010 20:50:23 +0200 Subject: [PATCH 087/867] ipc: include floating-nodes in tree reply --- src/ipc.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ipc.c b/src/ipc.c index e757ed22..1ca0778b 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -168,6 +168,13 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { } y(array_close); + ystr("floating-nodes"); + y(array_open); + TAILQ_FOREACH(node, &(con->floating_head), floating_windows) { + dump_node(gen, node, inplace_restart); + } + y(array_close); + ystr("focus"); y(array_open); TAILQ_FOREACH(node, &(con->focus_head), nodes) { From afa8be9547248cb559dc473ae7e93833c28f1d5b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Jun 2010 20:52:22 +0200 Subject: [PATCH 088/867] lib/i3test: Implement get_focused --- testcases/t/lib/i3test.pm | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 4b76b7f4..da3c1b28 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -19,7 +19,7 @@ use AnyEvent::I3; #use Exporter qw(import); use base 'Exporter'; -our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content get_ws); +our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content get_ws get_focused); BEGIN { my $window_count = 0; @@ -76,6 +76,28 @@ sub get_ws_content { return wantarray ? ($cons[0]->{nodes}, $cons[0]->{focus}) : $cons[0]->{nodes}; } +sub get_focused { + my ($ws) = @_; + my $i3 = i3("/tmp/nestedcons"); + my $tree = $i3->get_workspaces->recv; + + my @ws = map { @{$_->{nodes}} } @{$tree->{nodes}}; + my @cons = grep { $_->{name} eq $ws } @ws; + my $con = $cons[0]; + + my @focused = @{$con->{focus}}; + my $lf; + while (@focused > 0) { + $lf = $focused[0]; + last unless defined($con->{focus}); + @focused = @{$con->{focus}}; + @cons = grep { $_->{id} == $lf } (@{$con->{nodes}}, @{$con->{'floating-nodes'}}); + $con = $cons[0]; + } + + return $lf; +} + sub get_ws { my ($name) = @_; my $i3 = i3("/tmp/nestedcons"); @@ -85,5 +107,4 @@ sub get_ws { return $cons[0]; } - 1 From 3aa1801392da0596a821b8d5447a6f1b2b210449 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Jun 2010 20:52:59 +0200 Subject: [PATCH 089/867] add testcase for crash on closing floating windows whose parent was killed --- testcases/t/27-regress-floating-parent.t | 40 ++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 testcases/t/27-regress-floating-parent.t diff --git a/testcases/t/27-regress-floating-parent.t b/testcases/t/27-regress-floating-parent.t new file mode 100644 index 00000000..827f9207 --- /dev/null +++ b/testcases/t/27-regress-floating-parent.t @@ -0,0 +1,40 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Regression: make a container floating, kill its parent, make it tiling again +# +use i3test tests => 3; +use X11::XCB qw(:all); +use v5.10; + +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +$i3->command('open')->recv; +$i3->command('open')->recv; +my $old = get_focused($tmp); +$i3->command('split v')->recv; +$i3->command('open')->recv; +my $floating = get_focused($tmp); +diag("focused floating: " . get_focused($tmp)); +$i3->command('mode toggle')->recv; +# TODO: eliminate this race conditition +sleep 1; +$i3->command(qq|[con_id="$old"] focus|)->recv; +is(get_focused($tmp), $old, 'old container focused'); + +$i3->command('kill')->recv; +$i3->command('kill')->recv; +$i3->command(qq|[con_id="$floating"] focus|)->recv; +is(get_focused($tmp), $floating, 'floating window focused'); + +sleep 1; +$i3->command('mode toggle')->recv; + +my $tree = $i3->get_workspaces->recv; +my @nodes = @{$tree->{nodes}}; +ok(@nodes > 0, 'i3 still lives'); + +diag( "Testing i3, Perl $], $^X" ); From 0ce62a755e6cca9827e10d18130d10225dc96daf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Jun 2010 21:34:47 +0200 Subject: [PATCH 090/867] "Re-parent" floating clients whose old_parent is being closed (makes t/27 pass) --- src/tree.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index 5a3a7613..7b31ea6b 100644 --- a/src/tree.c +++ b/src/tree.c @@ -105,12 +105,34 @@ Con *tree_open_con(Con *con) { return new; } +/* + * vanishing is the container that is about to be closed (so any floating + * client which has old_parent == vanishing needs to be "re-parented"). + * + */ +static void fix_floating_parent(Con *con, Con *vanishing) { + Con *child; + + if (con->old_parent == vanishing) { + LOG("Fixing vanishing old_parent (%p) of container %p to be %p\n", + vanishing, con, vanishing->parent); + con->old_parent = vanishing->parent; + } + + TAILQ_FOREACH(child, &(con->floating_head), floating_windows) + fix_floating_parent(child, vanishing); + + TAILQ_FOREACH(child, &(con->nodes_head), nodes) + fix_floating_parent(child, vanishing); +} + /* * Closes the given container including all children * */ void tree_close(Con *con, bool kill_window) { - /* TODO: check floating clients and adjust old_parent if necessary */ + /* check floating clients and adjust old_parent if necessary */ + fix_floating_parent(croot, con); /* Get the container which is next focused */ Con *next; From 249c3f58ab19f368e55b0051618672ee4cf143c8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Jun 2010 22:29:09 +0200 Subject: [PATCH 091/867] t/21: formatting --- testcases/t/21-next-prev.t | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testcases/t/21-next-prev.t b/testcases/t/21-next-prev.t index 6d9a6e5c..8e8d44ee 100644 --- a/testcases/t/21-next-prev.t +++ b/testcases/t/21-next-prev.t @@ -81,7 +81,9 @@ $i3->command('next horizontal')->recv; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $right, 'right container focused'); +###################################################################### # Test focus command +###################################################################### $i3->command(qq|[con_id="$mid"] focus|)->recv; ($nodes, $focus) = get_ws_content($tmp); From 18f7e1ffd1b89aedcea164be93d54e22cb93e2f0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Jun 2010 22:29:19 +0200 Subject: [PATCH 092/867] t/22: extend to verify that splitting in the same direction multiple times does not create new containers --- testcases/t/22-split.t | 47 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/testcases/t/22-split.t b/testcases/t/22-split.t index 95e387d1..fdfe0abc 100644 --- a/testcases/t/22-split.t +++ b/testcases/t/22-split.t @@ -3,7 +3,7 @@ # # Tests splitting # -use i3test tests => 11; +use i3test tests => 14; use X11::XCB qw(:all); use v5.10; @@ -13,10 +13,10 @@ my $tmp = get_unused_workspace(); $i3->command("workspace $tmp")->recv; my $ws = get_ws($tmp); -is($ws->{orientation}, 0, 'orientation horizontal by default'); +is($ws->{orientation}, 1, 'orientation horizontal by default'); $i3->command('split v')->recv; $ws = get_ws($tmp); -is($ws->{orientation}, 1, 'split v changes workspace orientation'); +is($ws->{orientation}, 2, 'split v changes workspace orientation'); ###################################################################### # Open two containers, split, open another container. Then verify @@ -46,7 +46,7 @@ $second = $content->[1]; is(@{$first->{nodes}}, 0, 'first container has no children'); isnt($second->{name}, $old_name, 'second container was replaced'); -is($second->{orientation}, 0, 'orientation is horizontal'); +is($second->{orientation}, 1, 'orientation is horizontal'); is(@{$second->{nodes}}, 2, 'second container has 2 children'); is($second->{nodes}->[0]->{name}, $old_name, 'found old second container'); @@ -54,5 +54,44 @@ is($second->{nodes}->[0]->{name}, $old_name, 'found old second container'); # - wrapping (no horizontal switch possible, goes level-up) # - going level-up "manually" +###################################################################### +# Test splitting multiple times without actually creating windows +###################################################################### + +$tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +$ws = get_ws($tmp); +is($ws->{orientation}, 1, 'orientation horizontal by default'); +$i3->command('split v')->recv; +$ws = get_ws($tmp); +is($ws->{orientation}, 2, 'split v changes workspace orientation'); + +$i3->command('open')->recv; +my @content = @{get_ws_content($tmp)}; + +# recursively sums up all nodes and their children +sub sum_nodes { + my ($nodes) = @_; + + return 0 if !@{$nodes}; + + my @children = (map { @{$_->{nodes}} } @{$nodes}, + map { @{$_->{'floating-nodes'}} } @{$nodes}); + + return @{$nodes} + sum_nodes(\@children); +} + +my $old_count = sum_nodes(\@content); +$i3->command('split v')->recv; + +@content = @{get_ws_content($tmp)}; +my $old_count = sum_nodes(\@content); + +$i3->command('split v')->recv; + +@content = @{get_ws_content($tmp)}; +my $count = sum_nodes(\@content); +is($count, $old_count, 'not more windows after splitting again'); diag( "Testing i3, Perl $], $^X" ); From b467242d69cd9ac15ecd0c079b4662fc2d73afbd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Jun 2010 22:45:18 +0200 Subject: [PATCH 093/867] Make splitting a container which was already split a noop --- include/data.h | 2 +- src/tree.c | 11 ++++++++++- src/workspace.c | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/data.h b/include/data.h index 22884aa7..def5d4b7 100644 --- a/include/data.h +++ b/include/data.h @@ -41,7 +41,7 @@ typedef struct Window i3Window; * Helper types *****************************************************************************/ typedef enum { D_LEFT, D_RIGHT, D_UP, D_DOWN } direction_t; -typedef enum { HORIZ, VERT, NO_ORIENTATION } orientation_t; +typedef enum { NO_ORIENTATION = 0, HORIZ, VERT } orientation_t; enum { BIND_NONE = 0, diff --git a/src/tree.c b/src/tree.c index 7b31ea6b..c4315e31 100644 --- a/src/tree.c +++ b/src/tree.c @@ -74,6 +74,7 @@ void tree_init() { ws->type = CT_WORKSPACE; ws->name = strdup("1"); ws->fullscreen_mode = CF_OUTPUT; + ws->orientation = HORIZ; } con_focus(ws); @@ -203,9 +204,17 @@ void tree_split(Con *con, orientation_t orientation) { con->orientation = orientation; return; } + + Con *parent = con->parent; + /* if we are in a container whose parent contains only one + * child and has the same orientation like we are trying to + * set, this operation is a no-op to not confuse the user */ + if (parent->orientation == orientation && + TAILQ_NEXT(con, nodes) == TAILQ_END(&(parent->nodes_head))) + return; + /* 2: replace it with a new Con */ Con *new = con_new(NULL); - Con *parent = con->parent; TAILQ_REPLACE(&(parent->nodes_head), con, new, nodes); TAILQ_REPLACE(&(parent->focus_head), con, new, focused); new->parent = parent; diff --git a/src/workspace.c b/src/workspace.c index 457603ee..17986baf 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -41,6 +41,7 @@ Con *workspace_get(const char *num) { workspace = con_new(output); workspace->type = CT_WORKSPACE; workspace->name = strdup(num); + workspace->orientation = HORIZ; ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); } From cea8f91e18660f30c2c81a6ef4600c3f757e6fd7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Jun 2010 23:20:57 +0200 Subject: [PATCH 094/867] parser: implement 'layout' --- src/cmdparse.l | 1 + src/cmdparse.y | 27 ++++++++++++++++++++++++++- src/render.c | 3 ++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/cmdparse.l b/src/cmdparse.l index 0102d145..8850ff64 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -88,6 +88,7 @@ global { return TOK_GLOBAL; } layout { return TOK_LAYOUT; } default { return TOK_DEFAULT; } stacked { return TOK_STACKED; } +stacking { return TOK_STACKED; } tabbed { return TOK_TABBED; } border { return TOK_BORDER; } none { return TOK_NONE; } diff --git a/src/cmdparse.y b/src/cmdparse.y index 40fd65af..51690a59 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -252,8 +252,8 @@ operation: | restart /*| reload | mark - | layout | border */ + | layout | restore | move | workspace @@ -466,3 +466,28 @@ restore: tree_append_json($3); } ; + +layout: + TOK_LAYOUT WHITESPACE layout_mode + { + printf("changing layout to %d\n", $3); + owindow *current; + + /* check if the match is empty, not if the result is empty */ + if (match_is_empty(¤t_match)) + focused->layout = $3; + else { + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + current->con->layout = $3; + } + } + + } + ; + +layout_mode: + TOK_DEFAULT { $$ = L_DEFAULT; } + | TOK_STACKED { $$ = L_STACKED; } + | TOK_TABBED { $$ = L_TABBED; } + ; diff --git a/src/render.c b/src/render.c index c0dbdd5f..51e0fc26 100644 --- a/src/render.c +++ b/src/render.c @@ -124,7 +124,8 @@ void render_con(Con *con) { /* in a stacking container, we ensure the focused client is raised */ if (con->layout == L_STACKED) { Con *foc = TAILQ_FIRST(&(con->focus_head)); - x_raise_con(foc); + if (foc != TAILQ_END(&(con->focus_head))) + x_raise_con(foc); } TAILQ_FOREACH(child, &(con->floating_head), floating_windows) { From f2148ffa0b41b875ca96dbcdddc09ef784fde8ec Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 2 Jun 2010 11:21:52 +0200 Subject: [PATCH 095/867] Include git branch name in the version string --- common.mk | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common.mk b/common.mk index 0334ac61..349da32f 100644 --- a/common.mk +++ b/common.mk @@ -7,7 +7,8 @@ SYSCONFDIR=/etc else SYSCONFDIR=$(PREFIX)/etc endif -GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1))" +# The escaping is absurd, but we need to escape for shell, sed, make, define +GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f .git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' .git/HEAD || echo 'unknown'))" VERSION:=$(shell git describe --tags --abbrev=0) CFLAGS += -std=c99 From c1789bef8e3f2072b78ad565049fce27a59d71dd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 2 Jun 2010 11:21:52 +0200 Subject: [PATCH 096/867] Include git branch name in the version string --- common.mk | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common.mk b/common.mk index 6887470f..00300cf8 100644 --- a/common.mk +++ b/common.mk @@ -7,7 +7,8 @@ SYSCONFDIR=/etc else SYSCONFDIR=$(PREFIX)/etc endif -GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1))" +# The escaping is absurd, but we need to escape for shell, sed, make, define +GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f .git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' .git/HEAD || echo 'unknown'))" VERSION:=$(shell git describe --tags --abbrev=0) CFLAGS += -std=c99 From a7d2c5942a155a19263c8910195f4cf99a78b13e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 2 Jun 2010 17:02:10 +0200 Subject: [PATCH 097/867] Clear event mask while reparenting This way, we can avoid to ignore UnmapNotify events generated by reparenting. It is generally considerable to have as little ignored events as possible due to side-effects. --- src/manage.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/manage.c b/src/manage.c index 7983baac..4305abd1 100644 --- a/src/manage.c +++ b/src/manage.c @@ -124,9 +124,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki LOG("reparenting!\n"); uint32_t mask = 0; uint32_t values[1]; - mask = XCB_CW_EVENT_MASK; - values[0] = CHILD_EVENT_MASK; - xcb_change_window_attributes(conn, window, mask, values); i3Window *cwindow = scalloc(sizeof(i3Window)); cwindow->id = window; @@ -162,17 +159,24 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki nc = tree_open_con(nc->parent); } } + DLOG("new container = %p\n", nc); nc->window = cwindow; x_reinit(nc); + /* to avoid getting an UnmapNotify event due to reparenting, we temporarily + * declare no interest in any state change event of this window */ + values[0] = XCB_NONE; + xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK, values); + xcb_void_cookie_t rcookie = xcb_reparent_window_checked(conn, window, nc->frame, 0, 0); if (xcb_request_check(conn, rcookie) != NULL) { LOG("Could not reparent the window, aborting\n"); goto out; } - LOG("ignoring sequence %d for reparenting!\n", rcookie.sequence); - add_ignore_event(rcookie.sequence); + mask = XCB_CW_EVENT_MASK; + values[0] = CHILD_EVENT_MASK; + xcb_change_window_attributes(conn, window, mask, values); xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, state_cookie, NULL); if (xcb_reply_contains_atom(reply, atoms[_NET_WM_STATE_FULLSCREEN])) From 1c5adc6c35cffaedc08c7d1dd1b03a3269d1367c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 2 Jun 2010 17:03:26 +0200 Subject: [PATCH 098/867] =?UTF-8?q?Don=E2=80=99t=20ignore=20sequence=20in?= =?UTF-8?q?=20UnmapNotify,=20there=20might=20be=20multiple=20windows=20in?= =?UTF-8?q?=20one=20sequence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This would lead to i3 thinking that a new window was already managed if it has the same X-ID as the old window. Instead, we need to fix the EnterNotify problem in a different way. --- src/handlers.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index e351aea0..91872e4b 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -454,9 +454,11 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti bool ignored = event_is_ignored(event->sequence); + /* FIXME: we cannot ignore this sequence because more UnmapNotifys with the same sequence + * numbers but different window IDs may follow */ /* we need to ignore EnterNotify events which will be generated because a * different window is visible now */ - add_ignore_event(event->sequence); + //add_ignore_event(event->sequence); DLOG("UnmapNotify for 0x%08x (received from 0x%08x), serial %d\n", event->window, event->event, event->sequence); if (ignored) { From 14a312c152c658d2593fc9ffe4e1b20cf8e4b59a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 2 Jun 2010 17:04:26 +0200 Subject: [PATCH 099/867] more debug output --- src/handlers.c | 1 + src/manage.c | 2 +- src/tree.c | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 91872e4b..7613110c 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -421,6 +421,7 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure * */ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event) { + DLOG("configure_event, sequence %d\n", event->sequence); /* We ignore this sequence twice because events for child and frame should be ignored */ add_ignore_event(event->sequence); add_ignore_event(event->sequence); diff --git a/src/manage.c b/src/manage.c index 4305abd1..5f5bb44c 100644 --- a/src/manage.c +++ b/src/manage.c @@ -111,7 +111,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* Check if the window is already managed */ if (con_by_window_id(window) != NULL) { - LOG("already managed\n"); + LOG("already managed (by con %p)\n", con_by_window_id(window)); goto out; } diff --git a/src/tree.c b/src/tree.c index c4315e31..048fd549 100644 --- a/src/tree.c +++ b/src/tree.c @@ -147,7 +147,7 @@ void tree_close(Con *con, bool kill_window) { next = con->parent; } - LOG("closing %p\n", con); + DLOG("closing %p, kill_window = %d\n", con, kill_window); Con *child; /* We cannot use TAILQ_FOREACH because the children get deleted * in their parent’s nodes_head */ From 7c3e88ad9372e5fd74aff8cd4be47c23e426aafc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 2 Jun 2010 17:20:32 +0200 Subject: [PATCH 100/867] parser: implement matching on the window id --- src/cmdparse.y | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/cmdparse.y b/src/cmdparse.y index 51690a59..ab3dd4cc 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -238,6 +238,13 @@ criteria: current_match.con_id = atoi($3); printf("id as int = %d\n", current_match.con_id); } + | TOK_ID '=' STR + { + printf("criteria: window id = %s\n", $3); + /* TODO: correctly parse number */ + current_match.id = atoi($3); + printf("window id as int = %d\n", current_match.id); + } ; operations: From 03c8da0a74463fb9506905685c340572a0b16304 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 2 Jun 2010 17:50:06 +0200 Subject: [PATCH 101/867] =?UTF-8?q?t/lib/i3test.pm:=20refactor=20get=5Fws?= =?UTF-8?q?=20and=20get=5Fws=5Fcontent=20using=20List::Util=E2=80=99s=20fi?= =?UTF-8?q?rst?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- testcases/t/lib/i3test.pm | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index da3c1b28..f3a9a2e5 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -15,6 +15,7 @@ use X11::XCB::Rect; use X11::XCB::Window; use X11::XCB qw(:all); use AnyEvent::I3; +use List::Util qw(first); # Test::Kit already uses Exporter #use Exporter qw(import); use base 'Exporter'; @@ -60,6 +61,17 @@ sub get_unused_workspace { $tmp } +sub get_ws { + my ($name) = @_; + my $i3 = i3("/tmp/nestedcons"); + my $tree = $i3->get_workspaces->recv; + my @ws = map { @{$_->{nodes}} } @{$tree->{nodes}}; + + # as there can only be one workspace with this name, we can safely + # return the first entry + return first { $_->{name} eq $name } @ws; +} + # # returns the content (== tree, starting from the node of a workspace) # of a workspace. If called in array context, also includes the focus @@ -67,13 +79,8 @@ sub get_unused_workspace { # sub get_ws_content { my ($name) = @_; - my $i3 = i3("/tmp/nestedcons"); - my $tree = $i3->get_workspaces->recv; - my @ws = map { @{$_->{nodes}} } @{$tree->{nodes}}; - my @cons = grep { $_->{name} eq $name } @ws; - # as there can only be one workspace with this name, we can safely - # return the first entry - return wantarray ? ($cons[0]->{nodes}, $cons[0]->{focus}) : $cons[0]->{nodes}; + my $con = get_ws($name); + return wantarray ? ($con->{nodes}, $con->{focus}) : $con->{nodes}; } sub get_focused { @@ -98,13 +105,4 @@ sub get_focused { return $lf; } -sub get_ws { - my ($name) = @_; - my $i3 = i3("/tmp/nestedcons"); - my $tree = $i3->get_workspaces->recv; - my @ws = map { @{$_->{nodes}} } @{$tree->{nodes}}; - my @cons = grep { $_->{name} eq $name } @ws; - return $cons[0]; -} - 1 From 32be3af10966addeb8c5b84e5364d9f189e6a90c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 2 Jun 2010 17:51:58 +0200 Subject: [PATCH 102/867] Re-implement support for the urgency hint, extend t/13-urgent.t The actual rendering will follow --- include/data.h | 4 +++ include/handlers.h | 2 ++ include/workspace.h | 4 ++- src/con.c | 4 +++ src/handlers.c | 78 ++++++++++++++++++++++------------------- src/ipc.c | 3 ++ src/nc.c | 4 +++ src/workspace.c | 38 +++++++++++--------- testcases/t/13-urgent.t | 75 ++++++++++++++++++++++++++++----------- 9 files changed, 137 insertions(+), 75 deletions(-) diff --git a/include/data.h b/include/data.h index def5d4b7..f74f0d52 100644 --- a/include/data.h +++ b/include/data.h @@ -272,6 +272,10 @@ struct Con { struct Window *window; + /* Should this container be marked urgent? This gets set when the window + * inside this container (if any) sets the urgency hint, for example. */ + bool urgent; + /* ids/gc for the frame window */ xcb_window_t frame; xcb_gcontext_t gc; diff --git a/include/handlers.h b/include/handlers.h index 12d64c48..73cafe4b 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -171,12 +171,14 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *reply); +#endif /** * Handles the WM_HINTS property for extracting the urgency state of the window. * */ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *reply); +#if 0 /** * Handles the transient for hints set by a window, signalizing that this diff --git a/include/workspace.h b/include/workspace.h index 7a61daa8..28b7e571 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -90,14 +90,16 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws); * */ void workspace_map_clients(xcb_connection_t *conn, Workspace *ws); +#endif /** * Goes through all clients on the given workspace and updates the workspace’s * urgent flag accordingly. * */ -void workspace_update_urgent_flag(Workspace *ws); +void workspace_update_urgent_flag(Con *ws); +#if 0 /* * Returns the width of the workspace. * diff --git a/src/con.c b/src/con.c index 411c8df6..19227708 100644 --- a/src/con.c +++ b/src/con.c @@ -91,6 +91,10 @@ void con_focus(Con *con) { con_focus(con->parent); focused = con; + if (con->urgent) { + con->urgent = false; + workspace_update_urgent_flag(con_get_workspace(con)); + } } /* diff --git a/src/handlers.c b/src/handlers.c index 7613110c..990f7dd2 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -810,6 +810,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w return 1; } +#endif /* * Handles the WM_HINTS property for extracting the urgency state of the window. @@ -817,46 +818,49 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w */ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *reply) { - Client *client = table_get(&by_child, window); - if (client == NULL) { - DLOG("Received WM_HINTS for unknown client\n"); - return 1; - } - xcb_wm_hints_t hints; - - if (reply != NULL) { - if (!xcb_get_wm_hints_from_reply(&hints, reply)) - return 1; - } else { - if (!xcb_get_wm_hints_reply(conn, xcb_get_wm_hints_unchecked(conn, client->child), &hints, NULL)) - return 1; - } - - Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); - if (!client->urgent && client == last_focused) { - DLOG("Ignoring urgency flag for current client\n"); - return 1; - } - - /* Update the flag on the client directly */ - client->urgent = (xcb_wm_hints_get_urgency(&hints) != 0); - CLIENT_LOG(client); - LOG("Urgency flag changed to %d\n", client->urgent); - - workspace_update_urgent_flag(client->workspace); - redecorate_window(conn, client); - - /* If the workspace this client is on is not visible, we need to redraw - * the workspace bar */ - if (!workspace_is_visible(client->workspace)) { - Output *output = client->workspace->output; - render_workspace(conn, output, output->current_workspace); - xcb_flush(conn); - } - + Con *con = con_by_window_id(window); + if (con == NULL) { + DLOG("Received WM_HINTS for unknown client\n"); return 1; + } + + xcb_wm_hints_t hints; + + if (reply != NULL) { + if (!xcb_get_wm_hints_from_reply(&hints, reply)) + return 1; + } else { + if (!xcb_get_wm_hints_reply(conn, xcb_get_wm_hints_unchecked(conn, con->window->id), &hints, NULL)) + return 1; + } + + if (!con->urgent && focused == con) { + DLOG("Ignoring urgency flag for current client\n"); + return 1; + } + + /* Update the flag on the client directly */ + con->urgent = (xcb_wm_hints_get_urgency(&hints) != 0); + //CLIENT_LOG(con); + LOG("Urgency flag changed to %d\n", con->urgent); + + workspace_update_urgent_flag(con_get_workspace(con)); + +#if 0 + /* If the workspace this client is on is not visible, we need to redraw + * the workspace bar */ + if (!workspace_is_visible(client->workspace)) { + Output *output = client->workspace->output; + render_workspace(conn, output, output->current_workspace); + xcb_flush(conn); + } +#endif + + return 1; } +#if 0 + /* * Handles the transient for hints set by a window, signalizing that this window is a popup window * for some other window. diff --git a/src/ipc.c b/src/ipc.c index 1ca0778b..bde24852 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -137,6 +137,9 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("orientation"); y(integer, con->orientation); + ystr("urgent"); + y(integer, con->urgent); + ystr("layout"); y(integer, con->layout); diff --git a/src/nc.c b/src/nc.c index ac8590af..a5769d17 100644 --- a/src/nc.c +++ b/src/nc.c @@ -2,6 +2,7 @@ * vim:ts=4:sw=4:expandtab */ #include +#include #include "all.h" static int xkb_event_base; @@ -254,6 +255,9 @@ int main(int argc, char *argv[]) { /* Watch _NET_WM_NAME (title of the window encoded in UTF-8) */ xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL); + /* Watch WM_HINTS (contains the urgent property) */ + xcb_property_set_handler(&prophs, WM_HINTS, UINT_MAX, handle_hints, NULL); + /* Watch WM_NAME (title of the window encoded in COMPOUND_TEXT) */ xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL); diff --git a/src/workspace.c b/src/workspace.c index 17986baf..6a81f30e 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -429,31 +429,37 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) { ignore_enter_notify_forall(conn, u_ws, false); } +#endif + +static bool get_urgency_flag(Con *con) { + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) + if (child->urgent || get_urgency_flag(child)) + return true; + + TAILQ_FOREACH(child, &(con->floating_head), floating_windows) + if (child->urgent || get_urgency_flag(child)) + return true; + + return false; +} /* * Goes through all clients on the given workspace and updates the workspace’s * urgent flag accordingly. * */ -void workspace_update_urgent_flag(Workspace *ws) { - Client *current; - bool old_flag = ws->urgent; - bool urgent = false; +void workspace_update_urgent_flag(Con *ws) { + bool old_flag = ws->urgent; + ws->urgent = get_urgency_flag(ws); + DLOG("Workspace urgency flag changed from %d to %d\n", old_flag, ws->urgent); - SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) { - if (!current->urgent) - continue; - - urgent = true; - break; - } - - ws->urgent = urgent; - - if (old_flag != urgent) - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}"); + if (old_flag != ws->urgent) + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}"); } +#if 0 + /* * Returns the width of the workspace. * diff --git a/testcases/t/13-urgent.t b/testcases/t/13-urgent.t index cf847456..710d1892 100644 --- a/testcases/t/13-urgent.t +++ b/testcases/t/13-urgent.t @@ -1,12 +1,10 @@ #!perl # vim:ts=4:sw=4:expandtab -# Beware that this test uses workspace 9 to perform some tests (it expects -# the workspace to be empty). -# TODO: skip it by default? -use i3test tests => 7; +use i3test tests => 10; use X11::XCB qw(:all); use Time::HiRes qw(sleep); +use List::Util qw(first); BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); @@ -14,33 +12,68 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $i3 = i3; - -# Switch to the nineth workspace -$i3->command('9')->recv; +my $i3 = i3("/tmp/nestedcons"); +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; ##################################################################### # Create two windows and put them in stacking mode ##################################################################### -my $top = i3test::open_standard_window($x); -sleep 0.25; -my $bottom = i3test::open_standard_window($x); -sleep 0.25; +$i3->command('split v')->recv; -$i3->command('s')->recv; +my $top = i3test::open_standard_window($x); +my $bottom = i3test::open_standard_window($x); + +my @urgent = grep { $_->{urgent} == 1 } @{get_ws_content($tmp)}; +is(@urgent, 0, 'no window got the urgent flag'); + +#$i3->command('layout stacking')->recv; ##################################################################### # Add the urgency hint, switch to a different workspace and back again ##################################################################### $top->add_hint('urgency'); -sleep 1; +sleep 0.5; -$i3->command('1')->recv; -$i3->command('9')->recv; -$i3->command('1')->recv; +@content = @{get_ws_content($tmp)}; +@urgent = grep { $_->{urgent} == 1 } @content; +$top_info = first { $_->{window} == $top->id } @content; +$bottom_info = first { $_->{window} == $bottom->id } @content; -my $std = i3test::open_standard_window($x); -sleep 0.25; -$std->add_hint('urgency'); -sleep 1; +is($top_info->{urgent}, 1, 'top window is marked urgent'); +is($bottom_info->{urgent}, 0, 'bottom window is not marked urgent'); +is(@urgent, 1, 'exactly one window got the urgent flag'); + +$i3->command('[id="' . $top->id . '"] focus')->recv; + +@urgent = grep { $_->{urgent} == 1 } @{get_ws_content($tmp)}; +is(@urgent, 0, 'no window got the urgent flag after focusing'); + +$top->add_hint('urgency'); +sleep 0.5; + +@urgent = grep { $_->{urgent} == 1 } @{get_ws_content($tmp)}; +is(@urgent, 0, 'no window got the urgent flag after re-setting urgency hint'); + +##################################################################### +# Check if the workspace urgency hint gets set/cleared correctly +##################################################################### +my $ws = get_ws($tmp); +is($ws->{urgent}, 0, 'urgent flag not set on workspace'); + +my $otmp = get_unused_workspace(); +$i3->command("workspace $otmp")->recv; + +$top->add_hint('urgency'); +sleep 0.5; + +$ws = get_ws($tmp); +is($ws->{urgent}, 1, 'urgent flag set on workspace'); + +$i3->command("workspace $tmp")->recv; + +$ws = get_ws($tmp); +is($ws->{urgent}, 0, 'urgent flag not set on workspace after switching'); + +diag( "Testing i3, Perl $], $^X" ); From 5bff638ea007f1290ab4bf9856794520010da328 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 2 Jun 2010 18:58:52 +0200 Subject: [PATCH 103/867] bugfix: the layout command needs to change the layout of the parent container --- src/cmdparse.y | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index ab3dd4cc..cae2d3c5 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -482,7 +482,7 @@ layout: /* check if the match is empty, not if the result is empty */ if (match_is_empty(¤t_match)) - focused->layout = $3; + focused->parent->layout = $3; else { TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); From bdb106553731438a2e4cb20edc420037f26612dd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 2 Jun 2010 22:34:31 +0200 Subject: [PATCH 104/867] bugfix: only print the first match when looking for the loglevel fixes problems with con.c, container.c, config.c --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b2b1b036..0b56d69b 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ endif # Depend on the specific file (.c for each .o) and on all headers src/%.o: src/%.c ${HEADERS} echo "CC $<" - $(CC) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR }' loglevels.tmp))" -c -o $@ $< + $(CC) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -c -o $@ $< all: src/cfgparse.y.o src/cfgparse.yy.o src/cmdparse.y.o src/cmdparse.yy.o ${FILES} echo "LINK i3" From 780e773a6a3e2cebb911a420522353fe4d95d680 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 2 Jun 2010 22:35:37 +0200 Subject: [PATCH 105/867] split containers do not directly accepts windows (they only have children) --- src/con.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/con.c b/src/con.c index 19227708..8aa1608c 100644 --- a/src/con.c +++ b/src/con.c @@ -115,6 +115,11 @@ bool con_accepts_window(Con *con) { if (con->type == CT_WORKSPACE) return false; + if (con->orientation != NO_ORIENTATION) { + DLOG("container %p does not accepts windows, orientation != NO_ORIENTATION\n", con); + return false; + } + /* TODO: if this is a swallowing container, we need to check its max_clients */ return (con->window == NULL); } From 6897e15e72ac222815b4a01d26179e026debd1ba Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 2 Jun 2010 23:32:05 +0200 Subject: [PATCH 106/867] Implement mark/goto, modify testcase --- include/data.h | 4 ++++ src/cmdparse.l | 2 ++ src/cmdparse.y | 32 ++++++++++++++++++++++++++++++++ src/match.c | 2 +- src/tree.c | 7 ++++++- testcases/t/11-goto.t | 29 ++++++++++++----------------- 6 files changed, 57 insertions(+), 19 deletions(-) diff --git a/include/data.h b/include/data.h index f74f0d52..ba70de0e 100644 --- a/include/data.h +++ b/include/data.h @@ -238,6 +238,7 @@ struct Match { char *application; char *class; char *instance; + char *mark; xcb_window_t id; Con *con_id; enum { M_ANY = 0, M_TILING, M_FLOATING } floating; @@ -268,6 +269,9 @@ struct Con { char *name; + /* user-definable mark to jump to this container later */ + char *mark; + double percent; struct Window *window; diff --git a/src/cmdparse.l b/src/cmdparse.l index 8850ff64..f085a2a4 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -112,10 +112,12 @@ down { return TOK_DOWN; } before { return TOK_BEFORE; } after { return TOK_AFTER; } restore { BEGIN(WANT_WS_STRING); return TOK_RESTORE; } +mark { BEGIN(WANT_WS_STRING); return TOK_MARK; } class { BEGIN(WANT_QSTRING); return TOK_CLASS; } id { BEGIN(WANT_QSTRING); return TOK_ID; } con_id { BEGIN(WANT_QSTRING); return TOK_CON_ID; } +con_mark { BEGIN(WANT_QSTRING); return TOK_MARK; } . { return (int)yytext[0]; } diff --git a/src/cmdparse.y b/src/cmdparse.y index cae2d3c5..f694e1ff 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -124,6 +124,7 @@ void parse_cmd(const char *new) { %token TOK_AFTER "after" %token TOK_BEFORE "before" %token TOK_RESTORE "restore" +%token TOK_MARK "mark" %token TOK_CLASS "class" %token TOK_ID "id" @@ -205,6 +206,11 @@ matchend: TAILQ_INSERT_TAIL(&owindows, current, owindows); } + } else if (current_match.mark != NULL && current->con->mark != NULL && + strcasecmp(current_match.mark, current->con->mark) == 0) { + printf("match by mark\n"); + TAILQ_INSERT_TAIL(&owindows, current, owindows); + } else { if (current->con->window == NULL) continue; @@ -245,6 +251,11 @@ criteria: current_match.id = atoi($3); printf("window id as int = %d\n", current_match.id); } + | TOK_MARK '=' STR + { + printf("criteria: mark = %s\n", $3); + current_match.mark = $3; + } ; operations: @@ -274,6 +285,7 @@ operation: | split | mode | level + | mark ; exec: @@ -498,3 +510,23 @@ layout_mode: | TOK_STACKED { $$ = L_STACKED; } | TOK_TABBED { $$ = L_TABBED; } ; + +mark: + TOK_MARK WHITESPACE STR + { + printf("marking window with str %s\n", $3); + owindow *current; + + /* check if the match is empty, not if the result is empty */ + if (match_is_empty(¤t_match)) + focused->mark = sstrdup($3); + else { + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + current->con->mark = sstrdup($3); + } + } + + free($3); + } + ; diff --git a/src/match.c b/src/match.c index 763a4e7e..603be7bf 100644 --- a/src/match.c +++ b/src/match.c @@ -13,6 +13,7 @@ bool match_is_empty(Match *match) { * TAILQ and I don’t want to start with things like assuming that the * last member of a struct really is at the end in memory… */ return (match->title == NULL && + match->mark == NULL && match->application == NULL && match->class == NULL && match->instance == NULL && @@ -33,7 +34,6 @@ bool match_matches_window(Match *match, i3Window *window) { return true; } - if (match->id != XCB_NONE && window->id == match->id) { LOG("match made by window id (%d)\n", window->id); return true; diff --git a/src/tree.c b/src/tree.c index 048fd549..a6f3dc13 100644 --- a/src/tree.c +++ b/src/tree.c @@ -201,6 +201,7 @@ void tree_close_con() { void tree_split(Con *con, orientation_t orientation) { /* for a workspace, we just need to change orientation */ if (con->type == CT_WORKSPACE) { + DLOG("Workspace, simply changing orientation to %d\n", orientation); con->orientation = orientation; return; } @@ -210,8 +211,12 @@ void tree_split(Con *con, orientation_t orientation) { * child and has the same orientation like we are trying to * set, this operation is a no-op to not confuse the user */ if (parent->orientation == orientation && - TAILQ_NEXT(con, nodes) == TAILQ_END(&(parent->nodes_head))) + TAILQ_NEXT(con, nodes) == TAILQ_END(&(parent->nodes_head))) { + DLOG("Not splitting the same way again\n"); return; + } + + DLOG("Splitting in orientation %d\n", orientation); /* 2: replace it with a new Con */ Con *new = con_new(NULL); diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t index ea17b406..31c5187f 100644 --- a/testcases/t/11-goto.t +++ b/testcases/t/11-goto.t @@ -1,10 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -# Beware that this test uses workspace 9 to perform some tests (it expects -# the workspace to be empty). -# TODO: skip it by default? -use i3test tests => 7; +use i3test tests => 6; use X11::XCB qw(:all); use Time::HiRes qw(sleep); use Digest::SHA1 qw(sha1_base64); @@ -15,21 +12,22 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $i3 = i3; +my $i3 = i3("/tmp/nestedcons"); +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; -# Switch to the nineth workspace -$i3->command('9')->recv; +$i3->command('split h')->recv; ##################################################################### # Create two windows and make sure focus switching works ##################################################################### my $top = i3test::open_standard_window($x); -sleep(0.25); +sleep 0.25; my $mid = i3test::open_standard_window($x); -sleep(0.25); +sleep 0.25; my $bottom = i3test::open_standard_window($x); -sleep(0.25); +sleep 0.25; diag("top id = " . $top->id); diag("mid id = " . $mid->id); @@ -49,10 +47,7 @@ sub focus_after { $focus = $x->input_focus; is($focus, $bottom->id, "Latest window focused"); -$focus = focus_after("ml"); -is($focus, $bottom->id, "Right window still focused"); - -$focus = focus_after("h"); +$focus = focus_after("prev h"); is($focus, $mid->id, "Middle window focused"); ##################################################################### @@ -61,14 +56,14 @@ is($focus, $mid->id, "Middle window focused"); my $random_mark = sha1_base64(rand()); -$focus = focus_after("goto $random_mark"); +$focus = focus_after(qq|[con_mark="$random_mark"] focus|); is($focus, $mid->id, "focus unchanged"); $i3->command("mark $random_mark")->recv; -$focus = focus_after("k"); +$focus = focus_after("prev h"); is($focus, $top->id, "Top window focused"); -$focus = focus_after("goto $random_mark"); +$focus = focus_after(qq|[con_mark="$random_mark"] focus|); is($focus, $mid->id, "goto worked"); From 285692c92cee4385a23b14982b902d3bd3ee0398 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 2 Jun 2010 23:53:22 +0200 Subject: [PATCH 107/867] Update testcases (and skip some tests for the moment) --- testcases/Makefile | 2 +- testcases/t/05-ipc.t | 12 ++-- testcases/t/06-focus.t | 99 ++++++++++++++++---------------- testcases/t/07-move.t | 4 ++ testcases/t/08-focus-stack.t | 16 +++--- testcases/t/09-stacking.t | 4 ++ testcases/t/12-floating-resize.t | 12 ++-- testcases/t/14-client-leader.t | 14 ++--- testcases/t/15-ipc-workspaces.t | 7 ++- testcases/t/16-nestedcons.t | 38 +++--------- 10 files changed, 98 insertions(+), 110 deletions(-) diff --git a/testcases/Makefile b/testcases/Makefile index 9741c24c..462ca397 100644 --- a/testcases/Makefile +++ b/testcases/Makefile @@ -1,5 +1,5 @@ test: - PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/22* + PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/*.t all: test diff --git a/testcases/t/05-ipc.t b/testcases/t/05-ipc.t index 13ebc4b2..62a3a9d4 100644 --- a/testcases/t/05-ipc.t +++ b/testcases/t/05-ipc.t @@ -1,7 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 3; +use i3test tests => 2; use X11::XCB qw(:all); use Time::HiRes qw(sleep); @@ -11,15 +11,14 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $i3 = i3; +my $i3 = i3("/tmp/nestedcons"); +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; ##################################################################### # Ensure IPC works by switching workspaces ##################################################################### -# Switch to the first workspace to get a clean testing environment -$i3->command('1')->recv; - # Create a window so we can get a focus different from NULL my $window = i3test::open_standard_window($x); diag("window->id = " . $window->id); @@ -30,7 +29,8 @@ my $focus = $x->input_focus; diag("old focus = $focus"); # Switch to the nineth workspace -$i3->command('9')->recv; +my $otmp = get_unused_workspace(); +$i3->command("workspace $otmp")->recv; my $new_focus = $x->input_focus; isnt($focus, $new_focus, "Focus changed"); diff --git a/testcases/t/06-focus.t b/testcases/t/06-focus.t index b91f8cc5..5c3de5d8 100644 --- a/testcases/t/06-focus.t +++ b/testcases/t/06-focus.t @@ -1,10 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -# Beware that this test uses workspace 9 to perform some tests (it expects -# the workspace to be empty). -# TODO: skip it by default? -use i3test tests => 13; +use i3test tests => 6; use X11::XCB qw(:all); use Time::HiRes qw(sleep); @@ -14,17 +11,17 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $i3 = i3; - -# Switch to the nineth workspace -$i3->command('9')->recv; +my $i3 = i3("/tmp/nestedcons"); +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; ##################################################################### # Create two windows and make sure focus switching works ##################################################################### # Change mode of the container to "default" for following tests -$i3->command('d')->recv; +$i3->command('layout default')->recv; +$i3->command('split v')->recv; my $top = i3test::open_standard_window($x); my $mid = i3test::open_standard_window($x); @@ -49,64 +46,64 @@ sub focus_after { $focus = $x->input_focus; is($focus, $bottom->id, "Latest window focused"); -$focus = focus_after("k"); +$focus = focus_after("prev v"); is($focus, $mid->id, "Middle window focused"); -$focus = focus_after("k"); +$focus = focus_after("prev v"); is($focus, $top->id, "Top window focused"); ##################################################################### # Test focus wrapping ##################################################################### -$focus = focus_after("k"); +$focus = focus_after("prev v"); is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)"); -$focus = focus_after("j"); +$focus = focus_after("next v"); is($focus, $top->id, "Top window focused (wrapping to the bottom works)"); ############################################### # Test focus with empty containers and colspan ############################################### -# Switch to the 10. workspace -$i3->command('10')->recv; - -$top = i3test::open_standard_window($x); -$bottom = i3test::open_standard_window($x); -sleep 0.25; - -$focus = focus_after("mj"); -$focus = focus_after("mh"); -$focus = focus_after("k"); -is($focus, $bottom->id, "Selecting top window without snapping doesn't work"); - -$focus = focus_after("sl"); -is($focus, $bottom->id, "Bottom window focused"); - -$focus = focus_after("k"); -is($focus, $top->id, "Top window focused"); - -# Same thing, but left/right instead of top/bottom - -# Switch to the 11. workspace -$i3->command('11')->recv; - -my $left = i3test::open_standard_window($x); -my $right = i3test::open_standard_window($x); -sleep 0.25; - -$focus = focus_after("ml"); -$focus = focus_after("h"); -$focus = focus_after("mk"); -$focus = focus_after("l"); -is($focus, $left->id, "Selecting right window without snapping doesn't work"); - -$focus = focus_after("sj"); -is($focus, $left->id, "left window focused"); - -$focus = focus_after("l"); -is($focus, $right->id, "right window focused"); +#my $otmp = get_unused_workspace(); +#$i3->command("workspace $otmp")->recv; +# +#$top = i3test::open_standard_window($x); +#$bottom = i3test::open_standard_window($x); +#sleep 0.25; +# +#$focus = focus_after("mj"); +#$focus = focus_after("mh"); +#$focus = focus_after("k"); +#is($focus, $bottom->id, "Selecting top window without snapping doesn't work"); +# +#$focus = focus_after("sl"); +#is($focus, $bottom->id, "Bottom window focused"); +# +#$focus = focus_after("k"); +#is($focus, $top->id, "Top window focused"); +# +## Same thing, but left/right instead of top/bottom +# +#my $o2tmp = get_unused_workspace(); +#$i3->command("workspace $o2tmp")->recv; +# +#my $left = i3test::open_standard_window($x); +#my $right = i3test::open_standard_window($x); +#sleep 0.25; +# +#$focus = focus_after("ml"); +#$focus = focus_after("h"); +#$focus = focus_after("mk"); +#$focus = focus_after("l"); +#is($focus, $left->id, "Selecting right window without snapping doesn't work"); +# +#$focus = focus_after("sj"); +#is($focus, $left->id, "left window focused"); +# +#$focus = focus_after("l"); +#is($focus, $right->id, "right window focused"); diag( "Testing i3, Perl $], $^X" ); diff --git a/testcases/t/07-move.t b/testcases/t/07-move.t index c852d001..6e35ebe4 100644 --- a/testcases/t/07-move.t +++ b/testcases/t/07-move.t @@ -12,6 +12,9 @@ BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } +SKIP: { + skip "Testcase not yet modified for new move concept", 7; + my $x = X11::XCB::Connection->new; my $i3 = i3; @@ -76,3 +79,4 @@ for my $cmd (qw(m12 t m13 12 13)) { $i3->command($cmd)->recv; } ok(1, "Still living"); +} diff --git a/testcases/t/08-focus-stack.t b/testcases/t/08-focus-stack.t index 9dd7726f..ce04feca 100644 --- a/testcases/t/08-focus-stack.t +++ b/testcases/t/08-focus-stack.t @@ -13,17 +13,16 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $i3 = i3; +my $i3 = i3("/tmp/nestedcons"); +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; -# Switch to the nineth workspace -$i3->command('9')->recv; +$i3->command('split h')->recv; my $tiled_left = i3test::open_standard_window($x); my $tiled_right = i3test::open_standard_window($x); -sleep(0.25); - -$i3->command('ml')->recv; +sleep 0.25; # Get input focus before creating the floating window my $focus = $x->input_focus; @@ -40,12 +39,13 @@ isa_ok($window, 'X11::XCB::Window'); $window->map; -sleep(0.25); +sleep 1; +sleep 0.25; is($x->input_focus, $window->id, 'floating window focused'); $window->unmap; -sleep(0.25); +sleep 0.25; is($x->input_focus, $focus, 'Focus correctly restored'); diff --git a/testcases/t/09-stacking.t b/testcases/t/09-stacking.t index c809be9e..1cb205ed 100644 --- a/testcases/t/09-stacking.t +++ b/testcases/t/09-stacking.t @@ -12,6 +12,9 @@ BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } +SKIP: { + skip "stacking test not yet updated", 21; + my $x = X11::XCB::Connection->new; my $i3 = i3; @@ -126,3 +129,4 @@ is($focus, $bottom->id, "Window above is bottom"); $focus = focus_after("k"); is($focus, $mid->id, "Window above is mid"); +} diff --git a/testcases/t/12-floating-resize.t b/testcases/t/12-floating-resize.t index 0d309c68..a5fb3c57 100644 --- a/testcases/t/12-floating-resize.t +++ b/testcases/t/12-floating-resize.t @@ -12,12 +12,14 @@ BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } +SKIP: { + skip "border styles not yet implemented", 14; + my $x = X11::XCB::Connection->new; -my $i3 = i3; - -# Switch to the nineth workspace -$i3->command('9')->recv; +my $i3 = i3("/tmp/nestedcons"); +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; ##################################################################### # Create a floating window and see if resizing works @@ -76,3 +78,5 @@ test_resize; $i3->command('bp')->recv; test_resize; + +} diff --git a/testcases/t/14-client-leader.t b/testcases/t/14-client-leader.t index bf07b927..fb330d96 100644 --- a/testcases/t/14-client-leader.t +++ b/testcases/t/14-client-leader.t @@ -1,8 +1,5 @@ #!perl # vim:ts=4:sw=4:expandtab -# Beware that this test uses workspace 9 and 10 to perform some tests (it expects -# the workspace to be empty). -# TODO: skip it by default? use i3test tests => 3; use X11::XCB qw(:all); @@ -13,10 +10,10 @@ BEGIN { } my $x = X11::XCB::Connection->new; -my $i3 = i3; +my $i3 = i3("/tmp/nestedcons"); -# Switch to the nineth workspace -$i3->command('9')->recv; +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; ##################################################################### # Create a parent window @@ -37,7 +34,8 @@ sleep 0.25; # Switch workspace to 10 and open a child window. It should be positioned # on workspace 9. ######################################################################### -$i3->command('10')->recv; +my $otmp = get_unused_workspace(); +$i3->command("workspace $otmp")->recv; my $child = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, @@ -54,6 +52,6 @@ sleep 0.25; isnt($x->input_focus, $child->id, "Child window focused"); # Switch back -$i3->command('9')->recv; +$i3->command("workspace $tmp")->recv; is($x->input_focus, $child->id, "Child window focused"); diff --git a/testcases/t/15-ipc-workspaces.t b/testcases/t/15-ipc-workspaces.t index 3cf3e562..6c0996c5 100644 --- a/testcases/t/15-ipc-workspaces.t +++ b/testcases/t/15-ipc-workspaces.t @@ -4,12 +4,15 @@ use i3test tests => 2; use List::MoreUtils qw(all); -my $i3 = i3; +my $i3 = i3("/tmp/nestedcons"); #################### # Request workspaces #################### +SKIP: { + skip "IPC API not yet stabilized", 2; + my $workspaces = $i3->get_workspaces->recv; ok(@{$workspaces} > 0, "More than zero workspaces found"); @@ -17,4 +20,6 @@ ok(@{$workspaces} > 0, "More than zero workspaces found"); my $name_exists = all { defined($_->{name}) } @{$workspaces}; ok($name_exists, "All workspaces have a name"); +} + diag( "Testing i3, Perl $], $^X" ); diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t index 3159929b..e3f96911 100644 --- a/testcases/t/16-nestedcons.t +++ b/testcases/t/16-nestedcons.t @@ -1,7 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 8; +use i3test tests => 7; use List::MoreUtils qw(all none); my $i3 = i3("/tmp/nestedcons"); @@ -11,32 +11,6 @@ my $i3 = i3("/tmp/nestedcons"); #################### my $tree = $i3->get_workspaces->recv; -# $VAR1 = { -# 'fullscreen_mode' => 0, -# 'nodes' => [ -# { -# 'fullscreen_mode' => 0, -# 'nodes' => [ -# { -# 'fullscreen_mode' => 0, -# 'nodes' => [], -# 'window' => undef, -# 'name' => '1', -# 'orientation' => 0, -# 'type' => 2 -# } -# ], -# 'window' => undef, -# 'name' => 'LVDS1', -# 'orientation' => 0, -# 'type' => 1 -# } -# ], -# 'window' => undef, -# 'name' => 'root', -# 'orientation' => 0, -# 'type' => 0 -# }; my $expected = { fullscreen_mode => 0, @@ -49,6 +23,8 @@ my $expected = { rect => ignore(), layout => 0, focus => ignore(), + urgent => 0, + 'floating-nodes' => ignore(), }; cmp_deeply($tree, $expected, 'root node OK'); @@ -65,8 +41,8 @@ for my $ws (map { @{$_->{nodes}} } @nodes) { push @workspaces, $ws; } -ok((all { $_->{type} == 2 } @workspaces), 'all workspaces are of type CT_CON'); -ok((all { @{$_->{nodes}} == 0 } @workspaces), 'all workspaces are empty yet'); +ok((all { $_->{type} == 4 } @workspaces), 'all workspaces are of type CT_WORKSPACE'); +#ok((all { @{$_->{nodes}} == 0 } @workspaces), 'all workspaces are empty yet'); ok((none { defined($_->{window}) } @workspaces), 'no CT_OUTPUT contains a window'); # TODO: get the focused container @@ -76,9 +52,9 @@ $i3->command('open')->recv; # TODO: get the focused container, check if it changed. # TODO: get the old focused container, check if there is a new child -diag(Dumper(\@workspaces)); +#diag(Dumper(\@workspaces)); -diag(Dumper($tree)); +#diag(Dumper($tree)); diag( "Testing i3, Perl $], $^X" ); From ed4e8e9bff794befd10fa5228b9ef50c944ec043 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 9 Jun 2010 09:59:33 +0200 Subject: [PATCH 108/867] debian: update i3-wm.docs --- debian/i3-wm.docs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/debian/i3-wm.docs b/debian/i3-wm.docs index a8747817..f283829c 100644 --- a/debian/i3-wm.docs +++ b/debian/i3-wm.docs @@ -10,3 +10,5 @@ docs/modes.png docs/stacklimit.png docs/ipc.html docs/multi-monitor.html +docs/wsbar.html +docs/wsbar.png From 1bddd2a9cb5ba3bd46b1aa8c4f4502f3cddfd586 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 16 Jun 2010 19:15:14 +0200 Subject: [PATCH 109/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20crash=20when?= =?UTF-8?q?=20a=20dock=20client=20starts=20up=20with=20nonsense=20coordina?= =?UTF-8?q?tes=20(Thanks=20dothebart)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes ticket #229 --- src/manage.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/manage.c b/src/manage.c index 10cf74c7..06f4e664 100644 --- a/src/manage.c +++ b/src/manage.c @@ -273,6 +273,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) { DLOG("Window is a dock.\n"); Output *t_out = get_output_containing(x, y); + if (t_out == NULL) + t_out = c_ws->output; if (t_out != c_ws->output) { DLOG("Dock client requested to be on output %s by geometry (%d, %d)\n", t_out->name, x, y); From a2402a5eb1a372d7f93650f6823a28ffa59a632e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 16 Jun 2010 19:26:55 +0200 Subject: [PATCH 110/867] Bugfix: i3-wsbar: properly catch errors when writing to child process This comes at the expense of having Try::Tiny as additional dependency, but I think Try::Tiny is widely available. --- i3-wsbar | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/i3-wsbar b/i3-wsbar index 3c76864e..5536e92c 100755 --- a/i3-wsbar +++ b/i3-wsbar @@ -7,6 +7,7 @@ use warnings; use Getopt::Long; use Pod::Usage; use IPC::Run qw(start pump); +use Try::Tiny; use AnyEvent::I3; use AnyEvent; use v5.10; @@ -204,7 +205,12 @@ sub update_output { $out .= "\n"; $outputs->{$name}->{cmd_input} = $out; - pump $outputs->{$name}->{cmd} while length $outputs->{$name}->{cmd_input}; + try { + pump $outputs->{$name}->{cmd} while length $outputs->{$name}->{cmd_input}; + } catch { + warn "Could not write to dzen2"; + exit 1; + } } } From 76c4e95b608787cd95ac2b0ba9ab442110cbd7df Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Jun 2010 11:48:47 +0200 Subject: [PATCH 111/867] i3-wsbar: replace %w with the width of the output (Thanks dothebart) This fixes ticket #231 --- i3-wsbar | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/i3-wsbar b/i3-wsbar index 5536e92c..4148c95c 100755 --- a/i3-wsbar +++ b/i3-wsbar @@ -136,9 +136,12 @@ sub got_outputs { } my $x = $new{$name}->{rect}->{x}; + my $w = $new{$name}->{rect}->{width}; my $launch = $command; $launch =~ s/([^%])%x/$1$x/g; + $launch =~ s/([^%])%w/$1$w/g; $launch =~ s/%%x/%x/g; + $launch =~ s/%%w/%w/g; $new{$name}->{cmd_input} = ''; my @cmd = ('/bin/sh', '-c', $launch); @@ -245,10 +248,11 @@ i3-wsbar -c [options] =item B<--command> This command (at the moment only dzen2 is supported) will be started for each -output. C<%x> will be replaced with the X coordinate of the output. +output. C<%x> will be replaced with the X coordinate of the output, C<%w> will +be replaced with the width of the output. Example: - --command "dzen2 -dock -x %x" + --command "dzen2 -dock -x %x -w %w" =item B<--input-on> From f411f3f25503f6a43ddc1eb9a071e304b491d575 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 9 Jun 2010 09:53:36 +0200 Subject: [PATCH 112/867] debian: update changelog --- debian/changelog | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/debian/changelog b/debian/changelog index 6aa5a51f..f4eb86d8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +i3-wm (3.e-bf1-1) unstable; urgency=low + + * Bugfix: Correctly initialize workspaces if RandR is not available + * Bugfix: Correctly handle asprintf() return value + * Bugfix: Update _NET_WM_STATE when clients request changes via ClientMessage + * Bugfix: Don’t invert directions when resizing floating clients (top/left) + * Bugfix: Don’t leak file descriptors + + -- Michael Stapelberg Wed, 09 Jun 2010 09:51:10 +0200 + i3-wm (3.e-3) unstable; urgency=low * Bump debian policy version From bdfad42c80641d728d8aac268fe7e89c56e9a315 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 23 Jun 2010 18:03:43 +0200 Subject: [PATCH 113/867] debian: call dh_installwm to register as alternative for x-window-manager --- debian/rules | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/debian/rules b/debian/rules index 981da126..464722c0 100755 --- a/debian/rules +++ b/debian/rules @@ -59,24 +59,14 @@ binary-arch: build install dh_installchangelogs dh_installdocs dh_installexamples -# dh_install -# dh_installmenu dh_installdebconf -# dh_installlogrotate -# dh_installemacsen -# dh_installpam -# dh_installmime -# dh_python dh_installinit -# dh_installcron -# dh_installinfo dh_installman + dh_installwm dh_link dh_strip --dbg-package=i3-wm-dbg dh_compress dh_fixperms -# dh_perl -# dh_makeshlibs dh_installdeb dh_shlibdeps dh_gencontrol From d9cb045f89a7e2bf96713d3ac38e7312356c4ecb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 23 Jun 2010 18:19:23 +0200 Subject: [PATCH 114/867] debian: add watch file --- debian/watch | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 debian/watch diff --git a/debian/watch b/debian/watch new file mode 100644 index 00000000..b800f77f --- /dev/null +++ b/debian/watch @@ -0,0 +1,2 @@ +version=3 +http://i3.zekjur.net/downloads/ /downloads/i3-(.*)\.tar\.bz2 From 0209309bc2751249d0f2b0f7b9dfe73fd7408b62 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 23 Jun 2010 18:23:38 +0200 Subject: [PATCH 115/867] debian: update changelog --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index f4eb86d8..4eff0f88 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i3-wm (3.e-bf1-2) unstable; urgency=low + + * debian: call dh_installwm to register as alternative for x-window-manager + + -- Michael Stapelberg Wed, 23 Jun 2010 18:23:10 +0200 + i3-wm (3.e-bf1-1) unstable; urgency=low * Bugfix: Correctly initialize workspaces if RandR is not available From cc865f47902f3898dcb85d49e592a14ad2c4e356 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 23 Jun 2010 18:29:35 +0200 Subject: [PATCH 116/867] debian: bump compatibility to 6 --- debian/compat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/compat b/debian/compat index 7ed6ff82..1e8b3149 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -5 +6 From 0b111f7f1ab8a7ae12c79a206ae1e9daa5a286ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sun, 20 Jun 2010 19:20:04 -0300 Subject: [PATCH 117/867] Reset the color to the default color, don't harcode white. --- i3-wsbar | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3-wsbar b/i3-wsbar index 4148c95c..26d8caf7 100755 --- a/i3-wsbar +++ b/i3-wsbar @@ -202,7 +202,7 @@ sub update_output { $out .= qq|^p(2)^pa(;2)|; } - $out .= qq|^p(_LOCK_X)^fg($dzen_bg)^r(${width}x17)^p(_UNLOCK_X)^fg(white)|; + $out .= qq|^p(_LOCK_X)^fg($dzen_bg)^r(${width}x17)^p(_UNLOCK_X)^fg()|; $out .= qq|^p(+5)|; $out .= $last_line if (!@input_on or $name ~~ @input_on); $out .= "\n"; From 69e1975e29a13beffe3251ff9d03c372ee5a2c98 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 28 Jun 2010 21:40:17 +0200 Subject: [PATCH 118/867] split up toggle_floating_mode into floating_enable and floating_disable --- include/floating.h | 20 ++++++-- src/floating.c | 116 ++++++++++++++++++++++++--------------------- 2 files changed, 79 insertions(+), 57 deletions(-) diff --git a/include/floating.h b/include/floating.h index 4f7cf422..7d1feb2d 100644 --- a/include/floating.h +++ b/include/floating.h @@ -28,9 +28,23 @@ typedef enum { BORDER_LEFT = (1 << 0), BORDER_BOTTOM = (1 << 3)} border_t; /** - * Enters floating mode for the given client. Correctly takes care of the - * position/size (separately stored for tiling/floating mode) and - * repositions/resizes/redecorates the client. + * Enables floating mode for the given container by detaching it from its + * parent, creating a new container around it and storing this container in the + * floating_windows list of the workspace. + * + */ +void floating_enable(Con *con, bool automatic); + +/** + * Disables floating mode for the given container by re-attaching the container + * to its old parent. + * + */ +void floating_disable(Con *con, bool automatic); + +/** + * Calls floating_enable() for tiling containers and floating_disable() for + * floating containers. * * If the automatic flag is set to true, this was an automatic update by a * change of the window class from the application which can be overwritten by diff --git a/src/floating.c b/src/floating.c index b82d5f17..05fe1f41 100644 --- a/src/floating.c +++ b/src/floating.c @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * @@ -16,6 +16,65 @@ extern xcb_connection_t *conn; +void floating_enable(Con *con, bool automatic) { + /* 1: detach the container from its parent */ + /* TODO: refactor this with tree_close() */ + TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); + TAILQ_REMOVE(&(con->parent->focus_head), con, focused); + + Con *child; + int children = 0; + TAILQ_FOREACH(child, &(con->parent->nodes_head), nodes) + children++; + /* TODO: better document why this math works */ + double fix = 1.0 / (1.0 - (1.0 / (children+1))); + TAILQ_FOREACH(child, &(con->parent->nodes_head), nodes) { + if (child->percent <= 0.0) + continue; + child->percent *= fix; + } + + /* 2: create a new container to render the decoration on, add + * it as a floating window to the workspace */ + Con *nc = con_new(NULL); + nc->parent = con_get_workspace(con); + nc->rect = con->rect; + nc->orientation = NO_ORIENTATION; + nc->type = CT_FLOATING_CON; + TAILQ_INSERT_TAIL(&(nc->parent->floating_head), nc, floating_windows); + TAILQ_INSERT_TAIL(&(nc->parent->focus_head), nc, focused); + + /* 3: attach the child to the new parent container */ + con->old_parent = con->parent; + con->parent = nc; + con->floating = FLOATING_USER_ON; + nc->rect.x = 400; + nc->rect.y = 400; + TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes); + TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused); +} + +void floating_disable(Con *con, bool automatic) { + assert(con->old_parent != NULL); + + /* 1: detach from parent container */ + TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); + TAILQ_REMOVE(&(con->parent->focus_head), con, focused); + + /* 2: kill parent container */ + TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows); + TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused); + tree_close(con->parent, false); + + /* 3: re-attach to previous parent */ + con->parent = con->old_parent; + TAILQ_INSERT_TAIL(&(con->parent->nodes_head), con, nodes); + TAILQ_INSERT_TAIL(&(con->parent->focus_head), con, focused); + + con->floating = FLOATING_USER_OFF; +} + + /* * Toggles floating mode for the given container. * @@ -29,63 +88,12 @@ void toggle_floating_mode(Con *con, bool automatic) { /* see if the client is already floating */ if (con_is_floating(con)) { LOG("already floating, re-setting to tiling\n"); - assert(con->old_parent != NULL); - - /* 1: detach from parent container */ - TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); - TAILQ_REMOVE(&(con->parent->focus_head), con, focused); - - /* 2: kill parent container */ - TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows); - TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused); - tree_close(con->parent, false); - - /* 3: re-attach to previous parent */ - con->parent = con->old_parent; - TAILQ_INSERT_TAIL(&(con->parent->nodes_head), con, nodes); - TAILQ_INSERT_TAIL(&(con->parent->focus_head), con, focused); - - con->floating = FLOATING_USER_OFF; + floating_disable(con, automatic); return; } - /* 1: detach the container from its parent */ - /* TODO: refactor this with tree_close() */ - TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); - TAILQ_REMOVE(&(con->parent->focus_head), con, focused); - - Con *child; - int children = 0; - TAILQ_FOREACH(child, &(con->parent->nodes_head), nodes) - children++; - /* TODO: better document why this math works */ - double fix = 1.0 / (1.0 - (1.0 / (children+1))); - TAILQ_FOREACH(child, &(con->parent->nodes_head), nodes) { - if (child->percent <= 0.0) - continue; - child->percent *= fix; - } - - /* 2: create a new container to render the decoration on, add - * it as a floating window to the workspace */ - Con *nc = con_new(NULL); - nc->parent = con_get_workspace(con); - nc->rect = con->rect; - nc->orientation = NO_ORIENTATION; - nc->type = CT_FLOATING_CON; - TAILQ_INSERT_TAIL(&(nc->parent->floating_head), nc, floating_windows); - TAILQ_INSERT_TAIL(&(nc->parent->focus_head), nc, focused); - - /* 3: attach the child to the new parent container */ - con->old_parent = con->parent; - con->parent = nc; - con->floating = FLOATING_USER_ON; - nc->rect.x = 400; - nc->rect.y = 400; - TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes); - TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused); - + floating_enable(con, automatic); #if 0 From 84e78c6dba6e41e38eff86b4ff387384e29f2c6b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 28 Jun 2010 21:40:36 +0200 Subject: [PATCH 119/867] automatically set dialog windows to floating --- src/manage.c | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/manage.c b/src/manage.c index 5f5bb44c..754ebb0f 100644 --- a/src/manage.c +++ b/src/manage.c @@ -163,6 +163,24 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki nc->window = cwindow; x_reinit(nc); + xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, wm_type_cookie, NULL); + if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DOCK])) + LOG("this window is a dock\n"); + + /* set floating if necessary */ + if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DIALOG]) || + xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_UTILITY]) || + xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_TOOLBAR]) || + xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_SPLASH])) { + LOG("This window is a dialog window, setting floating\n"); + + /* We respect the geometry wishes of floating windows, as long as they + * are bigger than our minimal useful size (75x50). */ + nc->rect.width = max(geom->width, 75); + nc->rect.height = max(geom->height, 50); + floating_enable(nc, false); + } + /* to avoid getting an UnmapNotify event due to reparenting, we temporarily * declare no interest in any state change event of this window */ values[0] = XCB_NONE; @@ -178,21 +196,10 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki values[0] = CHILD_EVENT_MASK; xcb_change_window_attributes(conn, window, mask, values); - xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, state_cookie, NULL); + reply = xcb_get_property_reply(conn, state_cookie, NULL); if (xcb_reply_contains_atom(reply, atoms[_NET_WM_STATE_FULLSCREEN])) con_toggle_fullscreen(nc); - reply = xcb_get_property_reply(conn, wm_type_cookie, NULL); - if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DOCK])) - LOG("this window is a dock\n"); - - if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DIALOG]) || - xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_UTILITY]) || - xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_TOOLBAR]) || - xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_SPLASH])) - LOG("This window is a dialog window\n"); - - /* Put the client inside the save set. Upon termination (whether killed or * normal exit does not matter) of the window manager, these clients will * be correctly reparented to their most closest living ancestor (= From c33d352fd25d6b97bd7976588c29c3bf26b44b03 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 28 Jun 2010 22:23:32 +0200 Subject: [PATCH 120/867] floating: re-implement floating_modifier + left/right mouse button to drag/resize --- include/floating.h | 5 +-- src/click.c | 29 ++++++++---- src/floating.c | 107 +++++++++++++++++++++++---------------------- src/manage.c | 12 +++++ 4 files changed, 89 insertions(+), 64 deletions(-) diff --git a/include/floating.h b/include/floating.h index 7d1feb2d..a312daee 100644 --- a/include/floating.h +++ b/include/floating.h @@ -78,7 +78,6 @@ int floating_border_click(xcb_connection_t *conn, Client *client, * */ void floating_drag_window(Con *con, xcb_button_press_event_t *event); -#if 0 /** * Called when the user clicked on a floating window while holding the @@ -86,9 +85,9 @@ void floating_drag_window(Con *con, xcb_button_press_event_t *event); * Calls the drag_pointer function with the resize_window callback * */ -void floating_resize_window(xcb_connection_t *conn, Client *client, - bool proportional, xcb_button_press_event_t *event); +void floating_resize_window(Con *con, bool proportional, xcb_button_press_event_t *event); +#if 0 /** * Changes focus in the given direction for floating clients. * diff --git a/src/click.c b/src/click.c index 61328e06..bb4af66a 100644 --- a/src/click.c +++ b/src/click.c @@ -238,7 +238,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client, int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) { Con *con; - LOG("Button %d pressed\n", event->state); + LOG("Button %d pressed on window 0x%08x\n", event->state, event->event); con = con_by_window_id(event->event); bool border_click = false; @@ -246,50 +246,61 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ con = con_by_frame_id(event->event); border_click = true; } + LOG("border_click = %d\n", border_click); //if (con && con->type == CT_FLOATING_CON) //con = TAILQ_FIRST(&(con->nodes_head)); /* See if this was a click with the configured modifier. If so, we need * to move around the client if it was floating. if not, we just process * as usual. */ - //if (config.floating_modifier != 0 && - //(event->state & config.floating_modifier) == config.floating_modifier) { + LOG("state = %d, floating_modifier = %d\n", event->state, config.floating_modifier); + if (border_click || + (config.floating_modifier != 0 && + (event->state & config.floating_modifier) == config.floating_modifier)) { if (con == NULL) { LOG("Not handling, floating_modifier was pressed and no client found\n"); return 1; } + LOG("handling\n"); #if 0 if (con->fullscreen) { LOG("Not handling, client is in fullscreen mode\n"); return 1; } #endif - if (con->type == CT_FLOATING_CON) { + if ((border_click && con->type == CT_FLOATING_CON) || + (!border_click && con_is_floating(con))) { + /* floating operations are always on the container around + * the "payload container", so make sure we use the right one */ + Con *floatingcon = (border_click ? con : con->parent); LOG("button %d pressed\n", event->detail); if (event->detail == 1) { LOG("left mouse button, dragging\n"); - floating_drag_window(con, event); + floating_drag_window(floatingcon, event); } -#if 0 else if (event->detail == 3) { bool proportional = (event->state & BIND_SHIFT); DLOG("right mouse button\n"); - floating_resize_window(conn, client, proportional, event); + floating_resize_window(floatingcon, proportional, event); } -#endif return 1; } #if 0 if (!floating_mod_on_tiled_client(conn, client, event)) { +#endif xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); xcb_flush(conn); +#if 0 } #endif return 1; - //} + } + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + xcb_flush(conn); + return 0; #if 0 if (client == NULL) { /* The client was neither on a client’s titlebar nor on a client itself, maybe on a stack_window? */ diff --git a/src/floating.c b/src/floating.c index 05fe1f41..5c478027 100644 --- a/src/floating.c +++ b/src/floating.c @@ -340,7 +340,6 @@ void floating_drag_window(Con *con, xcb_button_press_event_t *event) { tree_render(); } -#if 0 /* * This is an ugly data structure which we need because there is no standard * way of having nested functions (only available as a gcc extension at the @@ -349,55 +348,60 @@ void floating_drag_window(Con *con, xcb_button_press_event_t *event) { * */ struct resize_window_callback_params { - border_t corner; - bool proportional; - xcb_button_press_event_t *event; + border_t corner; + bool proportional; + xcb_button_press_event_t *event; }; DRAGGING_CB(resize_window_callback) { - struct resize_window_callback_params *params = extra; - xcb_button_press_event_t *event = params->event; - border_t corner = params->corner; + struct resize_window_callback_params *params = extra; + xcb_button_press_event_t *event = params->event; + border_t corner = params->corner; - int32_t dest_x = client->rect.x; - int32_t dest_y = client->rect.y; - uint32_t dest_width; - uint32_t dest_height; + int32_t dest_x = con->rect.x; + int32_t dest_y = con->rect.y; + uint32_t dest_width; + uint32_t dest_height; - double ratio = (double) old_rect->width / old_rect->height; + double ratio = (double) old_rect->width / old_rect->height; - /* First guess: We resize by exactly the amount the mouse moved, - * taking into account in which corner the client was grabbed */ - if (corner & BORDER_LEFT) - dest_width = old_rect->width - (new_x - event->root_x); - else dest_width = old_rect->width + (new_x - event->root_x); + /* First guess: We resize by exactly the amount the mouse moved, + * taking into account in which corner the client was grabbed */ + if (corner & BORDER_LEFT) + dest_width = old_rect->width - (new_x - event->root_x); + else dest_width = old_rect->width + (new_x - event->root_x); - if (corner & BORDER_TOP) - dest_height = old_rect->height - (new_y - event->root_y); - else dest_height = old_rect->height + (new_y - event->root_y); + if (corner & BORDER_TOP) + dest_height = old_rect->height - (new_y - event->root_y); + else dest_height = old_rect->height + (new_y - event->root_y); - /* Obey minimum window size */ - dest_width = max(dest_width, client_min_width(client)); - dest_height = max(dest_height, client_min_height(client)); + /* TODO: minimum window size */ +#if 0 + /* Obey minimum window size */ + dest_width = max(dest_width, client_min_width(client)); + dest_height = max(dest_height, client_min_height(client)); +#endif - /* User wants to keep proportions, so we may have to adjust our values */ - if (params->proportional) { - dest_width = max(dest_width, (int) (dest_height * ratio)); - dest_height = max(dest_height, (int) (dest_width / ratio)); - } + /* User wants to keep proportions, so we may have to adjust our values */ + if (params->proportional) { + dest_width = max(dest_width, (int) (dest_height * ratio)); + dest_height = max(dest_height, (int) (dest_width / ratio)); + } - /* If not the lower right corner is grabbed, we must also reposition - * the client by exactly the amount we resized it */ - if (corner & BORDER_LEFT) - dest_x = old_rect->x + (old_rect->width - dest_width); + /* If not the lower right corner is grabbed, we must also reposition + * the client by exactly the amount we resized it */ + if (corner & BORDER_LEFT) + dest_x = old_rect->x + (old_rect->width - dest_width); - if (corner & BORDER_TOP) - dest_y = old_rect->y + (old_rect->height - dest_height); + if (corner & BORDER_TOP) + dest_y = old_rect->y + (old_rect->height - dest_height); - client->rect = (Rect) { dest_x, dest_y, dest_width, dest_height }; + con->rect = (Rect) { dest_x, dest_y, dest_width, dest_height }; - /* resize_client flushes */ - resize_client(conn, client); + /* TODO: don’t re-render the whole tree just because we change + * coordinates of a floating window */ + tree_render(); + x_push_changes(croot); } /* @@ -406,28 +410,27 @@ DRAGGING_CB(resize_window_callback) { * Calls the drag_pointer function with the resize_window callback * */ -void floating_resize_window(xcb_connection_t *conn, Client *client, - bool proportional, xcb_button_press_event_t *event) { - DLOG("floating_resize_window\n"); +void floating_resize_window(Con *con, bool proportional, + xcb_button_press_event_t *event) { + DLOG("floating_resize_window\n"); - /* corner saves the nearest corner to the original click. It contains - * a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, …) */ - border_t corner = 0; + /* corner saves the nearest corner to the original click. It contains + * a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, …) */ + border_t corner = 0; - if (event->event_x <= (client->rect.width / 2)) - corner |= BORDER_LEFT; - else corner |= BORDER_RIGHT; + if (event->event_x <= (con->rect.width / 2)) + corner |= BORDER_LEFT; + else corner |= BORDER_RIGHT; - if (event->event_y <= (client->rect.height / 2)) - corner |= BORDER_TOP; - else corner |= BORDER_RIGHT; + if (event->event_y <= (con->rect.height / 2)) + corner |= BORDER_TOP; + else corner |= BORDER_RIGHT; - struct resize_window_callback_params params = { corner, proportional, event }; + struct resize_window_callback_params params = { corner, proportional, event }; - drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, ¶ms); + drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, ¶ms); } -#endif /* * This function grabs your pointer and lets you drag stuff around (borders). * Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received diff --git a/src/manage.c b/src/manage.c index 754ebb0f..48262642 100644 --- a/src/manage.c +++ b/src/manage.c @@ -128,6 +128,18 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki i3Window *cwindow = scalloc(sizeof(i3Window)); cwindow->id = window; + /* We need to grab the mouse buttons for click to focus */ + xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS, + XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, + 1 /* left mouse button */, + XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); + + xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS, + XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, + 3 /* right mouse button */, + XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); + + /* update as much information as possible so far (some replies may be NULL) */ window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL)); window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL)); From 574e6b51d18cffb9bffcdd7dcde0e2df92560ae6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 28 Jun 2010 22:26:23 +0200 Subject: [PATCH 121/867] re-implement click to focus --- src/click.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/click.c b/src/click.c index bb4af66a..dc73cbdc 100644 --- a/src/click.c +++ b/src/click.c @@ -298,6 +298,10 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ return 1; } + /* click to focus */ + con_focus(con); + tree_render(); + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); xcb_flush(conn); return 0; From 2f4210d3cf0114ee370897336b054508313b9b6d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 28 Jun 2010 22:36:08 +0200 Subject: [PATCH 122/867] floating: use con_fix_percent --- src/floating.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/floating.c b/src/floating.c index 5c478027..d6009320 100644 --- a/src/floating.c +++ b/src/floating.c @@ -22,17 +22,7 @@ void floating_enable(Con *con, bool automatic) { TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); TAILQ_REMOVE(&(con->parent->focus_head), con, focused); - Con *child; - int children = 0; - TAILQ_FOREACH(child, &(con->parent->nodes_head), nodes) - children++; - /* TODO: better document why this math works */ - double fix = 1.0 / (1.0 - (1.0 / (children+1))); - TAILQ_FOREACH(child, &(con->parent->nodes_head), nodes) { - if (child->percent <= 0.0) - continue; - child->percent *= fix; - } + con_fix_percent(con->parent, WINDOW_REMOVE); /* 2: create a new container to render the decoration on, add * it as a floating window to the workspace */ @@ -72,6 +62,8 @@ void floating_disable(Con *con, bool automatic) { TAILQ_INSERT_TAIL(&(con->parent->focus_head), con, focused); con->floating = FLOATING_USER_OFF; + + con_fix_percent(con->parent, WINDOW_ADD); } From 948378fa554815c4b010e5e939334e4430806507 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 28 Jun 2010 22:37:35 +0200 Subject: [PATCH 123/867] floating: correctly kill floating containers when closing --- src/tree.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/tree.c b/src/tree.c index a6f3dc13..d3e83875 100644 --- a/src/tree.c +++ b/src/tree.c @@ -174,6 +174,14 @@ void tree_close(Con *con, bool kill_window) { con_detach(con); con_fix_percent(con->parent, WINDOW_REMOVE); + if (con_is_floating(con)) { + DLOG("Container was floating, killing floating container\n"); + + TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows); + TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused); + tree_close(con->parent, false); + } + free(con->name); TAILQ_REMOVE(&all_cons, con, all_cons); free(con); From 60e507ca6fc51499aeba24459f6e957b9e3f8313 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 29 Jun 2010 14:48:19 +0200 Subject: [PATCH 124/867] ipc: send workspace event in workspace_initialize (Thanks fernando) --- docs/ipc | 4 ++-- src/workspace.c | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/ipc b/docs/ipc index f65ae484..5fcaf62e 100644 --- a/docs/ipc +++ b/docs/ipc @@ -267,8 +267,8 @@ output:: === workspace event This event consists of a single serialized map containing a property -+change (string)+ which indicates the type of the change ("focus", "init", -"empty", "urgent"). ++change (string)+ which indicates the type of the change ("focus", "create", +"init", "empty", "urgent"). *Example:* --------------------- diff --git a/src/workspace.c b/src/workspace.c index c950df8f..2ae0a498 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -59,7 +59,7 @@ Workspace *workspace_get(int number) { TAILQ_INSERT_TAIL(workspaces, ws, workspaces); - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"create\"}"); } DLOG("done\n"); @@ -291,6 +291,8 @@ void workspace_initialize(Workspace *ws, Output *output, bool recheck) { return; workspace_assign_to(ws, ws->output, false); + + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); } /* From 4eace6f88653a8c6aed9f0e29f43f7ea876f41aa Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 29 Jun 2010 19:05:31 +0200 Subject: [PATCH 125/867] Go down the tree when moving windows, add testcase for moving --- src/tree.c | 19 ++++++- testcases/t/24-move.t | 113 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 testcases/t/24-move.t diff --git a/src/tree.c b/src/tree.c index d3e83875..2bcaeaae 100644 --- a/src/tree.c +++ b/src/tree.c @@ -350,6 +350,10 @@ void tree_move(char way, orientation_t orientation) { LOG("cannot move further to the right\n"); return; } + + /* if this is a split container, we need to go down */ + while (!TAILQ_EMPTY(&(next->focus_head))) + next = TAILQ_FIRST(&(next->focus_head)); } con_detach(focused); @@ -360,18 +364,31 @@ void tree_move(char way, orientation_t orientation) { /* TODO: don’t influence focus handling? */ } else { LOG("i would insert it before %p / %s\n", current, current->name); + bool gone_down = false; if (!level_changed) { next = TAILQ_PREV(next, nodes_head, nodes); if (next == TAILQ_END(&(next->parent->nodes_head))) { LOG("cannot move further\n"); return; } + + /* if this is a split container, we need to go down */ + while (!TAILQ_EMPTY(&(next->focus_head))) { + gone_down = true; + next = TAILQ_FIRST(&(next->focus_head)); + } } con_detach(focused); focused->parent = next->parent; - TAILQ_INSERT_BEFORE(next, focused, nodes); + /* After going down in the tree, we insert the container *after* + * the currently focused one even though the command used "before". + * This is to keep the user experience clear, since the before/after + * only signifies the direction of the movement on top-level */ + if (gone_down) + TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, focused, nodes); + else TAILQ_INSERT_BEFORE(next, focused, nodes); TAILQ_INSERT_HEAD(&(next->parent->focus_head), focused, focused); /* TODO: don’t influence focus handling? */ } diff --git a/testcases/t/24-move.t b/testcases/t/24-move.t new file mode 100644 index 00000000..c7ddad4b --- /dev/null +++ b/testcases/t/24-move.t @@ -0,0 +1,113 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Tests moving. Basically, there are four different code-paths: +# 1) move a container which cannot be moved (single container on a workspace) +# 2) move a container before another single container +# 3) move a container inside another container +# 4) move a container in a different direction so that we need to go up in tree +# +use i3test tests => 16; +use X11::XCB qw(:all); +use v5.10; + +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +###################################################################### +# 1) move a container which cannot be moved +###################################################################### + +$i3->command('open')->recv; + +my $old_content = get_ws_content($tmp); +is(@{$old_content}, 1, 'one container on this workspace'); + +my $first = $old_content->[0]->{id}; + +$i3->command('move before h')->recv; +$i3->command('move before v')->recv; +$i3->command('move after v')->recv; +$i3->command('move after h')->recv; + +my $content = get_ws_content($tmp); +is_deeply($old_content, $content, 'workspace unmodified after useless moves'); + +###################################################################### +# 2) move a container before another single container +###################################################################### + +$i3->command('open')->recv; +$content = get_ws_content($tmp); +is(@{$content}, 2, 'two containers on this workspace'); +my $second = $content->[1]->{id}; + +is($content->[0]->{id}, $first, 'first container unmodified'); + +# Move the second container before the first one (→ swap them) +$i3->command('move before h')->recv; +$content = get_ws_content($tmp); +is($content->[0]->{id}, $second, 'first container modified'); + +# We should not be able to move any further +$i3->command('move before h')->recv; +$content = get_ws_content($tmp); +is($content->[0]->{id}, $second, 'first container unmodified'); + +# Now move in the other direction +$i3->command('move after h')->recv; +$content = get_ws_content($tmp); +is($content->[0]->{id}, $first, 'first container modified'); + +# We should not be able to move any further +$i3->command('move after h')->recv; +$content = get_ws_content($tmp); +is($content->[0]->{id}, $first, 'first container unmodified'); + +###################################################################### +# 3) move a container inside another container +###################################################################### + +# Split the current (second) container and create a new container on workspace +# level. Our layout looks like this now: +# -------------------------- +# | | second | | +# | first | ------ | third | +# | | | | +# -------------------------- +$i3->command('split v')->recv; +$i3->command('level up')->recv; +$i3->command('open')->recv; + +$content = get_ws_content($tmp); +is(@{$content}, 3, 'three containers on this workspace'); +my $third = $content->[2]->{id}; + +$i3->command('move before h')->recv; +$content = get_ws_content($tmp); +is(@{$content}, 2, 'only two containers on this workspace'); +my $nodes = $content->[1]->{nodes}; +is($nodes->[0]->{id}, $second, 'second container on top'); +is($nodes->[1]->{id}, $third, 'third container on bottom'); + +###################################################################### +# move it inside the split container +###################################################################### + +$i3->command('move before v')->recv; +$nodes = get_ws_content($tmp)->[1]->{nodes}; +is($nodes->[0]->{id}, $third, 'third container on top'); +is($nodes->[1]->{id}, $second, 'second container on bottom'); + +# move it outside again +$i3->command('move before h')->recv; +$content = get_ws_content($tmp); +is(@{$content}, 3, 'three nodes on this workspace'); + +$i3->command('move after h')->recv; +$content = get_ws_content($tmp); +is(@{$content}, 2, 'two nodes on this workspace'); + +diag( "Testing i3, Perl $], $^X" ); From 317d2bbe2b0ef0c7fcdcf0936c7a25b87f8e5343 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 30 Jun 2010 00:25:11 +0200 Subject: [PATCH 126/867] update configfile for the new commands (this is not the final default config file) --- i3.config | 175 +++++++++++++++++++++++------------------------------- 1 file changed, 75 insertions(+), 100 deletions(-) diff --git a/i3.config b/i3.config index 56cb2704..8f936cd2 100644 --- a/i3.config +++ b/i3.config @@ -1,5 +1,5 @@ -# This configuration uses Mod1 and Mod3. Make sure they are mapped properly using xev(1) -# and xmodmap(1). Usually, Mod1 is Alt (Alt_L) and Mod3 is Windows (Super_L) +# This configuration file was written for the NEO layout. If you are using a +# different layout, you should change it. # ISO 10646 = Unicode font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 @@ -7,116 +7,91 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 # Use Mouse+Mod1 to drag floating windows to their wanted position floating_modifier Mod1 -# Fullscreen (Mod1+f) -bind Mod1+41 f +# temporary path during development +ipc-socket /tmp/nestedcons -# Stacking (Mod1+h) -bind Mod1+43 s +# Open empty container +bindsym Mod1+Shift+Return open + +# Start terminal (Mod1+Enter) +bindsym Mod1+Return exec /usr/bin/urxvt + +# Start dmenu (Mod1+p) +bindsym Mod1+p exec /usr/bin/dmenu_run + +bindsym Mod1+h split h +bindsym Mod1+v split v + +# Fullscreen (Mod1+f) +bindsym Mod1+f fullscreen + +# Stacking (Mod1+s) +bindsym Mod1+s layout stacking # Tabbed (Mod1+w) -bind Mod1+25 T +bindsym Mod1+w layout tabbed -# Default (Mod1+e) -bind Mod1+26 d +# Default (Mod1+l) +bindsym Mod1+l layout default -# Toggle tiling/floating of the current window (Mod1+Shift+Space) -bind Mod1+Shift+65 t +bindsym Mod1+u level up +#bindsym Mod1+d level down -# Go into the tiling layer / floating layer, depending on whether -# the current window is tiling / floating (Mod1+t) -bind Mod1+28 focus ft +# Kill current client (Mod1+c) +bindsym Mod1+c kill -# Focus (Mod1+j/k/l/;) -bind Mod1+44 h -bind Mod1+45 j -bind Mod1+46 k -bind Mod1+47 l -# (alternatively, you can use the cursor keys:) -bindsym Mod1+Left h -bindsym Mod1+Down j -bindsym Mod1+Up k -bindsym Mod1+Right l +# Restore saved JSON layout +bindsym Mod1+y restore /home/michael/i3/layout.json -# Focus Container (Mod3+j/k/l/;) -bind Mod3+44 wch -bind Mod3+45 wcj -bind Mod3+46 wck -bind Mod3+47 wcl -# (alternatively, you can use the cursor keys:) -bindsym Mod3+Left wch -bindsym Mod3+Down wcj -bindsym Mod3+Up wck -bindsym Mod3+Right wcl +# Restart i3 +bindsym Mod1+Shift+c restart +# Exit i3 +bindsym Mod1+Shift+l exit -# Snap (Mod1+Control+j/k/l/;) -bind Mod1+Control+44 sh -bind Mod1+Control+45 sj -bind Mod1+Control+46 sk -bind Mod1+Control+47 sl -# (alternatively, you can use the cursor keys:) -bindsym Mod1+Control+Left sh -bindsym Mod1+Control+Down sj -bindsym Mod1+Control+Up sk -bindsym Mod1+Control+Right sl +# Focus (Mod1+n/r/t/d) +bindsym Mod1+n prev h +bindsym Mod1+r next v +bindsym Mod1+t prev v +bindsym Mod1+d next h -# Move (Mod1+Shift+j/k/l/;) -bind Mod1+Shift+44 mh -bind Mod1+Shift+45 mj -bind Mod1+Shift+46 mk -bind Mod1+Shift+47 ml -# (alternatively, you can use the cursor keys:) -bindsym Mod1+Shift+Left mh -bindsym Mod1+Shift+Down mj -bindsym Mod1+Shift+Up mk -bindsym Mod1+Shift+Right ml +# alternatively, you can use the cursor keys: +bindsym Mod1+Left prev h +bindsym Mod1+Right next h +bindsym Mod1+Down next v +bindsym Mod1+Up prev v -# Move Container (Mod3+Shift+j/k/l/;) -bind Mod3+Shift+44 wcmh -bind Mod3+Shift+45 wcmj -bind Mod3+Shift+46 wcmk -bind Mod3+Shift+47 wcml +# Move +bindsym Mod1+Shift+n move before h +bindsym Mod1+Shift+r move before v +bindsym Mod1+Shift+t move after v +bindsym Mod1+Shift+d move after h + +# alternatively, you can use the cursor keys: +bindsym Mod1+Shift+Left move before h +bindsym Mod1+Shift+Right move after h +bindsym Mod1+Shift+Down move before v +bindsym Mod1+Shift+Up move after v # Workspaces (Mod1+1/2/…) -bind Mod1+10 1 -bind Mod1+11 2 -bind Mod1+12 3 -bind Mod1+13 4 -bind Mod1+14 5 -bind Mod1+15 6 -bind Mod1+16 7 -bind Mod1+17 8 -bind Mod1+18 9 -bind Mod1+19 10 +bindsym Mod1+1 workspace 1 +bindsym Mod1+2 workspace 2 +bindsym Mod1+3 workspace 3 +bindsym Mod1+4 workspace 4 +bindsym Mod1+5 workspace 5 +bindsym Mod1+6 workspace 6 +bindsym Mod1+7 workspace 7 +bindsym Mod1+8 workspace 8 +bindsym Mod1+9 workspace 9 +bindsym Mod1+0 workspace 10 # Move to Workspaces -bind Mod1+Shift+10 m1 -bind Mod1+Shift+11 m2 -bind Mod1+Shift+12 m3 -bind Mod1+Shift+13 m4 -bind Mod1+Shift+14 m5 -bind Mod1+Shift+15 m6 -bind Mod1+Shift+16 m7 -bind Mod1+Shift+17 m8 -bind Mod1+Shift+18 m9 -bind Mod1+Shift+19 m10 - -# Mod1+Enter starts a new terminal -bind Mod1+36 exec /usr/bin/urxvt - -# Mod1+Shift+q kills the current client -bind Mod1+Shift+24 kill - -# Mod1+v starts dmenu and launches the selected application -# for now, we don’t have a launcher of our own. -bind Mod1+55 exec /usr/bin/dmenu_run - -# Mod1+Shift+e exits i3 -bind Mod1+Shift+26 exit - -# Mod1+Shift+r restarts i3 inplace -bind Mod1+Shift+27 restart - -############################################################# -# DELETE THE FOLLOWING LINES TO DISABLE THE WELCOME MESSAGE # -############################################################# -exec xmessage -file /etc/i3/welcome +bindsym Mod1+Shift+1 move workspace 1 +bindsym Mod1+Shift+2 move workspace 2 +bindsym Mod1+Shift+3 move workspace 3 +bindsym Mod1+Shift+4 move workspace 4 +bindsym Mod1+Shift+5 move workspace 5 +bindsym Mod1+Shift+6 move workspace 6 +bindsym Mod1+Shift+7 move workspace 7 +bindsym Mod1+Shift+8 move workspace 8 +bindsym Mod1+Shift+9 move workspace 9 +bindsym Mod1+Shift+0 move workspace 10 From ea30fdc327add691d7172594642b76e336d9f8e3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 30 Jun 2010 15:27:18 +0200 Subject: [PATCH 127/867] parser: call tree_close_con() instead of tree_close() when run interactively --- src/cmdparse.y | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index f694e1ff..8190403f 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -348,7 +348,7 @@ kill: printf("killing!\n"); /* check if the match is empty, not if the result is empty */ if (match_is_empty(¤t_match)) - tree_close(focused, true); + tree_close_con(); else { TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); From 2da4173144b9ca1716c48b7f1ff1d9d231322964 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 30 Jun 2010 15:31:29 +0200 Subject: [PATCH 128/867] parser: implement "reload" --- src/cmdparse.y | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index 8190403f..aa440046 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -268,7 +268,8 @@ operation: exec | exit | restart - /*| reload + | reload + /* | mark | border */ | layout @@ -304,6 +305,16 @@ exit: } ; +reload: + TOK_RELOAD + { + printf("reloading\n"); + load_configuration(conn, NULL, true); + /* Send an IPC event just in case the ws names have changed */ + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}"); + } + ; + restart: TOK_RESTART { From bd9e5c0bc4257e1a48d3d9608cf46ba2c0e64888 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 30 Jun 2010 15:54:34 +0200 Subject: [PATCH 129/867] parser: implement explicit "mode floating"/"mode tiling" --- src/cmdparse.y | 6 +++++- src/floating.c | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index aa440046..49e0e4d7 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -448,7 +448,11 @@ mode: toggle_floating_mode(focused, false); } else { printf("should switch mode to %s\n", ($3 == TOK_FLOATING ? "floating" : "tiling")); - /* TODO: actually switch mode (not toggle) */ + if ($3 == TOK_FLOATING) { + floating_enable(focused, false); + } else { + floating_disable(focused, false); + } } } ; diff --git a/src/floating.c b/src/floating.c index d6009320..8f144113 100644 --- a/src/floating.c +++ b/src/floating.c @@ -17,6 +17,11 @@ extern xcb_connection_t *conn; void floating_enable(Con *con, bool automatic) { + if (con_is_floating(con)) { + LOG("Container is already in floating mode, not doing anything.\n"); + return; + } + /* 1: detach the container from its parent */ /* TODO: refactor this with tree_close() */ TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); @@ -45,6 +50,11 @@ void floating_enable(Con *con, bool automatic) { } void floating_disable(Con *con, bool automatic) { + if (!con_is_floating(con)) { + LOG("Container isn't floating, not doing anything.\n"); + return; + } + assert(con->old_parent != NULL); /* 1: detach from parent container */ From 565ef78b12069014c2e8b1e73c0763d2acbab5cd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 30 Jun 2010 19:47:23 +0200 Subject: [PATCH 130/867] parser: implement resize command --- src/cmdparse.l | 10 +++++ src/cmdparse.y | 105 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/src/cmdparse.l b/src/cmdparse.l index f085a2a4..3b3aefe2 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -109,8 +109,16 @@ vertical { return TOK_VERTICAL; } level { return TOK_LEVEL; } up { return TOK_UP; } down { return TOK_DOWN; } +left { return TOK_LEFT; } +right { return TOK_RIGHT; } before { return TOK_BEFORE; } after { return TOK_AFTER; } +resize { return TOK_RESIZE; } +shrink { return TOK_SHRINK; } +grow { return TOK_GROW; } +px { return TOK_PX; } +or { return TOK_OR; } +ppt { return TOK_PPT; } restore { BEGIN(WANT_WS_STRING); return TOK_RESTORE; } mark { BEGIN(WANT_WS_STRING); return TOK_MARK; } @@ -119,6 +127,8 @@ id { BEGIN(WANT_QSTRING); return TOK_ID; } con_id { BEGIN(WANT_QSTRING); return TOK_CON_ID; } con_mark { BEGIN(WANT_QSTRING); return TOK_MARK; } +[0-9]+ { cmdyylval.number = atoi(yytext); return NUMBER; } + . { return (int)yytext[0]; } <> { diff --git a/src/cmdparse.y b/src/cmdparse.y index 49e0e4d7..9925bd7c 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -121,10 +121,18 @@ void parse_cmd(const char *new) { %token TOK_LEVEL "level" %token TOK_UP "up" %token TOK_DOWN "down" +%token TOK_LEFT "left" +%token TOK_RIGHT "right" %token TOK_AFTER "after" %token TOK_BEFORE "before" %token TOK_RESTORE "restore" %token TOK_MARK "mark" +%token TOK_RESIZE "resize" +%token TOK_GROW "grow" +%token TOK_SHRINK "shrink" +%token TOK_PX "px" +%token TOK_OR "or" +%token TOK_PPT "ppt" %token TOK_CLASS "class" %token TOK_ID "id" @@ -132,6 +140,7 @@ void parse_cmd(const char *new) { %token WHITESPACE "" %token STR "" +%token NUMBER "" %% @@ -270,7 +279,6 @@ operation: | restart | reload /* - | mark | border */ | layout | restore @@ -287,6 +295,7 @@ operation: | mode | level | mark + | resize ; exec: @@ -545,3 +554,97 @@ mark: free($3); } ; + +resize: + TOK_RESIZE WHITESPACE resize_way WHITESPACE direction resize_px resize_tiling + { + /* resize [ px] [or ppt] */ + printf("resizing in way %d, direction %d, px %d or ppt %d\n", $3, $5, $6, $7); + int direction = $5; + int px = $6; + int ppt = $7; + if ($3 == TOK_SHRINK) { + px *= -1; + ppt *= -1; + } + + if (con_is_floating(focused)) { + printf("floating resize\n"); + if (direction == TOK_UP) { + focused->parent->rect.y -= px; + focused->parent->rect.height += px; + } else if (direction == TOK_DOWN) { + focused->rect.height += px; + } else if (direction == TOK_LEFT) { + focused->rect.x -= px; + focused->rect.width += px; + } else { + focused->rect.width += px; + } + } else { + LOG("tiling resize\n"); + /* get the default percentage */ + int children = 0; + Con *other; + TAILQ_FOREACH(other, &(focused->parent->nodes_head), nodes) + children++; + LOG("ins. %d children\n", children); + double percentage = 1.0 / children; + LOG("default percentage = %f\n", percentage); + + if (direction == TOK_UP || direction == TOK_LEFT) { + other = TAILQ_PREV(focused, nodes_head, nodes); + } else { + other = TAILQ_NEXT(focused, nodes); + } + if (other == TAILQ_END(workspaces)) { + LOG("No other container in this direction found, cannot resize.\n"); + return 0; + } + LOG("other->percent = %f\n", other->percent); + LOG("focused->percent before = %f\n", focused->percent); + if (focused->percent == 0.0) + focused->percent = percentage; + if (other->percent == 0.0) + other->percent = percentage; + focused->percent += ((double)ppt / 100.0); + other->percent -= ((double)ppt / 100.0); + LOG("focused->percent after = %f\n", focused->percent); + LOG("other->percent after = %f\n", other->percent); + } + } + ; + +resize_px: + /* empty */ + { + $$ = 10; + } + | WHITESPACE NUMBER WHITESPACE TOK_PX + { + $$ = $2; + } + ; + +resize_tiling: + /* empty */ + { + $$ = 10; + } + | WHITESPACE TOK_OR WHITESPACE NUMBER WHITESPACE TOK_PPT + { + $$ = $4; + } + ; + +resize_way: + TOK_GROW { $$ = TOK_GROW; } + | TOK_SHRINK { $$ = TOK_SHRINK; } + ; + +direction: + TOK_UP { $$ = TOK_UP; } + | TOK_DOWN { $$ = TOK_DOWN; } + | TOK_LEFT { $$ = TOK_LEFT; } + | TOK_RIGHT { $$ = TOK_RIGHT; } + ; From 6d152103f57e12d933ce1d1b9412db9ca544b4c1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 30 Jun 2010 22:23:32 +0200 Subject: [PATCH 131/867] parser: implement move --- include/con.h | 2 ++ src/cmdparse.y | 18 ++++++++++++++++++ src/con.c | 22 ++++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/include/con.h b/include/con.h index 076a5cf5..5a6f0c15 100644 --- a/include/con.h +++ b/include/con.h @@ -19,4 +19,6 @@ enum { WINDOW_ADD = 0, WINDOW_REMOVE = 1 }; void con_fix_percent(Con *con, int action); void con_toggle_fullscreen(Con *con); +void con_move_to_workspace(Con *con, Con *workspace); + #endif diff --git a/src/cmdparse.y b/src/cmdparse.y index 9925bd7c..f4ad6e07 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -495,6 +495,24 @@ move: * we should not need any of both */ tree_move(($3 == TOK_BEFORE ? 'p' : 'n'), ($5 == 'v' ? VERT : HORIZ)); } + | TOK_MOVE WHITESPACE TOK_WORKSPACE WHITESPACE STR + { + owindow *current; + + printf("should move window to workspace %s\n", $5); + /* get the workspace */ + Con *ws = workspace_get($5); + + /* check if the match is empty, not if the result is empty */ + if (match_is_empty(¤t_match)) + con_move_to_workspace(focused, ws); + else { + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + con_move_to_workspace(current->con, ws); + } + } + } ; before_after: diff --git a/src/con.c b/src/con.c index 8aa1608c..c7c1c903 100644 --- a/src/con.c +++ b/src/con.c @@ -315,3 +315,25 @@ void con_toggle_fullscreen(Con *con) { xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, atoms[_NET_WM_STATE], ATOM, 32, num, values); } + +/* + * TODO: is there a better place for this function? + * + */ +void con_move_to_workspace(Con *con, Con *workspace) { + /* 1: get the focused container of this workspace by going down as far as + * possible */ + Con *next = workspace; + + while (!TAILQ_EMPTY(&(next->focus_head))) + next = TAILQ_FIRST(&(next->focus_head)); + + /* 2: we go up one level, but only when next is a normal container */ + if (next->type != CT_WORKSPACE) + next = next->parent; + + DLOG("Re-attaching container to %p / %s\n", next, next->name); + /* 3: re-attach the con to the parent of this focused container */ + con_detach(con); + con_attach(con, next); +} From 5d0f17d53d12a445ba730a3972f1749f40656e73 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 30 Jun 2010 22:37:57 +0200 Subject: [PATCH 132/867] bugfix: correctly focus follow up window when closing floating windows --- src/tree.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index 2bcaeaae..ec8b0a83 100644 --- a/src/tree.c +++ b/src/tree.c @@ -140,7 +140,7 @@ void tree_close(Con *con, bool kill_window) { if (con->type == CT_FLOATING_CON) { next = TAILQ_NEXT(con, floating_windows); if (next == TAILQ_END(&(con->parent->floating_head))) - next = con->parent; + next = con_get_workspace(con); } else { next = TAILQ_NEXT(con, focused); if (next == TAILQ_END(&(con->parent->nodes_head))) @@ -180,12 +180,19 @@ void tree_close(Con *con, bool kill_window) { TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows); TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused); tree_close(con->parent, false); + next = NULL; } free(con->name); TAILQ_REMOVE(&all_cons, con, all_cons); free(con); + /* in the case of floating windows, we already focused another container + * when closing the parent, so we can exit now. */ + if (!next) + return; + + DLOG("focusing %p / %s\n", next, next->name); /* TODO: check if the container (or one of its children) was focused */ con_focus(next); } From 64306e813e690310ab0fc3e12fbc727f9853258e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 2 Jul 2010 20:33:26 +0200 Subject: [PATCH 133/867] Bugfix: Ignore sequences of mapping/unmapping windows to avoid getting enter_notifies --- src/x.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/x.c b/src/x.c index f0a34d3b..9df33e51 100644 --- a/src/x.c +++ b/src/x.c @@ -226,15 +226,23 @@ static void x_push_node(Con *con) { * container was empty before, but now got a child) */ if (state->mapped != con->mapped || (con->mapped && state->initial)) { if (!con->mapped) { - LOG("unmapping container\n"); - xcb_unmap_window(conn, con->frame); + xcb_void_cookie_t cookie; + cookie = xcb_unmap_window(conn, con->frame); + LOG("unmapping container (serial %d)\n", cookie.sequence); + /* Ignore enter_notifies which are generated when unmapping */ + add_ignore_event(cookie.sequence); } else { + xcb_void_cookie_t cookie; if (state->initial && con->window != NULL) { - LOG("mapping child window\n"); - xcb_map_window(conn, con->window->id); + cookie = xcb_map_window(conn, con->window->id); + LOG("mapping child window (serial %d)\n", cookie.sequence); + /* Ignore enter_notifies which are generated when mapping */ + add_ignore_event(cookie.sequence); } - LOG("mapping container\n"); - xcb_map_window(conn, con->frame); + cookie = xcb_map_window(conn, con->frame); + LOG("mapping container (serial %d)\n", cookie.sequence); + /* Ignore enter_notifies which are generated when mapping */ + add_ignore_event(cookie.sequence); } state->mapped = con->mapped; } From f7842e4c71d1fee3d3015ec2f49e4fc0a3161bb7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 3 Jul 2010 15:29:44 +0200 Subject: [PATCH 134/867] t/18-openkill.t: search not focused container instead of using the first one --- testcases/t/18-openkill.t | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/testcases/t/18-openkill.t b/testcases/t/18-openkill.t index 1d9d705d..9749c1da 100644 --- a/testcases/t/18-openkill.t +++ b/testcases/t/18-openkill.t @@ -3,6 +3,7 @@ # # Tests whether opening an empty container and killing it again works # +use List::Util qw(first); use i3test tests => 6; use v5.10; @@ -31,10 +32,8 @@ $i3->command('open')->recv; ok(@{get_ws_content($tmp)} == 2, 'two containers opened'); my $content = get_ws_content($tmp); -# TODO: get the focused window, don’t assume that it is -# the latest one -my $id = $content->[0]->{id}; -diag('id of not focused = ' . $id); +my $not_focused = first { !$_->{focused} } @{$content}; +my $id = $not_focused->{id}; $i3->command("[con_id=\"$id\"] kill")->recv; From 66fc7953796f7f7cabf84653267b6e8e6fddfa9c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 3 Jul 2010 16:28:58 +0200 Subject: [PATCH 135/867] lib/i3test: use custom import() instead of Test::Kit to also import v5.10 --- testcases/t/lib/i3test.pm | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index f3a9a2e5..cf4baccc 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -1,25 +1,15 @@ package i3test; # vim:ts=4:sw=4:expandtab -use Test::Kit qw( - Test::Exception - Data::Dumper - AnyEvent::I3 -), - 'Test::Deep' => { - exclude => [ qw(all) ], - }; - use File::Temp qw(tmpnam); use X11::XCB::Rect; use X11::XCB::Window; use X11::XCB qw(:all); use AnyEvent::I3; use List::Util qw(first); -# Test::Kit already uses Exporter -#use Exporter qw(import); -use base 'Exporter'; +use v5.10; +use Exporter (); our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content get_ws get_focused); BEGIN { @@ -29,6 +19,23 @@ BEGIN { } } +sub import { + my $class = shift; + my $pkg = caller; + eval "package $pkg; +use Test::More qw(@_); +use Test::Exception; +use Data::Dumper; +use AnyEvent::I3; +use Test::Deep qw(eq_deeply cmp_deeply cmp_set cmp_bag cmp_methods useclass noclass set bag subbagof superbagof subsetof supersetof superhashof subhashof bool str arraylength Isa ignore methods regexprefonly regexpmatches num regexponly scalref reftype hashkeysonly blessed array re hash regexpref hash_each shallow array_each code arrayelementsonly arraylengthonly scalarrefonly listmethods any hashkeys isa); +use v5.10; +use strict; +use warnings; +"; + @_ = ($class); + goto \&Exporter::import; +} + sub open_standard_window { my ($x) = @_; From 49ed703299cf7b83fd7803b8becce8ede0962601 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 3 Jul 2010 17:42:36 +0200 Subject: [PATCH 136/867] Bugfix: Insert new containers at the right position (and add testcase) --- src/con.c | 9 +++++++- testcases/t/28-open-order.t | 46 +++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 testcases/t/28-open-order.t diff --git a/src/con.c b/src/con.c index c7c1c903..1d8ca82e 100644 --- a/src/con.c +++ b/src/con.c @@ -58,7 +58,14 @@ Con *con_new(Con *parent) { void con_attach(Con *con, Con *parent) { con->parent = parent; - TAILQ_INSERT_TAIL(&(parent->nodes_head), con, nodes); + Con *current = TAILQ_FIRST(&(parent->focus_head)); + + if (current == TAILQ_END(&(parent->focus_head))) + TAILQ_INSERT_TAIL(&(parent->nodes_head), con, nodes); + else { + DLOG("inserting after\n"); + TAILQ_INSERT_AFTER(&(parent->nodes_head), current, con, nodes); + } /* We insert to the TAIL because con_focus() will correct this. * This way, we have the option to insert Cons without having * to focus them. */ diff --git a/testcases/t/28-open-order.t b/testcases/t/28-open-order.t new file mode 100644 index 00000000..b48e5ee5 --- /dev/null +++ b/testcases/t/28-open-order.t @@ -0,0 +1,46 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Check if new containers are opened after the currently focused one instead +# of always at the end +use List::Util qw(first); +use i3test tests => 7; + +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + +# Open two new container +$i3->command("open")->recv; + +ok(@{get_ws_content($tmp)} == 1, 'containers opened'); + +my ($nodes, $focus) = get_ws_content($tmp); +my $first = $focus->[0]; + +$i3->command("open")->recv; + +($nodes, $focus) = get_ws_content($tmp); +my $second = $focus->[0]; + +isnt($first, $second, 'different container focused'); + +############################################################## +# see if new containers open after the currently focused +############################################################## + +$i3->command(qq|[con_id="$first"] focus|)->recv; +$i3->command('open')->recv; +$content = get_ws_content($tmp); +ok(@{$content} == 3, 'three containers opened'); + +is($content->[0]->{id}, $first, 'first container unmodified'); +isnt($content->[1]->{id}, $second, 'second container replaced'); +is($content->[2]->{id}, $second, 'third container unmodified'); + +diag( "Testing i3, Perl $], $^X" ); +# + From 5adcea6b3cff8c3cbc88522d799c340185e46318 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 3 Jul 2010 19:27:25 +0200 Subject: [PATCH 137/867] config: add reload keybinding --- i3.config | 2 ++ 1 file changed, 2 insertions(+) diff --git a/i3.config b/i3.config index 8f936cd2..132918f2 100644 --- a/i3.config +++ b/i3.config @@ -45,6 +45,8 @@ bindsym Mod1+y restore /home/michael/i3/layout.json # Restart i3 bindsym Mod1+Shift+c restart +# Reload i3 +bindsym Mod1+Shift+j reload # Exit i3 bindsym Mod1+Shift+l exit From b186446fb76425dc88a8dc7f94ff477eab5f5829 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 4 Jul 2010 19:50:44 +0200 Subject: [PATCH 138/867] Bugfix: Correctly restore focus after close (and add testcase) --- src/tree.c | 5 ++- testcases/t/29-focus-after-close.t | 49 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 testcases/t/29-focus-after-close.t diff --git a/src/tree.c b/src/tree.c index ec8b0a83..5b4e883b 100644 --- a/src/tree.c +++ b/src/tree.c @@ -143,8 +143,11 @@ void tree_close(Con *con, bool kill_window) { next = con_get_workspace(con); } else { next = TAILQ_NEXT(con, focused); - if (next == TAILQ_END(&(con->parent->nodes_head))) + if (next == TAILQ_END(&(con->parent->nodes_head))) { next = con->parent; + while (!TAILQ_EMPTY(&(next->focus_head))) + next = TAILQ_FIRST(&(next->focus_head)); + } } DLOG("closing %p, kill_window = %d\n", con, kill_window); diff --git a/testcases/t/29-focus-after-close.t b/testcases/t/29-focus-after-close.t new file mode 100644 index 00000000..b28ecf2a --- /dev/null +++ b/testcases/t/29-focus-after-close.t @@ -0,0 +1,49 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Check if the focus is correctly restored after closing windows. +# +use i3test tests => 6; +use Time::HiRes qw(sleep); + +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + +$i3->command('open')->recv; +my ($nodes, $focus) = get_ws_content($tmp); +my $first = $focus->[0]; + +$i3->command('split v')->recv; + +($nodes, $focus) = get_ws_content($tmp); + +is($nodes->[0]->{focused}, 0, 'split container not focused'); +$i3->command('level up')->recv; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[0]->{focused}, 1, 'split container focused after level up'); + +$i3->command('open')->recv; + +($nodes, $focus) = get_ws_content($tmp); +my $second = $focus->[0]; + +isnt($first, $second, 'different container focused'); + +############################################################## +# see if the focus goes down to $first (not to its split parent) +# when closing $second +############################################################## + +$i3->command('kill')->recv; +# TODO: this testcase sometimes has different outcomes when the +# sleep is missing. why? +sleep 0.25; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[0]->{nodes}->[0]->{id}, $first, 'first container found'); +is($nodes->[0]->{nodes}->[0]->{focused}, 1, 'first container focused'); + +diag( "Testing i3, Perl $], $^X" ); From 16f5c879f6ba8b81b858d475d20b9e348a145104 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 4 Jul 2010 19:53:05 +0200 Subject: [PATCH 139/867] testcases: remove 'use v5.10' as this is automatically done in lib/i3test --- testcases/t/02-fullscreen.t | 1 - testcases/t/17-workspace.t | 1 - testcases/t/18-openkill.t | 1 - testcases/t/19-match.t | 1 - testcases/t/20-multiple-cmds.t | 1 - testcases/t/21-next-prev.t | 1 - testcases/t/22-split.t | 1 - testcases/t/24-move.t | 1 - testcases/t/26-regress-close.t | 1 - testcases/t/27-regress-floating-parent.t | 1 - 10 files changed, 10 deletions(-) diff --git a/testcases/t/02-fullscreen.t b/testcases/t/02-fullscreen.t index 98194ba2..aa6861fb 100644 --- a/testcases/t/02-fullscreen.t +++ b/testcases/t/02-fullscreen.t @@ -5,7 +5,6 @@ use i3test tests => 24; use X11::XCB qw(:all); use List::Util qw(first); use Time::HiRes qw(sleep); -use v5.10; my $i3 = i3("/tmp/nestedcons"); diff --git a/testcases/t/17-workspace.t b/testcases/t/17-workspace.t index bb30bd02..33046bec 100644 --- a/testcases/t/17-workspace.t +++ b/testcases/t/17-workspace.t @@ -5,7 +5,6 @@ # (necessary for further tests) # use i3test tests => 3; -use v5.10; my $i3 = i3("/tmp/nestedcons"); diff --git a/testcases/t/18-openkill.t b/testcases/t/18-openkill.t index 9749c1da..b78877f7 100644 --- a/testcases/t/18-openkill.t +++ b/testcases/t/18-openkill.t @@ -5,7 +5,6 @@ # use List::Util qw(first); use i3test tests => 6; -use v5.10; my $i3 = i3("/tmp/nestedcons"); diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t index b5c01977..727109a0 100644 --- a/testcases/t/19-match.t +++ b/testcases/t/19-match.t @@ -5,7 +5,6 @@ # use i3test tests => 4; use X11::XCB qw(:all); -use v5.10; my $i3 = i3("/tmp/nestedcons"); diff --git a/testcases/t/20-multiple-cmds.t b/testcases/t/20-multiple-cmds.t index 9ee5b41c..f1714d80 100644 --- a/testcases/t/20-multiple-cmds.t +++ b/testcases/t/20-multiple-cmds.t @@ -5,7 +5,6 @@ # use i3test tests => 24; use X11::XCB qw(:all); -use v5.10; my $i3 = i3("/tmp/nestedcons"); diff --git a/testcases/t/21-next-prev.t b/testcases/t/21-next-prev.t index 8e8d44ee..688a4984 100644 --- a/testcases/t/21-next-prev.t +++ b/testcases/t/21-next-prev.t @@ -5,7 +5,6 @@ # use i3test tests => 14; use X11::XCB qw(:all); -use v5.10; my $i3 = i3("/tmp/nestedcons"); diff --git a/testcases/t/22-split.t b/testcases/t/22-split.t index fdfe0abc..eca8a4a5 100644 --- a/testcases/t/22-split.t +++ b/testcases/t/22-split.t @@ -5,7 +5,6 @@ # use i3test tests => 14; use X11::XCB qw(:all); -use v5.10; my $i3 = i3("/tmp/nestedcons"); diff --git a/testcases/t/24-move.t b/testcases/t/24-move.t index c7ddad4b..a98622f2 100644 --- a/testcases/t/24-move.t +++ b/testcases/t/24-move.t @@ -9,7 +9,6 @@ # use i3test tests => 16; use X11::XCB qw(:all); -use v5.10; my $i3 = i3("/tmp/nestedcons"); diff --git a/testcases/t/26-regress-close.t b/testcases/t/26-regress-close.t index 9df3aafe..95bac458 100644 --- a/testcases/t/26-regress-close.t +++ b/testcases/t/26-regress-close.t @@ -6,7 +6,6 @@ # use i3test tests => 1; use X11::XCB qw(:all); -use v5.10; my $i3 = i3("/tmp/nestedcons"); diff --git a/testcases/t/27-regress-floating-parent.t b/testcases/t/27-regress-floating-parent.t index 827f9207..c09858f6 100644 --- a/testcases/t/27-regress-floating-parent.t +++ b/testcases/t/27-regress-floating-parent.t @@ -5,7 +5,6 @@ # use i3test tests => 3; use X11::XCB qw(:all); -use v5.10; my $i3 = i3("/tmp/nestedcons"); From 66480d372596a489629de6aac19d704f70dae321 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 4 Jul 2010 22:16:54 +0200 Subject: [PATCH 140/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20try=20to=20fo?= =?UTF-8?q?cus=20the=20container=20itself=20when=20closing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tree.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index 5b4e883b..29b85730 100644 --- a/src/tree.c +++ b/src/tree.c @@ -145,7 +145,8 @@ void tree_close(Con *con, bool kill_window) { next = TAILQ_NEXT(con, focused); if (next == TAILQ_END(&(con->parent->nodes_head))) { next = con->parent; - while (!TAILQ_EMPTY(&(next->focus_head))) + while (!TAILQ_EMPTY(&(next->focus_head)) && + TAILQ_FIRST(&(next->focus_head)) != con) next = TAILQ_FIRST(&(next->focus_head)); } } From 1a0fcea48eb7b9fd3fe72443ab6301dddee0aeee Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 4 Jul 2010 22:17:18 +0200 Subject: [PATCH 141/867] ipc: add member 'focused' to every container --- src/ipc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ipc.c b/src/ipc.c index bde24852..7c0a0f37 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -140,6 +140,9 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("urgent"); y(integer, con->urgent); + ystr("focused"); + y(integer, (con == focused)); + ystr("layout"); y(integer, con->layout); From 61f9a793476c426da908ab3a216902caa6575df8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Jul 2010 22:01:25 +0200 Subject: [PATCH 142/867] use decimal coordinates in debug message --- src/handlers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index 990f7dd2..500bdb7f 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -157,7 +157,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", event->event, event->mode, event->detail, event->sequence); - DLOG("coordinates %x, %x\n", event->event_x, event->event_y); + DLOG("coordinates %d, %d\n", event->event_x, event->event_y); if (event->mode != XCB_NOTIFY_MODE_NORMAL) { DLOG("This was not a normal notify, ignoring\n"); return 1; From a79d33fc7fd41ab6e9b853f5356eeec64aa66ef5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Jul 2010 22:12:25 +0200 Subject: [PATCH 143/867] Remove some dead code (to be re-implemented), rename nc.c to main.c --- Makefile | 2 +- include/all.h | 1 - include/client.h | 121 ---- include/commands.h | 24 - include/container.h | 26 - include/layout.h | 94 --- include/resize.h | 36 -- src/client.c | 328 ----------- src/commands.c | 1301 ------------------------------------------ src/container.c | 43 -- src/layout.c | 779 ------------------------- src/{nc.c => main.c} | 0 src/mainx.c | 600 ------------------- src/resize.c | 323 ----------- 14 files changed, 1 insertion(+), 3677 deletions(-) delete mode 100644 include/client.h delete mode 100644 include/commands.h delete mode 100644 include/container.h delete mode 100644 include/layout.h delete mode 100644 include/resize.h delete mode 100644 src/client.c delete mode 100644 src/commands.c delete mode 100644 src/container.c delete mode 100644 src/layout.c rename src/{nc.c => main.c} (100%) delete mode 100644 src/mainx.c delete mode 100644 src/resize.c diff --git a/Makefile b/Makefile index 0b56d69b..8aaf20cf 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c -FILES:=src/ipc.c src/nc.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c +FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) diff --git a/include/all.h b/include/all.h index acf5e337..2890b9c6 100644 --- a/include/all.h +++ b/include/all.h @@ -30,7 +30,6 @@ #include #include "util.h" -#include "commands.h" #include "ipc.h" #include "tree.h" #include "log.h" diff --git a/include/client.h b/include/client.h deleted file mode 100644 index 627cab84..00000000 --- a/include/client.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - * vim:ts=8:expandtab - * - * i3 - an improved dynamic tiling window manager - * - * © 2009 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - */ -#include - -#include "data.h" - -#ifndef _CLIENT_H -#define _CLIENT_H - -/** - * Removes the given client from the container, either because it will be - * inserted into another one or because it was unmapped - * - */ -void client_remove_from_container(xcb_connection_t *conn, Client *client, - Container *container, - bool remove_from_focusstack); - -/** - * Warps the pointer into the given client (in the middle of it, to be - * specific), therefore selecting it - * - */ -void client_warp_pointer_into(xcb_connection_t *conn, Client *client); - -/** - * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window - * - */ -void client_kill(xcb_connection_t *conn, Client *window); - -/** - * Checks if the given window class and title match the given client Window - * title is passed as "normal" string and as UCS-2 converted string for - * matching _NET_WM_NAME capable clients as well as those using legacy hints. - * - */ -bool client_matches_class_name(Client *client, char *to_class, char *to_title, - char *to_title_ucs, int to_title_ucs_len); - -/** - * Sets the position of the given client in the X stack to the highest (tiling - * layer is always on the same position, so this doesn’t matter) below the - * first floating client, so that floating windows are always on top. - * - */ -void client_set_below_floating(xcb_connection_t *conn, Client *client); - -/** - * Returns true if the client is floating. Makes the code more beatiful, as - * floating is not simply a boolean, but also saves whether the user selected - * the current state or whether it was automatically set. - * - */ -bool client_is_floating(Client *client); - -/** - * Change the border type for the given client to normal (n), 1px border (p) or - * completely borderless (b). - * - */ -void client_change_border(xcb_connection_t *conn, Client *client, char border_type); - -/** - * Change the border type for the given client to normal (n), 1px border (p) or - * completely borderless (b) without actually re-rendering the layout. Useful - * for calling it when initializing a new client. - * - */ -bool client_init_border(xcb_connection_t *conn, Client *client, char border_type); - -/** - * Unmap the client, correctly setting any state which is needed. - * - */ -void client_unmap(xcb_connection_t *conn, Client *client); - -/** - * Map the client, correctly restoring any state needed. - * - */ -void client_map(xcb_connection_t *conn, Client *client); - -/** - * Set the given mark for this client. Used for jumping to the client - * afterwards (like m and ' in vim). - * - */ -void client_mark(xcb_connection_t *conn, Client *client, const char *mark); - -/** - * Returns the minimum height of a specific window. The height is calculated - * by using 2 pixels (for the client window itself), possibly padding this to - * comply with the client’s base_height and then adding the decoration height. - * - */ -uint32_t client_min_height(Client *client); - -/** - * See client_min_height. - * - */ -uint32_t client_min_width(Client *client); - -/** - * Pretty-prints the client’s information into the logfile. - * - */ -#define CLIENT_LOG(client) do { \ - DLOG("Window: frame 0x%08x, child 0x%08x\n", client->frame, client->child); \ - } while (0) - -#endif diff --git a/include/commands.h b/include/commands.h deleted file mode 100644 index c6b45d1f..00000000 --- a/include/commands.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * vim:ts=8:expandtab - * - * i3 - an improved dynamic tiling window manager - * - * (c) 2009 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - */ -#ifndef _COMMANDS_H -#define _COMMANDS_H - -#include - -#if 0 -bool focus_window_in_container(xcb_connection_t *conn, Container *container, - direction_t direction); -#endif - -/** Parses a command, see file CMDMODE for more information */ -void parse_command(const char *command); - -#endif diff --git a/include/container.h b/include/container.h deleted file mode 100644 index 78938508..00000000 --- a/include/container.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * vim:ts=8:expandtab - * - * i3 - an improved dynamic tiling window manager - * - * © 2009 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - */ -#include "data.h" - -#ifndef _CONTAINER_H -#define _CONTAINER_H - -/** - * Returns the mode of the given container (or MODE_DEFAULT if a NULL pointer - * was passed in order to save a few explicit checks in other places). If - * for_frame was set to true, the special case of having exactly one client - * in a container is handled so that MODE_DEFAULT is returned. For some parts - * of the rendering, this is interesting, other parts need the real mode. - * - */ -int container_mode(Container *con, bool for_frame); - -#endif diff --git a/include/layout.h b/include/layout.h deleted file mode 100644 index 1cbb7837..00000000 --- a/include/layout.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * vim:ts=8:expandtab - * - * i3 - an improved dynamic tiling window manager - * - * (c) 2009 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - */ -#include - -#ifndef _LAYOUT_H -#define _LAYOUT_H - -/** - * Gets the unoccupied space (= space which is available for windows which - * were resized by the user) This is necessary to render both, customly - * resized windows and never touched windows correctly, meaning that the - * aspect ratio will be maintained when opening new windows. - * - */ -int get_unoccupied_x(Workspace *workspace); - -/** See get_unoccupied_x */ -int get_unoccupied_y(Workspace *workspace); - -/** - * (Re-)draws window decorations for a given Client onto the given - * drawable/graphic context. When in stacking mode, the window decorations - * are drawn onto an own window. - * - */ -void decorate_window(xcb_connection_t *conn, Client *client, - xcb_drawable_t drawable, xcb_gcontext_t gc, - int offset_x, int offset_y); - -/** - * Redecorates the given client correctly by checking if it’s in a stacking - * container and re-rendering the stack window or just calling decorate_window - * if it’s not in a stacking container. - * - */ -void redecorate_window(xcb_connection_t *conn, Client *client); - -/** - * Pushes the client’s x and y coordinates to X11 - * - */ -void reposition_client(xcb_connection_t *conn, Client *client); - -/** - * Pushes the client’s width/height to X11 and resizes the child window. This - * function also updates the client’s position, so if you work on tiling clients - * only, you can use this function instead of separate calls to reposition_client - * and resize_client to reduce flickering. - * - */ -void resize_client(xcb_connection_t *conn, Client *client); - -/** - * Renders the given container. Is called by render_layout() or individually - * (for example when focus changes in a stacking container) - * - */ -void render_container(xcb_connection_t *conn, Container *container); - -/** - * Modifies the event mask of all clients on the given workspace to either - * ignore or to handle enter notifies. It is handy to ignore notifies because - * they will be sent when a window is mapped under the cursor, thus when the - * user didn’t enter the window actively at all. - * - */ -void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, - bool ignore_enter_notify); - -/** - * Renders the given workspace on the given screen - * - */ -void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws); - -/** - * Renders the whole layout, that is: Go through each screen, each workspace, - * each container and render each client. This also renders the bars. - * - * If you don’t need to render *everything*, you should call render_container - * on the container you want to refresh. - * - */ -void render_layout(xcb_connection_t *conn); - -#endif diff --git a/include/resize.h b/include/resize.h deleted file mode 100644 index c187e837..00000000 --- a/include/resize.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * vim:ts=8:expandtab - * - * i3 - an improved dynamic tiling window manager - * - * (c) 2009 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - */ - -#ifndef _RESIZE_H -#define _RESIZE_H - -#include - -typedef enum { O_HORIZONTAL, O_VERTICAL } resize_orientation_t; - -/** - * Renders the resize window between the first/second container and resizes - * the table column/row. - * - */ -int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, - int second, resize_orientation_t orientation, - xcb_button_press_event_t *event); -/** - * Resizes a column/row by the given amount of pixels. Called by - * resize_graphical_handler (the user clicked) or parse_resize_command (the - * user issued the command) - * - */ -void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int second, - resize_orientation_t orientation, int pixels); - -#endif diff --git a/src/client.c b/src/client.c deleted file mode 100644 index 43fcd7b4..00000000 --- a/src/client.c +++ /dev/null @@ -1,328 +0,0 @@ -/* - * vim:ts=8:expandtab - * - * i3 - an improved dynamic tiling window manager - * - * © 2009-2010 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - * client.c: holds all client-specific functions - * - */ -#include -#include -#include -#include - -#include -#include - -#include "data.h" -#include "i3.h" -#include "xcb.h" -#include "util.h" -#include "queue.h" -#include "layout.h" -#include "client.h" -#include "table.h" -#include "workspace.h" -#include "config.h" -#include "log.h" - -/* - * Removes the given client from the container, either because it will be inserted into another - * one or because it was unmapped - * - */ -void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container, bool remove_from_focusstack) { - CIRCLEQ_REMOVE(&(container->clients), client, clients); - - if (remove_from_focusstack) - SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients); - - /* If the container will be empty now and is in stacking mode, we need to - unmap the stack_win */ - if (CIRCLEQ_EMPTY(&(container->clients)) && - (container->mode == MODE_STACK || - container->mode == MODE_TABBED)) { - DLOG("Unmapping stack window\n"); - struct Stack_Window *stack_win = &(container->stack_win); - stack_win->rect.height = 0; - xcb_unmap_window(conn, stack_win->window); - xcb_flush(conn); - } -} - -/* - * Warps the pointer into the given client (in the middle of it, to be specific), therefore - * selecting it - * - */ -void client_warp_pointer_into(xcb_connection_t *conn, Client *client) { - int mid_x = client->rect.width / 2, - mid_y = client->rect.height / 2; - xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y); -} - -/* - * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW) - * - */ -static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) { - xcb_get_property_cookie_t cookie; - xcb_get_wm_protocols_reply_t protocols; - bool result = false; - - cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]); - if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1) - return false; - - /* Check if the client’s protocols have the requested atom set */ - for (uint32_t i = 0; i < protocols.atoms_len; i++) - if (protocols.atoms[i] == atom) - result = true; - - xcb_get_wm_protocols_reply_wipe(&protocols); - - return result; -} - -/* - * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window - * - */ -void client_kill(xcb_connection_t *conn, Client *window) { - /* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */ - if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) { - LOG("Killing window the hard way\n"); - xcb_kill_client(conn, window->child); - return; - } - - xcb_client_message_event_t ev; - - memset(&ev, 0, sizeof(xcb_client_message_event_t)); - - ev.response_type = XCB_CLIENT_MESSAGE; - ev.window = window->child; - ev.type = atoms[WM_PROTOCOLS]; - ev.format = 32; - ev.data.data32[0] = atoms[WM_DELETE_WINDOW]; - ev.data.data32[1] = XCB_CURRENT_TIME; - - LOG("Sending WM_DELETE to the client\n"); - xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev); - xcb_flush(conn); -} - -/* - * Checks if the given window class and title match the given client - * Window title is passed as "normal" string and as UCS-2 converted string for - * matching _NET_WM_NAME capable clients as well as those using legacy hints. - * - */ -bool client_matches_class_name(Client *client, char *to_class, char *to_title, - char *to_title_ucs, int to_title_ucs_len) { - /* Check if the given class is part of the window class */ - if ((client->window_class_instance == NULL || - strcasestr(client->window_class_instance, to_class) == NULL) && - (client->window_class_class == NULL || - strcasestr(client->window_class_class, to_class) == NULL)) - return false; - - /* If no title was given, we’re done */ - if (to_title == NULL) - return true; - - if (client->name_len > -1) { - /* UCS-2 converted window titles */ - if (client->name == NULL || memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL) - return false; - } else { - /* Legacy hints */ - if (client->name == NULL || strcasestr(client->name, to_title) == NULL) - return false; - } - - return true; -} - -/* - * Sets the position of the given client in the X stack to the highest (tiling layer is always - * on the same position, so this doesn’t matter) below the first floating client, so that - * floating windows are always on top. - * - */ -void client_set_below_floating(xcb_connection_t *conn, Client *client) { - /* Ensure that it is below all floating clients */ - Workspace *ws = client->workspace; - Client *first_floating = TAILQ_FIRST(&(ws->floating_clients)); - if (first_floating == TAILQ_END(&(ws->floating_clients))) - return; - - DLOG("Setting below floating\n"); - uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW }; - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); - - if (client->workspace->fullscreen_client == NULL) - return; - - DLOG("(and below fullscreen)\n"); - /* Ensure that the window is still below the fullscreen window */ - values[0] = client->workspace->fullscreen_client->frame; - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); -} - -/* - * Returns true if the client is floating. Makes the code more beatiful, as floating - * is not simply a boolean, but also saves whether the user selected the current state - * or whether it was automatically set. - * - */ -bool client_is_floating(Client *client) { - return (client->floating >= FLOATING_AUTO_ON); -} - -/* - * Change the border type for the given client to normal (n), 1px border (p) or - * completely borderless (b) without actually re-rendering the layout. Useful - * for calling it when initializing a new client. - * - */ -bool client_init_border(xcb_connection_t *conn, Client *client, char border_type) { - switch (border_type) { - case 'n': - LOG("Changing to normal border\n"); - client->titlebar_position = TITLEBAR_TOP; - client->borderless = false; - return true; - case 'p': - LOG("Changing to 1px border\n"); - client->titlebar_position = TITLEBAR_OFF; - client->borderless = false; - return true; - case 'b': - LOG("Changing to borderless\n"); - client->titlebar_position = TITLEBAR_OFF; - client->borderless = true; - return true; - default: - LOG("Unknown border mode\n"); - return false; - } -} - -/* - * Change the border type for the given client to normal (n), 1px border (p) or - * completely borderless (b). - * - */ -void client_change_border(xcb_connection_t *conn, Client *client, char border_type) { - if (!client_init_border(conn, client, border_type)) - return; - - /* Ensure that the child’s position inside our window gets updated */ - client->force_reconfigure = true; - - /* For clients inside a container, we can simply render the container */ - if (client->container != NULL) - render_container(conn, client->container); - else { - /* If the client is floating, directly push its size */ - if (client_is_floating(client)) - resize_client(conn, client); - /* Otherwise, it may be a dock client, thus render the whole layout */ - else render_layout(conn); - } - - redecorate_window(conn, client); -} - -/* - * Unmap the client, correctly setting any state which is needed. - * - */ -void client_unmap(xcb_connection_t *conn, Client *client) { - /* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */ - long data[] = { XCB_WM_STATE_WITHDRAWN, XCB_NONE }; - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data); - - xcb_unmap_window(conn, client->frame); -} - -/* - * Map the client, correctly restoring any state needed. - * - */ -void client_map(xcb_connection_t *conn, Client *client) { - /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t. - * Also, xprop(1) needs that to work. */ - long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE }; - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data); - - xcb_map_window(conn, client->frame); -} - -/* - * Set the given mark for this client. Used for jumping to the client - * afterwards (like m and ' in vim). - * - */ -void client_mark(xcb_connection_t *conn, Client *client, const char *mark) { - if (client->mark != NULL) - free(client->mark); - client->mark = sstrdup(mark); - - /* Make sure no other client has this mark set */ - Client *current; - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) - SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) { - if (current == client || - current->mark == NULL || - strcmp(current->mark, mark) != 0) - continue; - - free(current->mark); - current->mark = NULL; - /* We can break here since there can only be one other - * client with this mark. */ - break; - } -} - -/* - * Returns the minimum height of a specific window. The height is calculated - * by using 2 pixels (for the client window itself), possibly padding this to - * comply with the client’s base_height and then adding the decoration height. - * - */ -uint32_t client_min_height(Client *client) { - uint32_t height = max(2, client->base_height); - i3Font *font = load_font(global_conn, config.font); - - if (client->titlebar_position == TITLEBAR_OFF && client->borderless) - return height; - - if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) - return height + 2; - - return height + font->height + 2 + 2; -} - -/* - * See client_min_height. - * - */ -uint32_t client_min_width(Client *client) { - uint32_t width = max(2, client->base_width); - - if (client->titlebar_position == TITLEBAR_OFF && client->borderless) - return width; - - if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) - return width + 2; - - return width + 2 + 2; -} diff --git a/src/commands.c b/src/commands.c deleted file mode 100644 index 2d8155f2..00000000 --- a/src/commands.c +++ /dev/null @@ -1,1301 +0,0 @@ -/* - * vim:ts=8:expandtab - * - * i3 - an improved dynamic tiling window manager - * - * © 2009-2010 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - */ -#include -#include -#include -#include -#include -#include - -#include - -#include "util.h" -#include "data.h" -#include "table.h" -#include "layout.h" -#include "i3.h" -#include "randr.h" -#include "client.h" -#include "floating.h" -#include "xcb.h" -#include "config.h" -#include "workspace.h" -#include "commands.h" -#include "resize.h" -#include "log.h" -#include "sighandler.h" -#include "manage.h" -#include "ipc.h" - -bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { - /* If this container is empty, we’re done */ - if (container->currently_focused == NULL) - return false; - - /* Get the previous/next client or wrap around */ - Client *candidate = NULL; - if (direction == D_UP) { - if ((candidate = CIRCLEQ_PREV_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL) - candidate = CIRCLEQ_LAST(&(container->clients)); - } - else if (direction == D_DOWN) { - if ((candidate = CIRCLEQ_NEXT_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL) - candidate = CIRCLEQ_FIRST(&(container->clients)); - } else ELOG("Direction not implemented!\n"); - - /* If we could not switch, the container contains exactly one client. We return false */ - if (candidate == container->currently_focused) - return false; - - /* Set focus */ - set_focus(conn, candidate, true); - - return true; -} - -typedef enum { THING_WINDOW, THING_CONTAINER, THING_SCREEN } thing_t; - -static void jump_to_mark(xcb_connection_t *conn, const char *mark) { - Client *current; - LOG("Jumping to \"%s\"\n", mark); - - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) - SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) { - if (current->mark == NULL || strcmp(current->mark, mark) != 0) - continue; - - workspace_show(conn, current->workspace->num + 1); - set_focus(conn, current, true); - return; - } - - ELOG("No window with this mark found\n"); -} - -static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) { - DLOG("focusing direction %d\n", direction); - - int new_row = current_row, - new_col = current_col; - Container *container = CUR_CELL; - Workspace *t_ws = c_ws; - - /* Makes sure new_col and new_row are within bounds of the new workspace */ -#define CHECK_COLROW_BOUNDARIES \ - do { \ - if (new_col >= t_ws->cols) \ - new_col = (t_ws->cols - 1); \ - if (new_row >= t_ws->rows) \ - new_row = (t_ws->rows - 1); \ - } while (0) - - /* There always is a container. If not, current_col or current_row is wrong */ - assert(container != NULL); - - if (container->workspace->fullscreen_client != NULL) { - LOG("You're in fullscreen mode. Forcing focus to operate on whole screens\n"); - thing = THING_SCREEN; - } - - /* For focusing screens, situation is different: we get the rect - * of the current screen, then get the screen which is on its - * right/left/bottom/top and just switch to the workspace on - * the target screen. */ - if (thing == THING_SCREEN) { - Output *cs = c_ws->output; - assert(cs != NULL); - Rect bounds = cs->rect; - - if (direction == D_RIGHT) - bounds.x += bounds.width; - else if (direction == D_LEFT) - bounds.x -= bounds.width; - else if (direction == D_UP) - bounds.y -= bounds.height; - else bounds.y += bounds.height; - - Output *target = get_output_containing(bounds.x, bounds.y); - if (target == NULL) { - DLOG("Target output NULL\n"); - /* Wrap around if the target screen is out of bounds */ - if (direction == D_RIGHT) - target = get_output_most(D_LEFT, cs); - else if (direction == D_LEFT) - target = get_output_most(D_RIGHT, cs); - else if (direction == D_UP) - target = get_output_most(D_DOWN, cs); - else target = get_output_most(D_UP, cs); - } - - DLOG("Switching to ws %d\n", target->current_workspace + 1); - workspace_show(conn, target->current_workspace->num + 1); - return; - } - - /* TODO: for horizontal default layout, this has to be expanded to LEFT/RIGHT */ - if (direction == D_UP || direction == D_DOWN) { - if (thing == THING_WINDOW) - /* Let’s see if we can perform up/down focus in the current container */ - if (focus_window_in_container(conn, container, direction)) - return; - - if (direction == D_DOWN && cell_exists(t_ws, current_col, current_row+1)) - new_row = current_row + t_ws->table[current_col][current_row]->rowspan; - else if (direction == D_UP && cell_exists(c_ws, current_col, current_row-1)) { - /* Set new_row as a sane default, but it may get overwritten in a second */ - new_row--; - - /* Search from the top to correctly handle rowspanned containers */ - for (int rows = 0; rows < current_row; rows += t_ws->table[current_col][rows]->rowspan) { - if (new_row > (rows + (t_ws->table[current_col][rows]->rowspan - 1))) - continue; - - new_row = rows; - break; - } - } else { - /* Let’s see if there is a screen down/up there to which we can switch */ - DLOG("container is at %d with height %d\n", container->y, container->height); - Output *output; - int destination_y = (direction == D_UP ? (container->y - 1) : (container->y + container->height + 1)); - if ((output = get_output_containing(container->x, destination_y)) == NULL) { - DLOG("Wrapping screen around vertically\n"); - /* No screen found? Then wrap */ - output = get_output_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->output); - } - t_ws = output->current_workspace; - new_row = (direction == D_UP ? (t_ws->rows - 1) : 0); - } - - CHECK_COLROW_BOUNDARIES; - - DLOG("new_col = %d, new_row = %d\n", new_col, new_row); - if (t_ws->table[new_col][new_row]->currently_focused == NULL) { - DLOG("Cell empty, checking for colspanned client above...\n"); - for (int cols = 0; cols < new_col; cols += t_ws->table[cols][new_row]->colspan) { - if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1))) - continue; - - new_col = cols; - DLOG("Fixed it to new col %d\n", new_col); - break; - } - } - - if (t_ws->table[new_col][new_row]->currently_focused == NULL) { - DLOG("Cell still empty, checking for full cols above spanned width...\n"); - DLOG("new_col = %d\n", new_col); - DLOG("colspan = %d\n", container->colspan); - for (int cols = new_col; - cols < container->col + container->colspan; - cols += t_ws->table[cols][new_row]->colspan) { - DLOG("candidate: new_row = %d, cols = %d\n", new_row, cols); - if (t_ws->table[cols][new_row]->currently_focused == NULL) - continue; - - new_col = cols; - DLOG("Fixed it to new col %d\n", new_col); - break; - } - } - } else if (direction == D_LEFT || direction == D_RIGHT) { - if (direction == D_RIGHT && cell_exists(t_ws, current_col+1, current_row)) - new_col = current_col + t_ws->table[current_col][current_row]->colspan; - else if (direction == D_LEFT && cell_exists(t_ws, current_col-1, current_row)) { - /* Set new_col as a sane default, but it may get overwritten in a second */ - new_col--; - - /* Search from the left to correctly handle colspanned containers */ - for (int cols = 0; cols < current_col; cols += t_ws->table[cols][current_row]->colspan) { - if (new_col > (cols + (t_ws->table[cols][current_row]->colspan - 1))) - continue; - - new_col = cols; - break; - } - } else { - /* Let’s see if there is a screen left/right here to which we can switch */ - DLOG("container is at %d with width %d\n", container->x, container->width); - Output *output; - int destination_x = (direction == D_LEFT ? (container->x - 1) : (container->x + container->width + 1)); - if ((output = get_output_containing(destination_x, container->y)) == NULL) { - DLOG("Wrapping screen around horizontally\n"); - output = get_output_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->output); - } - t_ws = output->current_workspace; - new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0); - } - - CHECK_COLROW_BOUNDARIES; - - DLOG("new_col = %d, new_row = %d\n", new_col, new_row); - if (t_ws->table[new_col][new_row]->currently_focused == NULL) { - DLOG("Cell empty, checking for rowspanned client above...\n"); - for (int rows = 0; rows < new_row; rows += t_ws->table[new_col][rows]->rowspan) { - if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1))) - continue; - - new_row = rows; - DLOG("Fixed it to new row %d\n", new_row); - break; - } - } - - if (t_ws->table[new_col][new_row]->currently_focused == NULL) { - DLOG("Cell still empty, checking for full cols near full spanned height...\n"); - DLOG("new_row = %d\n", new_row); - DLOG("rowspan = %d\n", container->rowspan); - for (int rows = new_row; - rows < container->row + container->rowspan; - rows += t_ws->table[new_col][rows]->rowspan) { - DLOG("candidate: new_col = %d, rows = %d\n", new_col, rows); - if (t_ws->table[new_col][rows]->currently_focused == NULL) - continue; - - new_row = rows; - DLOG("Fixed it to new col %d\n", new_row); - break; - } - } - - } else { - ELOG("direction unhandled\n"); - return; - } - - CHECK_COLROW_BOUNDARIES; - - if (t_ws->table[new_col][new_row]->currently_focused != NULL) - set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true); -} - -/* - * Tries to move the window inside its current container. - * - * Returns true if the window could be moved, false otherwise. - * - */ -static bool move_current_window_in_container(xcb_connection_t *conn, Client *client, - direction_t direction) { - assert(client->container != NULL); - - Client *other = (direction == D_UP ? CIRCLEQ_PREV(client, clients) : - CIRCLEQ_NEXT(client, clients)); - - if (other == CIRCLEQ_END(&(client->container->clients))) - return false; - - DLOG("i can do that\n"); - /* We can move the client inside its current container */ - CIRCLEQ_REMOVE(&(client->container->clients), client, clients); - if (direction == D_UP) - CIRCLEQ_INSERT_BEFORE(&(client->container->clients), other, client, clients); - else CIRCLEQ_INSERT_AFTER(&(client->container->clients), other, client, clients); - render_layout(conn); - return true; -} - -/* - * Moves the current window or whole container to the given direction, creating a column/row if - * necessary. - * - */ -static void move_current_window(xcb_connection_t *conn, direction_t direction) { - LOG("moving window to direction %s\n", (direction == D_UP ? "up" : (direction == D_DOWN ? "down" : - (direction == D_LEFT ? "left" : "right")))); - /* Get current window */ - Container *container = CUR_CELL, - *new = NULL; - - /* There has to be a container, see focus_window() */ - assert(container != NULL); - - /* If there is no window or the dock window is focused, we’re done */ - if (container->currently_focused == NULL || - container->currently_focused->dock) - return; - - /* As soon as the client is moved away, the last focused client in the old - * container needs to get focus, if any. Therefore, we save it here. */ - Client *current_client = container->currently_focused; - Client *to_focus = get_last_focused_client(conn, container, current_client); - - if (to_focus == NULL) { - to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients); - if (to_focus == NULL) - to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients); - } - - switch (direction) { - case D_LEFT: - /* If we’re at the left-most position, move the rest of the table right */ - if (current_col == 0) { - expand_table_cols_at_head(c_ws); - new = CUR_CELL; - } else - new = CUR_TABLE[--current_col][current_row]; - break; - case D_RIGHT: - if (current_col == (c_ws->cols-1)) - expand_table_cols(c_ws); - - new = CUR_TABLE[++current_col][current_row]; - break; - case D_UP: - if (move_current_window_in_container(conn, current_client, D_UP)) - return; - - /* if we’re at the up-most position, move the rest of the table down */ - if (current_row == 0) { - expand_table_rows_at_head(c_ws); - new = CUR_CELL; - } else - new = CUR_TABLE[current_col][--current_row]; - break; - case D_DOWN: - if (move_current_window_in_container(conn, current_client, D_DOWN)) - return; - - if (current_row == (c_ws->rows-1)) - expand_table_rows(c_ws); - - new = CUR_TABLE[current_col][++current_row]; - break; - /* To make static analyzers happy: */ - default: - return; - } - - /* Remove it from the old container and put it into the new one */ - client_remove_from_container(conn, current_client, container, true); - - if (new->currently_focused != NULL) - CIRCLEQ_INSERT_AFTER(&(new->clients), new->currently_focused, current_client, clients); - else CIRCLEQ_INSERT_TAIL(&(new->clients), current_client, clients); - SLIST_INSERT_HEAD(&(new->workspace->focus_stack), current_client, focus_clients); - - /* Update data structures */ - current_client->container = new; - current_client->workspace = new->workspace; - container->currently_focused = to_focus; - new->currently_focused = current_client; - - Workspace *workspace = container->workspace; - - /* delete all empty columns/rows */ - cleanup_table(conn, workspace); - - /* Fix colspan/rowspan if it’d overlap */ - fix_colrowspan(conn, workspace); - - render_workspace(conn, workspace->output, workspace); - xcb_flush(conn); - - set_focus(conn, current_client, true); -} - -static void move_current_container(xcb_connection_t *conn, direction_t direction) { - LOG("moving container to direction %s\n", (direction == D_UP ? "up" : (direction == D_DOWN ? "down" : - (direction == D_LEFT ? "left" : "right")))); - /* Get current window */ - Container *container = CUR_CELL, - *new = NULL; - - Container **old = &CUR_CELL; - - /* There has to be a container, see focus_window() */ - assert(container != NULL); - - switch (direction) { - case D_LEFT: - /* If we’re at the left-most position, move the rest of the table right */ - if (current_col == 0) { - expand_table_cols_at_head(c_ws); - new = CUR_CELL; - old = &CUR_TABLE[current_col+1][current_row]; - } else - new = CUR_TABLE[--current_col][current_row]; - break; - case D_RIGHT: - if (current_col == (c_ws->cols-1)) - expand_table_cols(c_ws); - - new = CUR_TABLE[++current_col][current_row]; - break; - case D_UP: - /* if we’re at the up-most position, move the rest of the table down */ - if (current_row == 0) { - expand_table_rows_at_head(c_ws); - new = CUR_CELL; - old = &CUR_TABLE[current_col][current_row+1]; - } else - new = CUR_TABLE[current_col][--current_row]; - break; - case D_DOWN: - if (current_row == (c_ws->rows-1)) - expand_table_rows(c_ws); - - new = CUR_TABLE[current_col][++current_row]; - break; - /* To make static analyzers happy: */ - default: - return; - } - - DLOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row); - - /* Swap the containers */ - int col = new->col; - int row = new->row; - - *old = new; - new->col = container->col; - new->row = container->row; - - CUR_CELL = container; - container->col = col; - container->row = row; - - Workspace *workspace = container->workspace; - - /* delete all empty columns/rows */ - cleanup_table(conn, workspace); - - /* Fix colspan/rowspan if it’d overlap */ - fix_colrowspan(conn, workspace); - - render_layout(conn); -} - -/* - * "Snaps" the current container (not possible for windows, because it works at table base) - * to the given direction, that is, adjusts cellspan/rowspan - * - */ -static void snap_current_container(xcb_connection_t *conn, direction_t direction) { - LOG("snapping container to direction %d\n", direction); - - Container *container = CUR_CELL; - - assert(container != NULL); - - switch (direction) { - case D_LEFT: - /* Snap to the left is actually a move to the left and then a snap right */ - if (!cell_exists(container->workspace, container->col - 1, container->row) || - CUR_TABLE[container->col-1][container->row]->currently_focused != NULL) { - ELOG("cannot snap to left - the cell is already used\n"); - return; - } - - move_current_window(conn, D_LEFT); - snap_current_container(conn, D_RIGHT); - return; - case D_RIGHT: { - /* Check if the cell is used */ - int new_col = container->col + container->colspan; - for (int i = 0; i < container->rowspan; i++) - if (!cell_exists(container->workspace, new_col, container->row + i) || - CUR_TABLE[new_col][container->row + i]->currently_focused != NULL) { - ELOG("cannot snap to right - the cell is already used\n"); - return; - } - - /* Check if there are other cells with rowspan, which are in our way. - * If so, reduce their rowspan. */ - for (int i = container->row-1; i >= 0; i--) { - DLOG("we got cell %d, %d with rowspan %d\n", - new_col, i, CUR_TABLE[new_col][i]->rowspan); - while ((CUR_TABLE[new_col][i]->rowspan-1) >= (container->row - i)) - CUR_TABLE[new_col][i]->rowspan--; - DLOG("new rowspan = %d\n", CUR_TABLE[new_col][i]->rowspan); - } - - container->colspan++; - break; - } - case D_UP: - if (!cell_exists(container->workspace, container->col, container->row - 1) || - CUR_TABLE[container->col][container->row-1]->currently_focused != NULL) { - ELOG("cannot snap to top - the cell is already used\n"); - return; - } - - move_current_window(conn, D_UP); - snap_current_container(conn, D_DOWN); - return; - case D_DOWN: { - DLOG("snapping down\n"); - int new_row = container->row + container->rowspan; - for (int i = 0; i < container->colspan; i++) - if (!cell_exists(container->workspace, container->col + i, new_row) || - CUR_TABLE[container->col + i][new_row]->currently_focused != NULL) { - ELOG("cannot snap down - the cell is already used\n"); - return; - } - - for (int i = container->col-1; i >= 0; i--) { - DLOG("we got cell %d, %d with colspan %d\n", - i, new_row, CUR_TABLE[i][new_row]->colspan); - while ((CUR_TABLE[i][new_row]->colspan-1) >= (container->col - i)) - CUR_TABLE[i][new_row]->colspan--; - DLOG("new colspan = %d\n", CUR_TABLE[i][new_row]->colspan); - - } - - container->rowspan++; - break; - } - /* To make static analyzers happy: */ - default: - return; - } - - render_layout(conn); -} - -static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *client, int workspace) { - /* t_ws (to workspace) is just a container pointer to the workspace we’re switching to */ - Workspace *t_ws = workspace_get(workspace-1), - *old_ws = client->workspace; - - LOG("moving floating\n"); - - workspace_initialize(t_ws, c_ws->output, false); - - /* Check if there is already a fullscreen client on the destination workspace and - * stop moving if so. */ - if (client->fullscreen && (t_ws->fullscreen_client != NULL)) { - ELOG("Not moving: Fullscreen client already existing on destination workspace.\n"); - return; - } - - floating_assign_to_workspace(client, t_ws); - - /* If we’re moving it to an invisible screen, we need to unmap it */ - if (!workspace_is_visible(t_ws)) { - DLOG("This workspace is not visible, unmapping\n"); - client_unmap(conn, client); - } else { - /* If this is not the case, we move the window to a workspace - * which is on another screen, so we also need to adjust its - * coordinates. */ - DLOG("before x = %d, y = %d\n", client->rect.x, client->rect.y); - uint32_t relative_x = client->rect.x - old_ws->rect.x, - relative_y = client->rect.y - old_ws->rect.y; - DLOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y); - client->rect.x = t_ws->rect.x + relative_x; - client->rect.y = t_ws->rect.y + relative_y; - DLOG("after x = %d, y = %d\n", client->rect.x, client->rect.y); - reposition_client(conn, client); - xcb_flush(conn); - } - - /* Configure the window above all tiling windows (or below a fullscreen - * window, if any) */ - if (t_ws->fullscreen_client != NULL) { - uint32_t values[] = { t_ws->fullscreen_client->frame, XCB_STACK_MODE_BELOW }; - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); - } else { - Client *last_tiling; - SLIST_FOREACH(last_tiling, &(t_ws->focus_stack), focus_clients) - if (!client_is_floating(last_tiling)) - break; - if (last_tiling != SLIST_END(&(t_ws->focus_stack))) { - uint32_t values[] = { last_tiling->frame, XCB_STACK_MODE_ABOVE }; - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); - } - } - - DLOG("done\n"); - - render_layout(conn); - - if (workspace_is_visible(t_ws)) { - client_warp_pointer_into(conn, client); - set_focus(conn, client, true); - } -} - -/* - * Moves the currently selected window to the given workspace - * - */ -static void move_current_window_to_workspace(xcb_connection_t *conn, int workspace) { - LOG("Moving current window to workspace %d\n", workspace); - - Container *container = CUR_CELL; - - assert(container != NULL); - - /* t_ws (to workspace) is just a container pointer to the workspace we’re switching to */ - Workspace *t_ws = workspace_get(workspace-1); - - Client *current_client = container->currently_focused; - if (current_client == NULL) { - ELOG("No currently focused client in current container.\n"); - return; - } - Client *to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients); - if (to_focus == NULL) - to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients); - - workspace_initialize(t_ws, container->workspace->output, false); - /* Check if there is already a fullscreen client on the destination workspace and - * stop moving if so. */ - if (current_client->fullscreen && (t_ws->fullscreen_client != NULL)) { - ELOG("Not moving: Fullscreen client already existing on destination workspace.\n"); - return; - } - - Container *to_container = t_ws->table[t_ws->current_col][t_ws->current_row]; - - assert(to_container != NULL); - - client_remove_from_container(conn, current_client, container, true); - if (container->workspace->fullscreen_client == current_client) - container->workspace->fullscreen_client = NULL; - - /* TODO: insert it to the correct position */ - CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients); - - SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients); - DLOG("Moved.\n"); - - current_client->container = to_container; - current_client->workspace = to_container->workspace; - container->currently_focused = to_focus; - to_container->currently_focused = current_client; - - /* If we’re moving it to an invisible screen, we need to unmap it */ - if (!workspace_is_visible(to_container->workspace)) { - DLOG("This workspace is not visible, unmapping\n"); - client_unmap(conn, current_client); - } else { - if (current_client->fullscreen) { - DLOG("Calling client_enter_fullscreen again\n"); - client_enter_fullscreen(conn, current_client, false); - } - } - - /* delete all empty columns/rows */ - cleanup_table(conn, container->workspace); - - render_layout(conn); - - if (workspace_is_visible(to_container->workspace)) { - client_warp_pointer_into(conn, current_client); - set_focus(conn, current_client, true); - } -} - -/* - * Jumps to the given window class / title. - * Title is matched using strstr, that is, matches if it appears anywhere - * in the string. Regular expressions seem to be a bit overkill here. However, - * if we need them for something else somewhen, we may introduce them here, too. - * - */ -static void jump_to_window(xcb_connection_t *conn, const char *arguments) { - char *classtitle; - Client *client; - - /* The first character is a quote, this was checked before */ - classtitle = sstrdup(arguments+1); - /* The last character is a quote, we just set it to NULL */ - classtitle[strlen(classtitle)-1] = '\0'; - - if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) { - free(classtitle); - ELOG("No matching client found.\n"); - return; - } - - free(classtitle); - workspace_show(conn, client->workspace->num + 1); - set_focus(conn, client, true); -} - -/* - * Jump directly to the specified workspace, row and col. - * Great for reaching windows that you always keep in the same spot (hello irssi, I'm looking at you) - * - */ -static void jump_to_container(xcb_connection_t *conn, const char *arguments) { - int ws, row, col; - int result; - - result = sscanf(arguments, "%d %d %d", &ws, &col, &row); - LOG("Jump called with %d parameters (\"%s\")\n", result, arguments); - - /* No match? Either no arguments were specified, or no numbers */ - if (result < 1) { - ELOG("At least one valid argument required\n"); - return; - } - - /* Move to the target workspace */ - workspace_show(conn, ws); - - if (result < 3) - return; - - DLOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows); - - /* Move to row/col */ - if (row >= c_ws->rows) - row = c_ws->rows - 1; - if (col >= c_ws->cols) - col = c_ws->cols - 1; - - DLOG("Jumping to col %d, row %d\n", col, row); - if (c_ws->table[col][row]->currently_focused != NULL) - set_focus(conn, c_ws->table[col][row]->currently_focused, true); -} - -/* - * Travels the focus stack by the given number of times (or once, if no argument - * was specified). That is, selects the window you were in before you focused - * the current window. - * - * The special values 'floating' (select the next floating window), 'tiling' - * (select the next tiling window), 'ft' (if the current window is floating, - * select the next tiling window and vice-versa) are also valid - * - */ -static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) { - /* Start count at -1 to always skip the first element */ - int times, count = -1; - Client *current; - bool floating_criteria; - - /* Either it’s one of the special values… */ - if (strcasecmp(arguments, "floating") == 0) { - floating_criteria = true; - } else if (strcasecmp(arguments, "tiling") == 0) { - floating_criteria = false; - } else if (strcasecmp(arguments, "ft") == 0) { - Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); - if (last_focused == SLIST_END(&(c_ws->focus_stack))) { - ELOG("Cannot select the next floating/tiling client because there is no client at all\n"); - return; - } - - floating_criteria = !client_is_floating(last_focused); - } else { - /* …or a number was specified */ - if (sscanf(arguments, "%u", ×) != 1) { - ELOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments); - times = 1; - } - - SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) { - if (++count < times) { - DLOG("Skipping\n"); - continue; - } - - DLOG("Focussing\n"); - set_focus(conn, current, true); - break; - } - return; - } - - /* Select the next client matching the criteria parsed above */ - SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) - if (client_is_floating(current) == floating_criteria) { - set_focus(conn, current, true); - break; - } -} - -/* - * Switch to next or previous existing workspace - * - */ -static void next_previous_workspace(xcb_connection_t *conn, int direction) { - Workspace *ws = c_ws; - - if (direction == 'n') { - while (1) { - ws = TAILQ_NEXT(ws, workspaces); - - if (ws == TAILQ_END(workspaces)) - ws = TAILQ_FIRST(workspaces); - - if (ws == c_ws) - return; - - if (ws->output == NULL) - continue; - - workspace_show(conn, ws->num + 1); - return; - } - } else if (direction == 'p') { - while (1) { - ws = TAILQ_PREV(ws, workspaces_head, workspaces); - - if (ws == TAILQ_END(workspaces)) - ws = TAILQ_LAST(workspaces, workspaces_head); - - if (ws == c_ws) - return; - - if (ws->output == NULL) - continue; - - workspace_show(conn, ws->num + 1); - return; - } - } -} - -static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, const char *command) { - int first, second; - resize_orientation_t orientation = O_VERTICAL; - Container *con = last_focused->container; - Workspace *ws = last_focused->workspace; - - if (client_is_floating(last_focused)) { - DLOG("Resizing a floating client\n"); - if (STARTS_WITH(command, "left")) { - command += strlen("left"); - last_focused->rect.width += atoi(command); - last_focused->rect.x -= atoi(command); - } else if (STARTS_WITH(command, "right")) { - command += strlen("right"); - last_focused->rect.width += atoi(command); - } else if (STARTS_WITH(command, "top")) { - command += strlen("top"); - last_focused->rect.height += atoi(command); - last_focused->rect.y -= atoi(command); - } else if (STARTS_WITH(command, "bottom")) { - command += strlen("bottom"); - last_focused->rect.height += atoi(command); - } else { - ELOG("Syntax: resize [+|-]\n"); - return; - } - - /* resize_client flushes */ - resize_client(conn, last_focused); - - return; - } - - if (STARTS_WITH(command, "left")) { - if (con->col == 0) - return; - first = con->col - 1; - second = con->col; - command += strlen("left"); - } else if (STARTS_WITH(command, "right")) { - first = con->col + (con->colspan - 1); - DLOG("column %d\n", first); - - if (!cell_exists(ws, first, con->row) || - (first == (ws->cols-1))) - return; - - second = first + 1; - command += strlen("right"); - } else if (STARTS_WITH(command, "top")) { - if (con->row == 0) - return; - first = con->row - 1; - second = con->row; - orientation = O_HORIZONTAL; - command += strlen("top"); - } else if (STARTS_WITH(command, "bottom")) { - first = con->row + (con->rowspan - 1); - if (!cell_exists(ws, con->col, first) || - (first == (ws->rows-1))) - return; - - second = first + 1; - orientation = O_HORIZONTAL; - command += strlen("bottom"); - } else { - ELOG("Syntax: resize [+|-]\n"); - return; - } - - int pixels = atoi(command); - if (pixels == 0) - return; - - resize_container(conn, ws, first, second, orientation, pixels); -} - -/* - * Parses a command, see file CMDMODE for more information - * - */ -void parse_command(xcb_connection_t *conn, const char *command) { - LOG("--- parsing command \"%s\" ---\n", command); - /* Get the first client from focus stack because floating clients are not - * in any container, therefore CUR_CELL is not appropriate. */ - Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); - if (last_focused == SLIST_END(&(c_ws->focus_stack))) - last_focused = NULL; - - /* Hmm, just to be sure */ - if (command[0] == '\0') - return; - - /* Is it an ? Then execute the given command. */ - if (STARTS_WITH(command, "exec ")) { - LOG("starting \"%s\"\n", command + strlen("exec ")); - start_application(command+strlen("exec ")); - return; - } - - if (STARTS_WITH(command, "mark")) { - if (last_focused == NULL) { - ELOG("There is no window to mark\n"); - return; - } - const char *rest = command + strlen("mark"); - while (*rest == ' ') - rest++; - if (*rest == '\0') { - DLOG("interactive mark starting\n"); - start_application("i3-input -p 'mark ' -l 1 -P 'Mark: '"); - } else { - LOG("mark with \"%s\"\n", rest); - client_mark(conn, last_focused, rest); - } - return; - } - - if (STARTS_WITH(command, "goto")) { - const char *rest = command + strlen("goto"); - while (*rest == ' ') - rest++; - if (*rest == '\0') { - DLOG("interactive go to mark starting\n"); - start_application("i3-input -p 'goto ' -l 1 -P 'Goto: '"); - } else { - LOG("go to \"%s\"\n", rest); - jump_to_mark(conn, rest); - } - return; - } - - if (STARTS_WITH(command, "stack-limit ")) { - if (last_focused == NULL || client_is_floating(last_focused)) { - ELOG("No container focused\n"); - return; - } - const char *rest = command + strlen("stack-limit "); - if (strncmp(rest, "rows ", strlen("rows ")) == 0) { - last_focused->container->stack_limit = STACK_LIMIT_ROWS; - rest += strlen("rows "); - } else if (strncmp(rest, "cols ", strlen("cols ")) == 0) { - last_focused->container->stack_limit = STACK_LIMIT_COLS; - rest += strlen("cols "); - } else { - ELOG("Syntax: stack-limit \n"); - return; - } - - last_focused->container->stack_limit_value = atoi(rest); - if (last_focused->container->stack_limit_value == 0) - last_focused->container->stack_limit = STACK_LIMIT_NONE; - - return; - } - - if (STARTS_WITH(command, "resize ")) { - if (last_focused == NULL) - return; - const char *rest = command + strlen("resize "); - parse_resize_command(conn, last_focused, rest); - return; - } - - if (STARTS_WITH(command, "mode ")) { - const char *rest = command + strlen("mode "); - switch_mode(conn, rest); - return; - } - - /* Is it an ? */ - if (STARTS_WITH(command, "exit")) { - LOG("User issued exit-command, exiting without error.\n"); - restore_geometry(global_conn); - ipc_shutdown(); - exit(EXIT_SUCCESS); - } - - /* Is it a ? */ - if (STARTS_WITH(command, "reload")) { - load_configuration(conn, NULL, true); - render_layout(conn); - /* Send an IPC event just in case the ws names have changed */ - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}"); - return; - } - - /* Is it ? Then restart in place. */ - if (STARTS_WITH(command, "restart")) { - i3_restart(); - } - - if (STARTS_WITH(command, "kill")) { - if (last_focused == NULL) { - ELOG("There is no window to kill\n"); - return; - } - - LOG("Killing current window\n"); - client_kill(conn, last_focused); - return; - } - - /* Is it a jump to a specified workspace, row, col? */ - if (STARTS_WITH(command, "jump ")) { - const char *arguments = command + strlen("jump "); - if (arguments[0] == '"') - jump_to_window(conn, arguments); - else jump_to_container(conn, arguments); - return; - } - - /* Should we travel the focus stack? */ - if (STARTS_WITH(command, "focus")) { - const char *arguments = command + strlen("focus "); - travel_focus_stack(conn, arguments); - return; - } - - /* Is it 'f' for fullscreen, or 'fg' for fullscreen_global? */ - if (command[0] == 'f') { - if (last_focused == NULL) - return; - if (command[1] == 'g') - client_toggle_fullscreen_global(conn, last_focused); - else - client_toggle_fullscreen(conn, last_focused); - return; - } - - /* Is it just 's' for stacking or 'd' for default? */ - if ((command[0] == 's' || command[0] == 'd' || command[0] == 'T') && (command[1] == '\0')) { - if (last_focused != NULL && client_is_floating(last_focused)) { - ELOG("not switching, this is a floating client\n"); - return; - } - LOG("Switching mode for current container\n"); - int new_mode = MODE_DEFAULT; - if (command[0] == 's' && CUR_CELL->mode != MODE_STACK) - new_mode = MODE_STACK; - if (command[0] == 'T' && CUR_CELL->mode != MODE_TABBED) - new_mode = MODE_TABBED; - switch_layout_mode(conn, CUR_CELL, new_mode); - return; - } - - /* Is it 'bn' (border normal), 'bp' (border 1pixel) or 'bb' (border borderless)? */ - /* or even 'bt' (toggle border: 'bp' -> 'bb' -> 'bn' ) */ - if (command[0] == 'b') { - if (last_focused == NULL) { - ELOG("No window focused, cannot change border type\n"); - return; - } - - char com = command[1]; - if (command[1] == 't') { - if (last_focused->titlebar_position == TITLEBAR_TOP && - !last_focused->borderless) - com = 'p'; - else if (last_focused->titlebar_position == TITLEBAR_OFF && - !last_focused->borderless) - com = 'b'; - else com = 'n'; - } - - client_change_border(conn, last_focused, com); - return; - } - - if (command[0] == 'H') { - LOG("Hiding all floating windows\n"); - floating_toggle_hide(conn, c_ws); - return; - } - - enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE, WITH_SCREEN } with = WITH_WINDOW; - - /* Is it a ? */ - if (command[0] == 'w') { - command++; - /* TODO: implement */ - if (command[0] == 'c') { - with = WITH_CONTAINER; - command++; - } else if (command[0] == 'w') { - with = WITH_WORKSPACE; - command++; - } else if (command[0] == 's') { - with = WITH_SCREEN; - command++; - } else { - ELOG("not yet implemented.\n"); - return; - } - } - - /* Is it 't' for toggle tiling/floating? */ - if (command[0] == 't') { - if (with == WITH_WORKSPACE) { - c_ws->auto_float = !c_ws->auto_float; - LOG("autofloat is now %d\n", c_ws->auto_float); - return; - } - if (last_focused == NULL) { - ELOG("Cannot toggle tiling/floating: workspace empty\n"); - return; - } - - Workspace *ws = last_focused->workspace; - - if (last_focused->fullscreen) - client_leave_fullscreen(conn, last_focused); - - toggle_floating_mode(conn, last_focused, false); - /* delete all empty columns/rows */ - cleanup_table(conn, ws); - - /* Fix colspan/rowspan if it’d overlap */ - fix_colrowspan(conn, ws); - - render_workspace(conn, ws->output, ws); - - /* Re-focus the client because cleanup_table sets the focus to the last - * focused client inside a container only. */ - set_focus(conn, last_focused, true); - - return; - } - - /* Is it 'n' or 'p' for next/previous workspace? (nw) */ - if ((command[0] == 'n' || command[0] == 'p') && command[1] == 'w') { - next_previous_workspace(conn, command[0]); - return; - } - - /* It’s a normal */ - char *rest = NULL; - enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS; - direction_t direction; - int times = strtol(command, &rest, 10); - if (rest == NULL) { - ELOG("Invalid command (\"%s\")\n", command); - return; - } - - if (*rest == '\0') { - /* No rest? This was a workspace number, not a times specification */ - workspace_show(conn, times); - return; - } - - if (*rest == 'm' || *rest == 's') { - action = (*rest == 'm' ? ACTION_MOVE : ACTION_SNAP); - rest++; - } - - int workspace = strtol(rest, &rest, 10); - - if (rest == NULL) { - ELOG("Invalid command (\"%s\")\n", command); - return; - } - - if (*rest == '\0') { - if (last_focused != NULL && client_is_floating(last_focused)) - move_floating_window_to_workspace(conn, last_focused, workspace); - else move_current_window_to_workspace(conn, workspace); - return; - } - - if (last_focused == NULL) { - ELOG("Not performing (no window found)\n"); - return; - } - - if (client_is_floating(last_focused) && - (action != ACTION_FOCUS && action != ACTION_MOVE)) { - ELOG("Not performing (floating)\n"); - return; - } - - /* Now perform action to */ - while (*rest != '\0') { - if (*rest == 'h') - direction = D_LEFT; - else if (*rest == 'j') - direction = D_DOWN; - else if (*rest == 'k') - direction = D_UP; - else if (*rest == 'l') - direction = D_RIGHT; - else { - ELOG("unknown direction: %c\n", *rest); - return; - } - rest++; - - if (action == ACTION_FOCUS) { - if (with == WITH_SCREEN) { - focus_thing(conn, direction, THING_SCREEN); - continue; - } - if (client_is_floating(last_focused)) { - floating_focus_direction(conn, last_focused, direction); - continue; - } - focus_thing(conn, direction, (with == WITH_WINDOW ? THING_WINDOW : THING_CONTAINER)); - continue; - } - - if (action == ACTION_MOVE) { - if (with == WITH_SCREEN) { - /* TODO: this should swap the screen’s contents - * (e.g. all workspaces) with the next/previous/… - * screen */ - ELOG("Not yet implemented\n"); - continue; - } - if (client_is_floating(last_focused)) { - floating_move(conn, last_focused, direction); - continue; - } - if (with == WITH_WINDOW) - move_current_window(conn, direction); - else move_current_container(conn, direction); - continue; - } - - if (action == ACTION_SNAP) { - if (with == WITH_SCREEN) { - ELOG("You cannot snap a screen (it makes no sense).\n"); - continue; - } - snap_current_container(conn, direction); - continue; - } - } -} diff --git a/src/container.c b/src/container.c deleted file mode 100644 index 8533fd49..00000000 --- a/src/container.c +++ /dev/null @@ -1,43 +0,0 @@ -/* - * vim:ts=8:expandtab - * - * i3 - an improved dynamic tiling window manager - * - * © 2009 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - */ - -#include "data.h" -#include "log.h" - -/* - * Returns the mode of the given container (or MODE_DEFAULT if a NULL pointer - * was passed in order to save a few explicit checks in other places). If - * for_frame was set to true, the special case of having exactly one client - * in a container is handled so that MODE_DEFAULT is returned. For some parts - * of the rendering, this is interesting, other parts need the real mode. - * - */ -int container_mode(Container *con, bool for_frame) { - int num_clients = 0; - Client *client; - - if (con == NULL || con->mode == MODE_DEFAULT) - return MODE_DEFAULT; - - if (!for_frame) - return con->mode; - - CIRCLEQ_FOREACH(client, &(con->clients), clients) - num_clients++; - - /* If the container contains only one client, mode is irrelevant */ - if (num_clients == 1) { - DLOG("mode to default\n"); - return MODE_DEFAULT; - } - - return con->mode; -} diff --git a/src/layout.c b/src/layout.c deleted file mode 100644 index b1338040..00000000 --- a/src/layout.c +++ /dev/null @@ -1,779 +0,0 @@ -/* - * vim:ts=8:expandtab - * - * i3 - an improved dynamic tiling window manager - * - * © 2009-2010 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - * layout.c: Functions handling layout/drawing of window decorations - * - */ -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "i3.h" -#include "xcb.h" -#include "table.h" -#include "util.h" -#include "randr.h" -#include "layout.h" -#include "client.h" -#include "floating.h" -#include "handlers.h" -#include "workspace.h" -#include "log.h" -#include "container.h" - -/* - * Gets the unoccupied space (= space which is available for windows which were resized by the user) - * for the given row. This is necessary to render both, customly resized windows and never touched - * windows correctly, meaning that the aspect ratio will be maintained when opening new windows. - * - */ -int get_unoccupied_x(Workspace *workspace) { - double unoccupied = workspace->rect.width; - double default_factor = ((float)workspace->rect.width / workspace->cols) / workspace->rect.width; - - DLOG("get_unoccupied_x(), starting with %f, default_factor = %f\n", unoccupied, default_factor); - - for (int cols = 0; cols < workspace->cols; cols++) { - DLOG("width_factor[%d] = %f, unoccupied = %f\n", cols, workspace->width_factor[cols], unoccupied); - - if (workspace->width_factor[cols] == 0) - unoccupied -= workspace->rect.width * default_factor; - } - - DLOG("unoccupied space: %f\n", unoccupied); - return unoccupied; -} - -/* See get_unoccupied_x() */ -int get_unoccupied_y(Workspace *workspace) { - int height = workspace_height(workspace); - double unoccupied = height; - double default_factor = ((float)height / workspace->rows) / height; - - DLOG("get_unoccupied_y(), starting with %f, default_factor = %f\n", unoccupied, default_factor); - - for (int rows = 0; rows < workspace->rows; rows++) { - DLOG("height_factor[%d] = %f, unoccupied = %f\n", rows, workspace->height_factor[rows], unoccupied); - if (workspace->height_factor[rows] == 0) - unoccupied -= height * default_factor; - } - - DLOG("unoccupied space: %f\n", unoccupied); - return unoccupied; -} - -/* - * Redecorates the given client correctly by checking if it’s in a stacking container and - * re-rendering the stack window or just calling decorate_window if it’s not in a stacking - * container. - * - */ -void redecorate_window(xcb_connection_t *conn, Client *client) { - if (client->container != NULL && - (client->container->mode == MODE_STACK || - client->container->mode == MODE_TABBED)) { - render_container(conn, client->container); - /* We clear the frame to generate exposure events, because the color used - in drawing may be different */ - xcb_clear_area(conn, true, client->frame, 0, 0, client->rect.width, client->rect.height); - } else decorate_window(conn, client, client->frame, client->titlegc, 0, 0); - xcb_flush(conn); -} - -/* - * (Re-)draws window decorations for a given Client onto the given drawable/graphic context. - * When in stacking mode, the window decorations are drawn onto an own window. - * - */ -void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable, - xcb_gcontext_t gc, int offset_x, int offset_y) { - i3Font *font = load_font(conn, config.font); - int decoration_height = font->height + 2 + 2; - struct Colortriple *color; - Client *last_focused; - - /* Clients without a container (docks) won’t get decorated */ - if (client->dock) - return; - - last_focused = SLIST_FIRST(&(client->workspace->focus_stack)); - /* Is the window urgent? */ - if (client->urgent) - color = &(config.client.urgent); - else { - if (client_is_floating(client)) { - if (last_focused == client) - color = &(config.client.focused); - else color = &(config.client.unfocused); - } else { - if (client->container->currently_focused == client) { - /* Distinguish if the window is currently focused… */ - if (last_focused == client && c_ws == client->workspace) - color = &(config.client.focused); - /* …or if it is the focused window in a not focused container */ - else color = &(config.client.focused_inactive); - } else color = &(config.client.unfocused); - } - } - - /* Our plan is the following: - - Draw a rect around the whole client in color->background - - Draw two lines in a lighter color - - Draw the window’s title - */ - int mode = container_mode(client->container, true); - - /* Draw a rectangle in background color around the window */ - if (client->borderless && mode == MODE_DEFAULT) - xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); - else xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background); - - /* In stacking mode, we only render the rect for this specific decoration */ - if (mode == MODE_STACK || mode == MODE_TABBED) { - /* We need to use the container’s width because it is the more recent value - when - in stacking mode, clients get reconfigured only on demand (the not active client - is not reconfigured), so the client’s rect.width would be wrong */ - xcb_rectangle_t rect = {offset_x, offset_y, - offset_x + client->container->width, - offset_y + decoration_height }; - xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); - } else { - xcb_rectangle_t rect = {0, 0, client->rect.width, client->rect.height}; - xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); - - /* Draw the inner background to have a black frame around clients (such as mplayer) - which cannot be resized exactly in our frames and therefore are centered */ - xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); - if (client->titlebar_position == TITLEBAR_OFF && client->borderless) { - xcb_rectangle_t crect = {0, 0, client->rect.width, client->rect.height}; - xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); - } else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) { - xcb_rectangle_t crect = {1, 1, client->rect.width - (1 + 1), client->rect.height - (1 + 1)}; - xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); - } else { - xcb_rectangle_t crect = {2, decoration_height, - client->rect.width - (2 + 2), client->rect.height - 2 - decoration_height}; - xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); - } - } - - mode = container_mode(client->container, false); - - if (client->titlebar_position != TITLEBAR_OFF) { - /* Draw the lines */ - xcb_draw_line(conn, drawable, gc, color->border, offset_x, offset_y, offset_x + client->rect.width, offset_y); - xcb_draw_line(conn, drawable, gc, color->border, - offset_x + 2, /* x */ - offset_y + font->height + 3, /* y */ - offset_x + client->rect.width - 3, /* to_x */ - offset_y + font->height + 3 /* to_y */); - } - - /* If the client has a title, we draw it */ - if (client->name != NULL && - (mode != MODE_DEFAULT || client->titlebar_position != TITLEBAR_OFF)) { - /* Draw the font */ - uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; - uint32_t values[] = { color->text, color->background, font->id }; - xcb_change_gc(conn, gc, mask, values); - - /* name_len == -1 means this is a legacy application which does not specify _NET_WM_NAME, - and we don’t handle the old window name (COMPOUND_TEXT) but only _NET_WM_NAME, which - is UTF-8 */ - if (client->name_len == -1) - xcb_image_text_8(conn, strlen(client->name), drawable, gc, offset_x + 3 /* X */, - offset_y + font->height /* Y = baseline of font */, client->name); - else - xcb_image_text_16(conn, client->name_len, drawable, gc, offset_x + 3 /* X */, - offset_y + font->height /* Y = baseline of font */, (xcb_char2b_t*)client->name); - } -} - -/* - * Pushes the client’s x and y coordinates to X11 - * - */ -void reposition_client(xcb_connection_t *conn, Client *client) { - Output *output; - - DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); - /* Note: We can use a pointer to client->x like an array of uint32_ts - because it is followed by client->y by definition */ - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x)); - - if (!client_is_floating(client)) - return; - - /* If the client is floating, we need to check if we moved it to a different workspace */ - output = get_output_containing(client->rect.x + (client->rect.width / 2), - client->rect.y + (client->rect.height / 2)); - if (client->workspace->output == output) - return; - - if (output == NULL) { - DLOG("Boundary checking disabled, no output found for (%d, %d)\n", client->rect.x, client->rect.y); - return; - } - - if (output->current_workspace == NULL) { - DLOG("Boundary checking deferred, no current workspace on output\n"); - client->force_reconfigure = true; - return; - } - - DLOG("Client is on workspace %p with output %p\n", client->workspace, client->workspace->output); - DLOG("but output at %d, %d is %p\n", client->rect.x, client->rect.y, output); - floating_assign_to_workspace(client, output->current_workspace); - - set_focus(conn, client, true); -} - -/* - * Pushes the client’s width/height to X11 and resizes the child window. This - * function also updates the client’s position, so if you work on tiling clients - * only, you can use this function instead of separate calls to reposition_client - * and resize_client to reduce flickering. - * - */ -void resize_client(xcb_connection_t *conn, Client *client) { - i3Font *font = load_font(conn, config.font); - - DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); - DLOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height); - xcb_set_window_rect(conn, client->frame, client->rect); - - /* Adjust the position of the child inside its frame. - * The coordinates of the child are relative to its frame, we - * add a border of 2 pixel to each value */ - Rect *rect = &(client->child_rect); - switch (container_mode(client->container, true)) { - case MODE_STACK: - case MODE_TABBED: - rect->x = 2; - rect->y = 0; - rect->width = client->rect.width - (2 + 2); - rect->height = client->rect.height - 2; - break; - default: - if (client->titlebar_position == TITLEBAR_OFF && client->borderless) { - rect->x = 0; - rect->y = 0; - rect->width = client->rect.width; - rect->height = client->rect.height; - } else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) { - rect->x = 1; - rect->y = 1; - rect->width = client->rect.width - 1 - 1; - rect->height = client->rect.height - 1 - 1; - } else { - rect->x = 2; - rect->y = font->height + 2 + 2; - rect->width = client->rect.width - (2 + 2); - rect->height = client->rect.height - ((font->height + 2 + 2) + 2); - } - break; - } - - rect->width -= (2 * client->border_width); - rect->height -= (2 * client->border_width); - - /* Obey the ratio, if any */ - if (client->proportional_height != 0 && - client->proportional_width != 0) { - DLOG("proportional height = %d, width = %d\n", client->proportional_height, client->proportional_width); - double new_height = rect->height + 1; - int new_width = rect->width; - - while (new_height > rect->height) { - new_height = ((double)client->proportional_height / client->proportional_width) * new_width; - - if (new_height > rect->height) - new_width--; - } - /* Center the window */ - rect->y += ceil(rect->height / 2) - floor(new_height / 2); - rect->x += ceil(rect->width / 2) - floor(new_width / 2); - - rect->height = new_height; - rect->width = new_width; - DLOG("new_height = %f, new_width = %d\n", new_height, new_width); - } - - if (client->height_increment > 1) { - int old_height = rect->height; - rect->height -= (rect->height - client->base_height) % client->height_increment; - DLOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n", - old_height - rect->height, client->height_increment, client->base_height); - } - - if (client->width_increment > 1) { - int old_width = rect->width; - rect->width -= (rect->width - client->base_width) % client->width_increment; - DLOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n", - old_width - rect->width, client->width_increment, client->base_width); - } - - DLOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height); - - xcb_set_window_rect(conn, client->child, *rect); - - /* After configuring a child window we need to fake a configure_notify_event (see ICCCM 4.2.3). - * This is necessary to inform the client of its position relative to the root window, - * not relative to its frame (as done in the configure_notify_event by the x server). */ - fake_absolute_configure_notify(conn, client); - - /* Force redrawing after resizing the window because any now lost - * pixels could contain old garbage. */ - xcb_expose_event_t generated; - generated.window = client->frame; - generated.count = 0; - handle_expose_event(NULL, conn, &generated); -} - -/* - * Renders the given container. Is called by render_layout() or individually (for example - * when focus changes in a stacking container) - * - */ -void render_container(xcb_connection_t *conn, Container *container) { - Client *client; - int num_clients = 0, current_client = 0; - - CIRCLEQ_FOREACH(client, &(container->clients), clients) - num_clients++; - - if (container->mode == MODE_DEFAULT) { - int height = (container->height / max(1, num_clients)); - int rest_pixels = (container->height % max(1, num_clients)); - DLOG("height per client = %d, rest = %d\n", height, rest_pixels); - - CIRCLEQ_FOREACH(client, &(container->clients), clients) { - /* If the client is in fullscreen mode, it does not get reconfigured */ - if (container->workspace->fullscreen_client == client) { - current_client++; - continue; - } - - /* If we have some pixels left to distribute, add one - * pixel to each client as long as possible. */ - int this_height = height; - if (rest_pixels > 0) { - height++; - rest_pixels--; - } - /* Check if we changed client->x or client->y by updating it. - * Note the bitwise OR instead of logical OR to force evaluation of both statements */ - if (client->force_reconfigure | - update_if_necessary(&(client->rect.x), container->x) | - update_if_necessary(&(client->rect.y), container->y + - (container->height / num_clients) * current_client) | - update_if_necessary(&(client->rect.width), container->width) | - update_if_necessary(&(client->rect.height), this_height)) - resize_client(conn, client); - - /* TODO: vertical default layout */ - - client->force_reconfigure = false; - - current_client++; - } - } else { - i3Font *font = load_font(conn, config.font); - int decoration_height = (font->height + 2 + 2); - struct Stack_Window *stack_win = &(container->stack_win); - /* The size for each tab (width), necessary as a separate variable - * because num_clients gets fixed to 1 in tabbed mode. */ - int size_each = (num_clients == 0 ? container->width : container->width / num_clients); - int stack_lines = num_clients; - - /* Check if we need to remap our stack title window, it gets unmapped when the container - is empty in src/handlers.c:unmap_notify() */ - if (stack_win->rect.height == 0 && num_clients > 1) { - DLOG("remapping stack win\n"); - xcb_map_window(conn, stack_win->window); - } else DLOG("not remapping stackwin, height = %d, num_clients = %d\n", - stack_win->rect.height, num_clients); - - if (container->mode == MODE_TABBED) { - /* By setting num_clients to 1 we force that the stack window will be only one line - * high. The rest of the code is useful in both cases. */ - DLOG("tabbed mode, setting num_clients = 1\n"); - if (stack_lines > 1) - stack_lines = 1; - } - - if (container->stack_limit == STACK_LIMIT_COLS) { - stack_lines = ceil((float)num_clients / container->stack_limit_value); - } else if (container->stack_limit == STACK_LIMIT_ROWS) { - stack_lines = min(num_clients, container->stack_limit_value); - } - - int height = decoration_height * stack_lines; - if (num_clients == 1) { - height = 0; - stack_win->rect.height = 0; - xcb_unmap_window(conn, stack_win->window); - - DLOG("Just one client, setting height to %d\n", height); - } - - /* Check if we need to reconfigure our stack title window */ - if (height > 0 && ( - update_if_necessary(&(stack_win->rect.x), container->x) | - update_if_necessary(&(stack_win->rect.y), container->y) | - update_if_necessary(&(stack_win->rect.width), container->width) | - update_if_necessary(&(stack_win->rect.height), height))) { - - /* Configuration can happen in two slightly different ways: - - If there is no client in fullscreen mode, 5 parameters are passed - (x, y, width, height, stack mode is set to above which means top-most position). - - If there is a fullscreen client, the fourth parameter is set to to the - fullscreen window as sibling and the stack mode is set to below, which means - that the stack_window will be placed just below the sibling, that is, under - the fullscreen window. - */ - uint32_t values[] = { stack_win->rect.x, stack_win->rect.y, - stack_win->rect.width, stack_win->rect.height, - XCB_STACK_MODE_ABOVE, XCB_STACK_MODE_BELOW }; - uint32_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | - XCB_CONFIG_WINDOW_STACK_MODE; - - /* Raise the stack window, but keep it below the first floating client - * and below the fullscreen client (if any) */ - Client *first_floating = TAILQ_FIRST(&(container->workspace->floating_clients)); - if (first_floating != TAILQ_END(&(container->workspace->floating_clients))) { - mask |= XCB_CONFIG_WINDOW_SIBLING; - values[4] = first_floating->frame; - } else if (container->workspace->fullscreen_client != NULL) { - mask |= XCB_CONFIG_WINDOW_SIBLING; - values[4] = container->workspace->fullscreen_client->frame; - } - - xcb_configure_window(conn, stack_win->window, mask, values); - } - - /* Prepare the pixmap for usage */ - if (num_clients > 1) - cached_pixmap_prepare(conn, &(stack_win->pixmap)); - - int current_row = 0, current_col = 0; - int wrap = 0; - - if (container->stack_limit == STACK_LIMIT_COLS) { - /* wrap stores the number of rows after which we will - * wrap to a new column. */ - wrap = ceil((float)num_clients / container->stack_limit_value); - } else if (container->stack_limit == STACK_LIMIT_ROWS) { - /* When limiting rows, the wrap variable serves a - * slightly different purpose: it holds the number of - * pixels which each client will get. This is constant - * during the following loop, so it saves us some - * divisions and ceil()ing. */ - wrap = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value)); - } - - /* Render the decorations of all clients */ - CIRCLEQ_FOREACH(client, &(container->clients), clients) { - /* If the client is in fullscreen mode, it does not get reconfigured */ - if (container->workspace->fullscreen_client == client) { - current_client++; - continue; - } - - /* Check if we changed client->x or client->y by updating it. - * Note the bitwise OR instead of logical OR to force evaluation of all statements */ - if (client->force_reconfigure | - update_if_necessary(&(client->rect.x), container->x) | - update_if_necessary(&(client->rect.y), container->y + height) | - update_if_necessary(&(client->rect.width), container->width) | - update_if_necessary(&(client->rect.height), container->height - height)) - resize_client(conn, client); - - client->force_reconfigure = false; - - int offset_x = 0; - int offset_y = 0; - if (container->mode == MODE_STACK || - (container->mode == MODE_TABBED && - container->stack_limit == STACK_LIMIT_COLS)) { - if (container->stack_limit == STACK_LIMIT_COLS) { - offset_x = current_col * (stack_win->rect.width / container->stack_limit_value); - offset_y = current_row * decoration_height; - current_row++; - if ((current_row % wrap) == 0) { - current_col++; - current_row = 0; - } - } else if (container->stack_limit == STACK_LIMIT_ROWS) { - offset_x = current_col * wrap; - offset_y = current_row * decoration_height; - current_row++; - if ((current_row % container->stack_limit_value) == 0) { - current_col++; - current_row = 0; - } - } else { - offset_y = current_client * decoration_height; - } - current_client++; - } else if (container->mode == MODE_TABBED) { - if (container->stack_limit == STACK_LIMIT_ROWS) { - LOG("You limited a tabbed container in its rows. " - "This makes no sense in tabbing mode.\n"); - } - offset_x = current_client++ * size_each; - } - if (stack_win->pixmap.id == XCB_NONE) - continue; - decorate_window(conn, client, stack_win->pixmap.id, - stack_win->pixmap.gc, offset_x, offset_y); - } - - /* Check if we need to fill one column because of an uneven - * amount of windows */ - if (container->mode == MODE_STACK) { - if (container->stack_limit == STACK_LIMIT_COLS && (current_col % 2) != 0) { - xcb_change_gc_single(conn, stack_win->pixmap.gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); - - int offset_x = current_col * (stack_win->rect.width / container->stack_limit_value); - int offset_y = current_row * decoration_height; - xcb_rectangle_t rect = {offset_x, offset_y, - offset_x + container->width, - offset_y + decoration_height }; - xcb_poly_fill_rectangle(conn, stack_win->pixmap.id, stack_win->pixmap.gc, 1, &rect); - } else if (container->stack_limit == STACK_LIMIT_ROWS && (current_row % 2) != 0) { - xcb_change_gc_single(conn, stack_win->pixmap.gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); - - int offset_x = current_col * wrap; - int offset_y = current_row * decoration_height; - xcb_rectangle_t rect = {offset_x, offset_y, - offset_x + container->width, - offset_y + decoration_height }; - xcb_poly_fill_rectangle(conn, stack_win->pixmap.id, stack_win->pixmap.gc, 1, &rect); - } - } - - if (stack_win->pixmap.id == XCB_NONE) - return; - xcb_copy_area(conn, stack_win->pixmap.id, stack_win->window, stack_win->pixmap.gc, - 0, 0, 0, 0, stack_win->rect.width, stack_win->rect.height); - } -} - -static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) { - Client *client; - SLIST_FOREACH(client, &(r_ws->output->dock_clients), dock_clients) { - DLOG("client is at %d, should be at %d\n", client->rect.y, *height); - if (client->force_reconfigure | - update_if_necessary(&(client->rect.x), r_ws->rect.x) | - update_if_necessary(&(client->rect.y), *height)) - reposition_client(conn, client); - - if (client->force_reconfigure | - update_if_necessary(&(client->rect.width), width) | - update_if_necessary(&(client->rect.height), client->desired_height)) - resize_client(conn, client); - - client->force_reconfigure = false; - DLOG("desired_height = %d\n", client->desired_height); - *height += client->desired_height; - } -} - -static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int width, int height) { - i3Font *font = load_font(conn, config.font); - Output *output = r_ws->output; - enum { SET_NORMAL = 0, SET_FOCUSED = 1 }; - - /* Fill the whole bar in black */ - xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); - xcb_rectangle_t rect = {0, 0, width, height}; - xcb_poly_fill_rectangle(conn, output->bar, output->bargc, 1, &rect); - - /* Set font */ - xcb_change_gc_single(conn, output->bargc, XCB_GC_FONT, font->id); - - int drawn = 0; - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->output != output) - continue; - - struct Colortriple *color; - - if (output->current_workspace == ws) - color = &(config.bar.focused); - else if (ws->urgent) - color = &(config.bar.urgent); - else color = &(config.bar.unfocused); - - /* Draw the outer rect */ - xcb_draw_rect(conn, output->bar, output->bargc, color->border, - drawn, /* x */ - 1, /* y */ - ws->text_width + 5 + 5, /* width = text width + 5 px left + 5px right */ - height - 2 /* height = max. height - 1 px upper and 1 px bottom border */); - - /* Draw the background of this rect */ - xcb_draw_rect(conn, output->bar, output->bargc, color->background, - drawn + 1, - 2, - ws->text_width + 4 + 4, - height - 4); - - xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, color->text); - xcb_change_gc_single(conn, output->bargc, XCB_GC_BACKGROUND, color->background); - xcb_image_text_16(conn, ws->name_len, output->bar, output->bargc, drawn + 5 /* X */, - font->height + 1 /* Y = baseline of font */, - (xcb_char2b_t*)ws->name); - drawn += ws->text_width + 12; - } -} - -/* - * Modifies the event mask of all clients on the given workspace to either ignore or to handle - * enter notifies. It is handy to ignore notifies because they will be sent when a window is mapped - * under the cursor, thus when the user didn’t enter the window actively at all. - * - */ -void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bool ignore_enter_notify) { - Client *client; - uint32_t values[1]; - - FOR_TABLE(workspace) { - if (workspace->table[cols][rows] == NULL) - continue; - - CIRCLEQ_FOREACH(client, &(workspace->table[cols][rows]->clients), clients) { - /* Change event mask for the decorations */ - values[0] = FRAME_EVENT_MASK; - if (ignore_enter_notify) - values[0] &= ~(XCB_EVENT_MASK_ENTER_WINDOW); - xcb_change_window_attributes(conn, client->frame, XCB_CW_EVENT_MASK, values); - - /* Change event mask for the child itself */ - values[0] = CHILD_EVENT_MASK; - if (ignore_enter_notify) - values[0] &= ~(XCB_EVENT_MASK_ENTER_WINDOW); - xcb_change_window_attributes(conn, client->child, XCB_CW_EVENT_MASK, values); - } - } -} - -/* - * Renders the given workspace on the given screen - * - */ -void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) { - i3Font *font = load_font(conn, config.font); - int width = r_ws->rect.width; - int height = r_ws->rect.height; - - /* Reserve space for dock clients */ - Client *client; - SLIST_FOREACH(client, &(output->dock_clients), dock_clients) - height -= client->desired_height; - - /* Space for the internal bar */ - if (!config.disable_workspace_bar) - height -= (font->height + 6); - - int xoffset[r_ws->rows]; - int yoffset[r_ws->cols]; - /* Initialize offsets */ - for (int cols = 0; cols < r_ws->cols; cols++) - yoffset[cols] = r_ws->rect.y; - for (int rows = 0; rows < r_ws->rows; rows++) - xoffset[rows] = r_ws->rect.x; - - ignore_enter_notify_forall(conn, r_ws, true); - - /* Go through the whole table and render what’s necessary */ - FOR_TABLE(r_ws) { - Container *container = r_ws->table[cols][rows]; - if (container == NULL) - continue; - int single_width = -1, single_height = -1; - /* Update position of the container */ - container->row = rows; - container->col = cols; - container->x = xoffset[rows]; - container->y = yoffset[cols]; - container->width = 0; - - for (int c = 0; c < container->colspan; c++) { - if (r_ws->width_factor[cols+c] == 0) - container->width += (width / r_ws->cols); - else container->width += get_unoccupied_x(r_ws) * r_ws->width_factor[cols+c]; - - if (single_width == -1) - single_width = container->width; - } - - DLOG("height is %d\n", height); - - container->height = 0; - - for (int c = 0; c < container->rowspan; c++) { - if (r_ws->height_factor[rows+c] == 0) - container->height += (height / r_ws->rows); - else container->height += get_unoccupied_y(r_ws) * r_ws->height_factor[rows+c]; - - if (single_height == -1) - single_height = container->height; - } - - /* Render the container if it is not empty */ - render_container(conn, container); - - xoffset[rows] += single_width; - yoffset[cols] += single_height; - } - - /* Reposition all floating clients with force_reconfigure == true */ - TAILQ_FOREACH(client, &(r_ws->floating_clients), floating_clients) { - if (!client->force_reconfigure) - continue; - - client->force_reconfigure = false; - reposition_client(conn, client); - resize_client(conn, client); - } - - ignore_enter_notify_forall(conn, r_ws, false); - - render_bars(conn, r_ws, width, &height); - if (!config.disable_workspace_bar) - render_internal_bar(conn, r_ws, width, font->height + 6); -} - -/* - * Renders the whole layout, that is: Go through each screen, each workspace, each container - * and render each client. This also renders the bars. - * - * If you don’t need to render *everything*, you should call render_container on the container - * you want to refresh. - * - */ -void render_layout(xcb_connection_t *conn) { - Output *output; - - TAILQ_FOREACH(output, &outputs, outputs) - if (output->current_workspace != NULL) - render_workspace(conn, output, output->current_workspace); - - xcb_flush(conn); -} diff --git a/src/nc.c b/src/main.c similarity index 100% rename from src/nc.c rename to src/main.c diff --git a/src/mainx.c b/src/mainx.c deleted file mode 100644 index e779361b..00000000 --- a/src/mainx.c +++ /dev/null @@ -1,600 +0,0 @@ -/* - * vim:ts=8:expandtab - * - * i3 - an improved dynamic tiling window manager - * - * © 2009-2010 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "config.h" -#include "data.h" -#include "debug.h" -#include "handlers.h" -#include "click.h" -#include "i3.h" -#include "layout.h" -#include "queue.h" -#include "table.h" -#include "util.h" -#include "xcb.h" -#include "randr.h" -#include "xinerama.h" -#include "manage.h" -#include "ipc.h" -#include "log.h" -#include "sighandler.h" - -static int xkb_event_base; - -int xkb_current_group; - -xcb_connection_t *global_conn; - -/* This is the path to i3, copied from argv[0] when starting up */ -char **start_argv; - -/* This is our connection to X11 for use with XKB */ -Display *xkbdpy; - -xcb_key_symbols_t *keysyms; - -/* The list of key bindings */ -struct bindings_head *bindings; - -/* The list of exec-lines */ -struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts); - -/* The list of assignments */ -struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments); - -/* This is a list of Stack_Windows, global, for easier/faster access on expose events */ -struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins); - -/* The event handlers need to be global because they are accessed by our custom event handler - in handle_button_press(), needed for graphical resizing */ -xcb_event_handlers_t evenths; -xcb_atom_t atoms[NUM_ATOMS]; - -xcb_window_t root; -int num_screens = 0; - -/* The depth of the root screen (used e.g. for creating new pixmaps later) */ -uint8_t root_depth; - -/* We hope that XKB is supported and set this to false */ -bool xkb_supported = true; - -/* - * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb. - * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop - * - */ -static void xcb_got_event(EV_P_ struct ev_io *w, int revents) { - /* empty, because xcb_prepare_cb and xcb_check_cb are used */ -} - -/* - * Flush before blocking (and waiting for new events) - * - */ -static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) { - xcb_flush(evenths.c); -} - -/* - * Instead of polling the X connection socket we leave this to - * xcb_poll_for_event() which knows better than we can ever know. - * - */ -static void xcb_check_cb(EV_P_ ev_check *w, int revents) { - xcb_generic_event_t *event; - - while ((event = xcb_poll_for_event(evenths.c)) != NULL) { - xcb_event_handle(&evenths, event); - free(event); - } -} - -/* - * When using xmodmap to change the keyboard mapping, this event - * is only sent via XKB. Therefore, we need this special handler. - * - */ -static void xkb_got_event(EV_P_ struct ev_io *w, int revents) { - DLOG("Handling XKB event\n"); - XkbEvent ev; - - /* When using xmodmap, every change (!) gets an own event. - * Therefore, we just read all events and only handle the - * mapping_notify once. */ - bool mapping_changed = false; - while (XPending(xkbdpy)) { - XNextEvent(xkbdpy, (XEvent*)&ev); - /* While we should never receive a non-XKB event, - * better do sanity checking */ - if (ev.type != xkb_event_base) - continue; - - if (ev.any.xkb_type == XkbMapNotify) { - mapping_changed = true; - continue; - } - - if (ev.any.xkb_type != XkbStateNotify) { - ELOG("Unknown XKB event received (type %d)\n", ev.any.xkb_type); - continue; - } - - /* See The XKB Extension: Library Specification, section 14.1 */ - /* We check if the current group (each group contains - * two levels) has been changed. Mode_switch activates - * group XkbGroup2Index */ - if (xkb_current_group == ev.state.group) - continue; - - xkb_current_group = ev.state.group; - - if (ev.state.group == XkbGroup2Index) { - DLOG("Mode_switch enabled\n"); - grab_all_keys(global_conn, true); - } - - if (ev.state.group == XkbGroup1Index) { - DLOG("Mode_switch disabled\n"); - ungrab_all_keys(global_conn); - grab_all_keys(global_conn, false); - } - } - - if (!mapping_changed) - return; - - DLOG("Keyboard mapping changed, updating keybindings\n"); - xcb_key_symbols_free(keysyms); - keysyms = xcb_key_symbols_alloc(global_conn); - - xcb_get_numlock_mask(global_conn); - - ungrab_all_keys(global_conn); - DLOG("Re-grabbing...\n"); - translate_keysyms(); - grab_all_keys(global_conn, (xkb_current_group == XkbGroup2Index)); - DLOG("Done\n"); -} - - -int main(int argc, char *argv[], char *env[]) { - int i, screens, opt; - char *override_configpath = NULL; - bool autostart = true; - bool only_check_config = false; - bool force_xinerama = false; - xcb_connection_t *conn; - xcb_property_handlers_t prophs; - xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS]; - static struct option long_options[] = { - {"no-autostart", no_argument, 0, 'a'}, - {"config", required_argument, 0, 'c'}, - {"version", no_argument, 0, 'v'}, - {"help", no_argument, 0, 'h'}, - {"force-xinerama", no_argument, 0, 0}, - {0, 0, 0, 0} - }; - int option_index = 0; - - setlocale(LC_ALL, ""); - - /* Disable output buffering to make redirects in .xsession actually useful for debugging */ - if (!isatty(fileno(stdout))) - setbuf(stdout, NULL); - - start_argv = argv; - - while ((opt = getopt_long(argc, argv, "c:Cvahld:V", long_options, &option_index)) != -1) { - switch (opt) { - case 'a': - LOG("Autostart disabled using -a\n"); - autostart = false; - break; - case 'c': - override_configpath = sstrdup(optarg); - break; - case 'C': - LOG("Checking configuration file only (-C)\n"); - only_check_config = true; - break; - case 'v': - printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n"); - exit(EXIT_SUCCESS); - case 'V': - set_verbosity(true); - break; - case 'd': - LOG("Enabling debug loglevel %s\n", optarg); - add_loglevel(optarg); - break; - case 'l': - /* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */ - break; - case 0: - if (strcmp(long_options[option_index].name, "force-xinerama") == 0) { - force_xinerama = true; - ELOG("Using Xinerama instead of RandR. This option should be " - "avoided at all cost because it does not refresh the list " - "of screens, so you cannot configure displays at runtime. " - "Please check if your driver really does not support RandR " - "and disable this option as soon as you can.\n"); - break; - } - /* fall-through */ - default: - fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]); - fprintf(stderr, "\n"); - fprintf(stderr, "-a: disable autostart\n"); - fprintf(stderr, "-v: display version and exit\n"); - fprintf(stderr, "-V: enable verbose mode\n"); - fprintf(stderr, "-d : enable debug loglevel \n"); - fprintf(stderr, "-c : use the provided configfile instead\n"); - fprintf(stderr, "-C: check configuration file and exit\n"); - fprintf(stderr, "--force-xinerama: Use Xinerama instead of RandR. This " - "option should only be used if you are stuck with the " - "nvidia closed source driver which does not support RandR.\n"); - exit(EXIT_FAILURE); - } - } - - LOG("i3 version " I3_VERSION " starting\n"); - - /* Initialize the table data structures for each workspace */ - init_table(); - - memset(&evenths, 0, sizeof(xcb_event_handlers_t)); - memset(&prophs, 0, sizeof(xcb_property_handlers_t)); - - conn = global_conn = xcb_connect(NULL, &screens); - - if (xcb_connection_has_error(conn)) - die("Cannot open display\n"); - - load_configuration(conn, override_configpath, false); - if (only_check_config) { - LOG("Done checking configuration file. Exiting.\n"); - exit(0); - } - - /* Create the initial container on the first workspace. This used to - * be part of init_table, but since it possibly requires an X - * connection and a loaded configuration (default mode for new - * containers may be stacking, which requires a new window to be - * created), it had to be delayed. */ - expand_table_cols(TAILQ_FIRST(workspaces)); - expand_table_rows(TAILQ_FIRST(workspaces)); - - /* Place requests for the atoms we need as soon as possible */ - #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name); - - REQUEST_ATOM(_NET_SUPPORTED); - REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN); - REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK); - REQUEST_ATOM(_NET_WM_NAME); - REQUEST_ATOM(_NET_WM_STATE); - REQUEST_ATOM(_NET_WM_WINDOW_TYPE); - REQUEST_ATOM(_NET_WM_DESKTOP); - REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK); - REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DIALOG); - REQUEST_ATOM(_NET_WM_WINDOW_TYPE_UTILITY); - REQUEST_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR); - REQUEST_ATOM(_NET_WM_WINDOW_TYPE_SPLASH); - REQUEST_ATOM(_NET_WM_STRUT_PARTIAL); - REQUEST_ATOM(WM_PROTOCOLS); - REQUEST_ATOM(WM_DELETE_WINDOW); - REQUEST_ATOM(UTF8_STRING); - REQUEST_ATOM(WM_STATE); - REQUEST_ATOM(WM_CLIENT_LEADER); - REQUEST_ATOM(_NET_CURRENT_DESKTOP); - REQUEST_ATOM(_NET_ACTIVE_WINDOW); - REQUEST_ATOM(_NET_WORKAREA); - - /* TODO: this has to be more beautiful somewhen */ - int major, minor, error; - - major = XkbMajorVersion; - minor = XkbMinorVersion; - - int errBase; - - if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &xkb_event_base, &errBase, &major, &minor, &error)) == NULL) { - ELOG("ERROR: XkbOpenDisplay() failed, disabling XKB support\n"); - xkb_supported = false; - } - - if (xkb_supported) { - if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) { - fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n"); - return 1; - } - - int i1; - if (!XkbQueryExtension(xkbdpy,&i1,&xkb_event_base,&errBase,&major,&minor)) { - fprintf(stderr, "XKB not supported by X-server\n"); - return 1; - } - /* end of ugliness */ - - if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, - XkbMapNotifyMask | XkbStateNotifyMask, - XkbMapNotifyMask | XkbStateNotifyMask)) { - fprintf(stderr, "Could not set XKB event mask\n"); - return 1; - } - } - - /* Initialize event loop using libev */ - struct ev_loop *loop = ev_loop_new(0); - if (loop == NULL) - die("Could not initialize libev. Bad LIBEV_FLAGS?\n"); - - struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io)); - struct ev_io *xkb = scalloc(sizeof(struct ev_io)); - struct ev_check *xcb_check = scalloc(sizeof(struct ev_check)); - struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare)); - - ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); - ev_io_start(loop, xcb_watcher); - - if (xkb_supported) { - ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ); - ev_io_start(loop, xkb); - - /* Flush the buffer so that libev can properly get new events */ - XFlush(xkbdpy); - } - - ev_check_init(xcb_check, xcb_check_cb); - ev_check_start(loop, xcb_check); - - ev_prepare_init(xcb_prepare, xcb_prepare_cb); - ev_prepare_start(loop, xcb_prepare); - - /* Grab the server to delay any events until we enter the eventloop */ - xcb_grab_server(conn); - - xcb_event_handlers_init(conn, &evenths); - - /* DEBUG: Trap all events and print them */ - for (i = 2; i < 128; ++i) - xcb_event_set_handler(&evenths, i, handle_event, 0); - - for (i = 0; i < 256; ++i) - xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0); - - /* Expose = an Application should redraw itself, in this case it’s our titlebars. */ - xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL); - - /* Key presses are pretty obvious, I think */ - xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL); - - /* Enter window = user moved his mouse over the window */ - xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL); - - /* Button press = user pushed a mouse button over one of our windows */ - xcb_event_set_button_press_handler(&evenths, handle_button_press, NULL); - - /* Map notify = there is a new window */ - xcb_event_set_map_request_handler(&evenths, handle_map_request, &prophs); - - /* Unmap notify = window disappeared. When sent from a client, we don’t manage - it any longer. Usually, the client destroys the window shortly afterwards. */ - xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL); - - /* Destroy notify is handled the same as unmap notify */ - xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL); - - /* Configure notify = window’s configuration (geometry, stacking, …). We only need - it to set up ignore the following enter_notify events */ - xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL); - - /* Configure request = window tried to change size on its own */ - xcb_event_set_configure_request_handler(&evenths, handle_configure_request, NULL); - - /* Motion notify = user moved his cursor (over the root window and may - * cross virtual screen boundaries doing that) */ - xcb_event_set_motion_notify_handler(&evenths, handle_motion_notify, NULL); - - /* Mapping notify = keyboard mapping changed (Xmodmap), re-grab bindings */ - xcb_event_set_mapping_notify_handler(&evenths, handle_mapping_notify, NULL); - - /* Client message are sent to the root window. The only interesting client message - for us is _NET_WM_STATE, we honour _NET_WM_STATE_FULLSCREEN */ - xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL); - - /* Initialize the property handlers */ - xcb_property_handlers_init(&prophs, &evenths); - - /* Watch size hints (to obey correct aspect ratio) */ - xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL); - - /* Get the root window and set the event mask */ - xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); - root = root_screen->root; - root_depth = root_screen->root_depth; - - uint32_t mask = XCB_CW_EVENT_MASK; - uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | - XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video - projector), the root window gets a - ConfigureNotify */ - XCB_EVENT_MASK_POINTER_MOTION | - XCB_EVENT_MASK_PROPERTY_CHANGE | - XCB_EVENT_MASK_ENTER_WINDOW }; - xcb_void_cookie_t cookie; - cookie = xcb_change_window_attributes_checked(conn, root, mask, values); - check_error(conn, cookie, "Another window manager seems to be running"); - - /* Setup NetWM atoms */ - #define GET_ATOM(name) { \ - xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \ - if (!reply) { \ - ELOG("Could not get atom " #name "\n"); \ - exit(-1); \ - } \ - atoms[name] = reply->atom; \ - free(reply); \ - } - - GET_ATOM(_NET_SUPPORTED); - GET_ATOM(_NET_WM_STATE_FULLSCREEN); - GET_ATOM(_NET_SUPPORTING_WM_CHECK); - GET_ATOM(_NET_WM_NAME); - GET_ATOM(_NET_WM_STATE); - GET_ATOM(_NET_WM_WINDOW_TYPE); - GET_ATOM(_NET_WM_DESKTOP); - GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK); - GET_ATOM(_NET_WM_WINDOW_TYPE_DIALOG); - GET_ATOM(_NET_WM_WINDOW_TYPE_UTILITY); - GET_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR); - GET_ATOM(_NET_WM_WINDOW_TYPE_SPLASH); - GET_ATOM(_NET_WM_STRUT_PARTIAL); - GET_ATOM(WM_PROTOCOLS); - GET_ATOM(WM_DELETE_WINDOW); - GET_ATOM(UTF8_STRING); - GET_ATOM(WM_STATE); - GET_ATOM(WM_CLIENT_LEADER); - GET_ATOM(_NET_CURRENT_DESKTOP); - GET_ATOM(_NET_ACTIVE_WINDOW); - GET_ATOM(_NET_WORKAREA); - - xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, handle_window_type, NULL); - /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */ - - /* Watch _NET_WM_NAME (= title of the window in UTF-8) property */ - xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL); - - /* Watch WM_TRANSIENT_FOR property (to which client this popup window belongs) */ - xcb_property_set_handler(&prophs, WM_TRANSIENT_FOR, UINT_MAX, handle_transient_for, NULL); - - /* Watch WM_NAME (= title of the window in compound text) property for legacy applications */ - xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL); - - /* Watch WM_CLASS (= class of the window) */ - xcb_property_set_handler(&prophs, WM_CLASS, 128, handle_windowclass_change, NULL); - - /* Watch WM_CLIENT_LEADER (= logical parent window for toolbars etc.) */ - xcb_property_set_handler(&prophs, atoms[WM_CLIENT_LEADER], UINT_MAX, handle_clientleader_change, NULL); - - /* Watch WM_HINTS (contains the urgent property) */ - xcb_property_set_handler(&prophs, WM_HINTS, UINT_MAX, handle_hints, NULL); - - /* Set up the atoms we support */ - check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], - ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED"); - /* Set up the window manager’s name */ - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root); - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3"); - - keysyms = xcb_key_symbols_alloc(conn); - - xcb_get_numlock_mask(conn); - - translate_keysyms(); - grab_all_keys(conn, false); - - int randr_base; - if (force_xinerama) { - initialize_xinerama(conn); - } else { - DLOG("Checking for XRandR...\n"); - initialize_randr(conn, &randr_base); - - xcb_event_set_handler(&evenths, - randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY, - handle_screen_change, - NULL); - } - - xcb_flush(conn); - - /* Get pointer position to see on which screen we’re starting */ - xcb_query_pointer_reply_t *reply; - if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) { - ELOG("Could not get pointer position\n"); - return 1; - } - - Output *screen = get_output_containing(reply->root_x, reply->root_y); - if (screen == NULL) { - ELOG("ERROR: No screen at %d x %d, starting on the first screen\n", - reply->root_x, reply->root_y); - screen = get_first_output(); - } - - DLOG("Starting on %p\n", screen->current_workspace); - c_ws = screen->current_workspace; - - manage_existing_windows(conn, &prophs, root); - - /* Create the UNIX domain socket for IPC */ - if (config.ipc_socket_path != NULL) { - int ipc_socket = ipc_create_socket(config.ipc_socket_path); - if (ipc_socket == -1) { - ELOG("Could not create the IPC socket, IPC disabled\n"); - } else { - struct ev_io *ipc_io = scalloc(sizeof(struct ev_io)); - ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ); - ev_io_start(loop, ipc_io); - } - } - - /* Handle the events which arrived until now */ - xcb_check_cb(NULL, NULL, 0); - - setup_signal_handler(); - - /* Ignore SIGPIPE to survive errors when an IPC client disconnects - * while we are sending him a message */ - signal(SIGPIPE, SIG_IGN); - - /* Ungrab the server to receive events and enter libev’s eventloop */ - xcb_ungrab_server(conn); - - /* Autostarting exec-lines */ - struct Autostart *exec; - if (autostart) { - TAILQ_FOREACH(exec, &autostarts, autostarts) { - LOG("auto-starting %s\n", exec->command); - start_application(exec->command); - } - } - - ev_loop(loop, 0); - - /* not reached */ - return 0; -} diff --git a/src/resize.c b/src/resize.c deleted file mode 100644 index a32e5755..00000000 --- a/src/resize.c +++ /dev/null @@ -1,323 +0,0 @@ -/* - * vim:ts=8:expandtab - * - * i3 - an improved dynamic tiling window manager - * - * © 2009-2010 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - * This file contains the functions for resizing table columns/rows because - * it’s actually lots of work, compared to the other handlers. - * - */ -#include -#include - -#include -#include - -#include "i3.h" -#include "data.h" -#include "resize.h" -#include "util.h" -#include "xcb.h" -#include "debug.h" -#include "layout.h" -#include "randr.h" -#include "config.h" -#include "floating.h" -#include "workspace.h" -#include "log.h" - -/* - * This is an ugly data structure which we need because there is no standard - * way of having nested functions (only available as a gcc extension at the - * moment, clang doesn’t support it) or blocks (only available as a clang - * extension and only on Mac OS X systems at the moment). - * - */ -struct callback_params { - resize_orientation_t orientation; - Output *screen; - xcb_window_t helpwin; - uint32_t *new_position; -}; - -DRAGGING_CB(resize_callback) { - struct callback_params *params = extra; - Output *screen = params->screen; - DLOG("new x = %d, y = %d\n", new_x, new_y); - if (params->orientation == O_VERTICAL) { - /* Check if the new coordinates are within screen boundaries */ - if (new_x > (screen->rect.x + screen->rect.width - 25) || - new_x < (screen->rect.x + 25)) - return; - - *(params->new_position) = new_x; - xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_X, params->new_position); - } else { - if (new_y > (screen->rect.y + screen->rect.height - 25) || - new_y < (screen->rect.y + 25)) - return; - - *(params->new_position) = new_y; - xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_Y, params->new_position); - } - - xcb_flush(conn); -} - -/* - * Renders the resize window between the first/second container and resizes - * the table column/row. - * - */ -int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second, - resize_orientation_t orientation, xcb_button_press_event_t *event) { - uint32_t new_position; - Output *screen = get_output_containing(event->root_x, event->root_y); - if (screen == NULL) { - ELOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y); - return 1; - } - - /* We cannot use the X root window's width_in_pixels or height_in_pixels - * attributes here since they are not updated when you configure new - * screens during runtime. Instead, we just use the most right and most - * bottom Xinerama screen and use their position + width/height to get - * the area of pixels currently in use */ - Output *most_right = get_output_most(D_RIGHT, screen), - *most_bottom = get_output_most(D_DOWN, screen); - - DLOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x); - - DLOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); - - uint32_t mask = 0; - uint32_t values[2]; - - mask = XCB_CW_OVERRIDE_REDIRECT; - values[0] = 1; - - /* Open a new window, the resizebar. Grab the pointer and move the window around - as the user moves the pointer. */ - Rect grabrect = {0, - 0, - most_right->rect.x + most_right->rect.width, - most_bottom->rect.x + most_bottom->rect.height}; - xcb_window_t grabwin = create_window(conn, grabrect, XCB_WINDOW_CLASS_INPUT_ONLY, -1, true, mask, values); - - Rect helprect; - if (orientation == O_VERTICAL) { - helprect.x = event->root_x; - helprect.y = screen->rect.y; - helprect.width = 2; - helprect.height = screen->rect.height; - new_position = event->root_x; - } else { - helprect.x = screen->rect.x; - helprect.y = event->root_y; - helprect.width = screen->rect.width; - helprect.height = 2; - new_position = event->root_y; - } - - mask = XCB_CW_BACK_PIXEL; - values[0] = config.client.focused.border; - - mask |= XCB_CW_OVERRIDE_REDIRECT; - values[1] = 1; - - xcb_window_t helpwin = create_window(conn, helprect, XCB_WINDOW_CLASS_INPUT_OUTPUT, - (orientation == O_VERTICAL ? - XCB_CURSOR_SB_H_DOUBLE_ARROW : - XCB_CURSOR_SB_V_DOUBLE_ARROW), true, mask, values); - - xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin); - - xcb_flush(conn); - - struct callback_params params = { orientation, screen, helpwin, &new_position }; - - drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback, ¶ms); - - xcb_destroy_window(conn, helpwin); - xcb_destroy_window(conn, grabwin); - xcb_flush(conn); - - int pixels; - if (orientation == O_VERTICAL) - pixels = (new_position - event->root_x); - else pixels = (new_position - event->root_y); - resize_container(conn, ws, first, second, orientation, pixels); - - return 1; -} - -/* - * Resizes a column/row by the given amount of pixels. Called by - * resize_graphical_handler (the user clicked) or parse_resize_command (the - * user issued the command) - * - */ -void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int second, - resize_orientation_t orientation, int pixels) { - - /* TODO: refactor this, both blocks are very identical */ - if (orientation == O_VERTICAL) { - int default_width = ws->rect.width / ws->cols; - int old_unoccupied_x = get_unoccupied_x(ws); - - /* We pre-calculate the unoccupied space to see if we need to adapt sizes before - * doing the resize */ - int new_unoccupied_x = old_unoccupied_x; - - if (old_unoccupied_x == 0) - old_unoccupied_x = ws->rect.width; - - if (ws->width_factor[first] == 0) - new_unoccupied_x += default_width; - - if (ws->width_factor[second] == 0) - new_unoccupied_x += default_width; - - DLOG("\n\n\n"); - DLOG("old = %d, new = %d\n", old_unoccupied_x, new_unoccupied_x); - - int cols_without_wf = 0; - int old_width, old_second_width; - for (int col = 0; col < ws->cols; col++) - if (ws->width_factor[col] == 0) - cols_without_wf++; - - DLOG("old_unoccupied_x = %d\n", old_unoccupied_x); - - DLOG("Updating first (before = %f)\n", ws->width_factor[first]); - /* Convert 0 (for default width_factor) to actual numbers */ - if (ws->width_factor[first] == 0) - old_width = (old_unoccupied_x / max(cols_without_wf, 1)); - else old_width = ws->width_factor[first] * old_unoccupied_x; - - DLOG("second (before = %f)\n", ws->width_factor[second]); - if (ws->width_factor[second] == 0) - old_second_width = (old_unoccupied_x / max(cols_without_wf, 1)); - else old_second_width = ws->width_factor[second] * old_unoccupied_x; - - DLOG("middle = %f\n", ws->width_factor[first]); - - /* If the space used for customly resized columns has changed we need to adapt the - * other customly resized columns, if any */ - if (new_unoccupied_x != old_unoccupied_x) - for (int col = 0; col < ws->cols; col++) { - if (ws->width_factor[col] == 0) - continue; - - DLOG("Updating other column (%d) (current width_factor = %f)\n", col, ws->width_factor[col]); - ws->width_factor[col] = (ws->width_factor[col] * old_unoccupied_x) / new_unoccupied_x; - DLOG("to %f\n", ws->width_factor[col]); - } - - DLOG("Updating first (before = %f)\n", ws->width_factor[first]); - /* Convert 0 (for default width_factor) to actual numbers */ - if (ws->width_factor[first] == 0) - ws->width_factor[first] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; - - DLOG("first->width = %d, pixels = %d\n", old_width, pixels); - ws->width_factor[first] *= (float)(old_width + pixels) / old_width; - DLOG("-> %f\n", ws->width_factor[first]); - - - DLOG("Updating second (before = %f)\n", ws->width_factor[second]); - if (ws->width_factor[second] == 0) - ws->width_factor[second] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; - - DLOG("middle = %f\n", ws->width_factor[second]); - DLOG("second->width = %d, pixels = %d\n", old_second_width, pixels); - ws->width_factor[second] *= (float)(old_second_width - pixels) / old_second_width; - DLOG("-> %f\n", ws->width_factor[second]); - - DLOG("new unoccupied_x = %d\n", get_unoccupied_x(ws)); - - DLOG("\n\n\n"); - } else { - int ws_height = workspace_height(ws); - int default_height = ws_height / ws->rows; - int old_unoccupied_y = get_unoccupied_y(ws); - - /* We pre-calculate the unoccupied space to see if we need to adapt sizes before - * doing the resize */ - int new_unoccupied_y = old_unoccupied_y; - - if (old_unoccupied_y == 0) - old_unoccupied_y = ws_height; - - if (ws->height_factor[first] == 0) - new_unoccupied_y += default_height; - - if (ws->height_factor[second] == 0) - new_unoccupied_y += default_height; - - int cols_without_hf = 0; - int old_height, old_second_height; - for (int row = 0; row < ws->rows; row++) - if (ws->height_factor[row] == 0) - cols_without_hf++; - - DLOG("old_unoccupied_y = %d\n", old_unoccupied_y); - - DLOG("Updating first (before = %f)\n", ws->height_factor[first]); - /* Convert 0 (for default width_factor) to actual numbers */ - if (ws->height_factor[first] == 0) - old_height = (old_unoccupied_y / max(cols_without_hf, 1)); - else old_height = ws->height_factor[first] * old_unoccupied_y; - - DLOG("second (before = %f)\n", ws->height_factor[second]); - if (ws->height_factor[second] == 0) - old_second_height = (old_unoccupied_y / max(cols_without_hf, 1)); - else old_second_height = ws->height_factor[second] * old_unoccupied_y; - - DLOG("middle = %f\n", ws->height_factor[first]); - - - DLOG("\n\n\n"); - DLOG("old = %d, new = %d\n", old_unoccupied_y, new_unoccupied_y); - - /* If the space used for customly resized columns has changed we need to adapt the - * other customly resized columns, if any */ - if (new_unoccupied_y != old_unoccupied_y) - for (int row = 0; row < ws->rows; row++) { - if (ws->height_factor[row] == 0) - continue; - - DLOG("Updating other column (%d) (current width_factor = %f)\n", row, ws->height_factor[row]); - ws->height_factor[row] = (ws->height_factor[row] * old_unoccupied_y) / new_unoccupied_y; - DLOG("to %f\n", ws->height_factor[row]); - } - - - DLOG("Updating first (before = %f)\n", ws->height_factor[first]); - /* Convert 0 (for default width_factor) to actual numbers */ - if (ws->height_factor[first] == 0) - ws->height_factor[first] = ((float)ws_height / ws->rows) / new_unoccupied_y; - - DLOG("first->width = %d, pixels = %d\n", old_height, pixels); - ws->height_factor[first] *= (float)(old_height + pixels) / old_height; - DLOG("-> %f\n", ws->height_factor[first]); - - - DLOG("Updating second (before = %f)\n", ws->height_factor[second]); - if (ws->height_factor[second] == 0) - ws->height_factor[second] = ((float)ws_height / ws->rows) / new_unoccupied_y; - DLOG("middle = %f\n", ws->height_factor[second]); - DLOG("second->width = %d, pixels = %d\n", old_second_height, pixels); - ws->height_factor[second] *= (float)(old_second_height - pixels) / old_second_height; - DLOG("-> %f\n", ws->height_factor[second]); - - DLOG("new unoccupied_y = %d\n", get_unoccupied_y(ws)); - - DLOG("\n\n\n"); - } - - render_layout(conn); -} From ff64b0db59a26a1c8a63fb552787f124d9238f60 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Jul 2010 22:23:05 +0200 Subject: [PATCH 144/867] Bugfix: Render decoration of single window in tabbed/stacked container (Thanks Fernando) --- src/layout.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/layout.c b/src/layout.c index b1338040..76343aa0 100644 --- a/src/layout.c +++ b/src/layout.c @@ -536,10 +536,11 @@ void render_container(xcb_connection_t *conn, Container *container) { } offset_x = current_client++ * size_each; } - if (stack_win->pixmap.id == XCB_NONE) - continue; - decorate_window(conn, client, stack_win->pixmap.id, - stack_win->pixmap.gc, offset_x, offset_y); + if (stack_win->pixmap.id != XCB_NONE) + decorate_window(conn, client, stack_win->pixmap.id, + stack_win->pixmap.gc, offset_x, offset_y); + else + decorate_window(conn, client, client->frame, client->titlegc, 0, 0); } /* Check if we need to fill one column because of an uneven From 1459ae6bf2d7bc104f91120ee8d97af1e5c84158 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Jul 2010 23:40:50 +0200 Subject: [PATCH 145/867] Bugfix: raise fullscreen containers before rendering their content --- src/render.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/render.c b/src/render.c index 51e0fc26..943ec895 100644 --- a/src/render.c +++ b/src/render.c @@ -54,6 +54,7 @@ void render_con(Con *con) { if (fullscreen) { LOG("got fs node: %p\n", fullscreen); fullscreen->rect = rect; + x_raise_con(fullscreen); render_con(fullscreen); return; } From 60bdf8786218ce3a15081ba80dcfaf4c2ad62cfb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Jul 2010 23:41:02 +0200 Subject: [PATCH 146/867] Bugfix: Push all following window stacking orders to X11 when the order of a single pair changed --- src/x.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/x.c b/src/x.c index 9df33e51..8e879993 100644 --- a/src/x.c +++ b/src/x.c @@ -293,12 +293,15 @@ void x_push_changes(Con *con) { x_push_node(con); LOG("-- PUSHING WINDOW STACK --\n"); + bool order_changed = false; /* X11 correctly represents the stack if we push it from bottom to top */ CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { LOG("stack: 0x%08x\n", state->id); con_state *prev = CIRCLEQ_PREV(state, state); con_state *old_prev = CIRCLEQ_PREV(state, old_state); - if ((state->initial || prev != old_prev) && prev != CIRCLEQ_END(&state_head)) { + if (prev != old_prev) + order_changed = true; + if ((state->initial || order_changed) && prev != CIRCLEQ_END(&state_head)) { state->initial = false; LOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id); uint32_t mask = 0; From 7415f1444894805d39b3f5400e887302a0fdf69f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Jul 2010 11:35:05 +0200 Subject: [PATCH 147/867] Add more documentation to functions/header files --- include/con.h | 94 +++++++++++++++++++++++++++++++++++++++++++++++- include/match.h | 10 ++++++ include/render.h | 8 +++++ include/tree.h | 60 +++++++++++++++++++++++++++++++ include/window.h | 19 ++++++++++ include/x.h | 39 ++++++++++++++++++++ src/con.c | 34 +++++++++++++++++- src/match.c | 15 ++++++++ src/tree.c | 29 ++++++++++++++- src/x.c | 12 +++++++ 10 files changed, 317 insertions(+), 3 deletions(-) diff --git a/include/con.h b/include/con.h index 5a6f0c15..f2d05dbc 100644 --- a/include/con.h +++ b/include/con.h @@ -1,24 +1,116 @@ #ifndef _CON_H #define _CON_H +/** + * Create a new container (and attach it to the given parent, if not NULL). + * This function initializes the data structures and creates the appropriate + * X11 IDs using x_con_init(). + * + */ Con *con_new(Con *parent); + +/** + * Sets input focus to the given container. Will be updated in X11 in the next + * run of x_push_changes(). + * + */ void con_focus(Con *con); + +/** + * Returns true when this node is a leaf node (has no children) + * + */ bool con_is_leaf(Con *con); + +/** + * Returns true if this node accepts a window (if the node swallows windows, + * it might already have swallowed enough and cannot hold any more). + * + */ bool con_accepts_window(Con *con); + +/** + * Gets the output container (first container with CT_OUTPUT in hierarchy) this + * node is on. + * + */ Con *con_get_output(Con *con); + +/** + * Gets the workspace container this node is on. + * + */ Con *con_get_workspace(Con *con); + +/** + * Returns the first fullscreen node below this node. + * + */ Con *con_get_fullscreen_con(Con *con); + +/** + * Returns true if the node is floating. + * + */ bool con_is_floating(Con *con); + +/** + * Returns the container with the given client window ID or NULL if no such + * container exists. + * + */ Con *con_by_window_id(xcb_window_t window); + +/** + * Returns the container with the given frame ID or NULL if no such container + * exists. + * + */ Con *con_by_frame_id(xcb_window_t frame); + +/** + * Returns the first container which wants to swallow this window + * TODO: priority + * + */ Con *con_for_window(i3Window *window, Match **store_match); + +/** + * Attaches the given container to the given parent. This happens when moving + * a container or when inserting a new container at a specific place in the + * tree. + * + */ void con_attach(Con *con, Con *parent); + +/** + * Detaches the given container from its current parent + * + */ void con_detach(Con *con); -enum { WINDOW_ADD = 0, WINDOW_REMOVE = 1 }; +/** + * Updates the percent attribute of the children of the given container. This + * function needs to be called when a window is added or removed from a + * container. + * + */ void con_fix_percent(Con *con, int action); +enum { WINDOW_ADD = 0, WINDOW_REMOVE = 1 }; + +/** + * Toggles fullscreen mode for the given container. Fullscreen mode will not be + * entered when there already is a fullscreen container on this workspace. + * + */ void con_toggle_fullscreen(Con *con); +/** + * Moves the given container to the currently focused container on the given + * workspace. + * TODO: is there a better place for this function? + * + */ void con_move_to_workspace(Con *con, Con *workspace); #endif diff --git a/include/match.h b/include/match.h index eb6aec1b..923be2e1 100644 --- a/include/match.h +++ b/include/match.h @@ -1,7 +1,17 @@ #ifndef _MATCH_H #define _MATCH_H +/** + * Check if a match is empty. This is necessary while parsing commands to see + * whether the user specified a match at all. + * + */ bool match_is_empty(Match *match); + +/** + * Check if a match data structure matches the given window. + * + */ bool match_matches_window(Match *match, i3Window *window); #endif diff --git a/include/render.h b/include/render.h index 26ae8d51..849f214e 100644 --- a/include/render.h +++ b/include/render.h @@ -5,6 +5,14 @@ #ifndef _RENDER_H #define _RENDER_H +/** + * "Renders" the given container (and its children), meaning that all rects are + * updated correctly. Note that this function does not call any xcb_* + * functions, so the changes are completely done in memory only (and + * side-effect free). As soon as you call x_push_changes(), the changes will be + * updated in X11. + * + */ void render_con(Con *con); #endif diff --git a/include/tree.h b/include/tree.h index fd393d8b..449f67ef 100644 --- a/include/tree.h +++ b/include/tree.h @@ -12,16 +12,76 @@ extern Con *focused; TAILQ_HEAD(all_cons_head, Con); extern struct all_cons_head all_cons; +/** + * Initializes the tree by creating the root node, adding all RandR outputs + * to the tree (that means randr_init() has to be called before) and + * assigning a workspace to each RandR output. + * + */ void tree_init(); + +/** + * Opens an empty container in the current container + * + */ Con *tree_open_con(Con *con); + +/** + * Splits (horizontally or vertically) the given container by creating a new + * container which contains the old one and the future ones. + * + */ void tree_split(Con *con, orientation_t orientation); + +/** + * Moves focus one level up. + * + */ void level_up(); + +/** + * Moves focus one level down. + * + */ void level_down(); + +/** + * Renders the tree, that is rendering all outputs using render_con() and + * pushing the changes to X11 using x_push_changes(). + * + */ void tree_render(); + +/** + * Closes the current container using tree_close(). + * + */ void tree_close_con(); + +/** + * Changes focus in the given way (next/previous) and given orientation + * (horizontal/vertical). + * + */ void tree_next(char way, orientation_t orientation); + +/** + * Moves the current container in the given way (next/previous) and given + * orientation (horizontal/vertical). + * + */ void tree_move(char way, orientation_t orientation); + +/** + * Closes the given container including all children + * + */ void tree_close(Con *con, bool kill_window); + +/** + * Loads tree from ~/.i3/_restart.json (used for in-place restarts). + * + */ bool tree_restore(); #endif diff --git a/include/window.h b/include/window.h index a126a36c..044bde70 100644 --- a/include/window.h +++ b/include/window.h @@ -1,8 +1,27 @@ #ifndef _WINDOW_H #define _WINDOW_H +/** + * Updates the WM_CLASS (consisting of the class and instance) for the + * given window. + * + */ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop); + +/** + * Updates the name by using _NET_WM_NAME (encoded in UTF-8) for the given + * window. Further updates using window_update_name_legacy will be ignored. + * + */ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop); + +/** + * Updates the name by using WM_NAME (encoded in COMPOUND_TEXT). We do not + * touch what the client sends us but pass it to xcb_image_text_8. To get + * proper unicode rendering, the application has to use _NET_WM_NAME (see + * window_update_name()). + * + */ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop); #endif diff --git a/include/x.h b/include/x.h index 1e564d4f..91af5014 100644 --- a/include/x.h +++ b/include/x.h @@ -5,12 +5,51 @@ #ifndef _X_H #define _X_H +/** + * Initializes the X11 part for the given container. Called exactly once for + * every container from con_new(). + * + */ void x_con_init(Con *con); + +/** + * Re-initializes the associated X window state for this container. You have + * to call this when you assign a client to an empty container to ensure that + * its state gets updated correctly. + * + */ void x_reinit(Con *con); + +/** + * Kills the window decoration associated with the given container. + * + */ void x_con_kill(Con *con); + +/** + * Kills the given X11 window using WM_DELETE_WINDOW (if supported). + * + */ void x_window_kill(xcb_window_t window); + +/** + * Draws the decoration of the given container onto its parent. + * + */ void x_draw_decoration(Con *con); + +/** + * Pushes all changes (state of each node, see x_push_node() and the window + * stack) to X11. + * + */ void x_push_changes(Con *con); + +/** + * Raises the specified container in the internal stack of X windows. The + * next call to x_push_changes() will make the change visible in X11. + * + */ void x_raise_con(Con *con); #endif diff --git a/src/con.c b/src/con.c index 1d8ca82e..dc9e0a54 100644 --- a/src/con.c +++ b/src/con.c @@ -24,7 +24,12 @@ char *colors[] = { "#aa00aa" }; - +/* + * Create a new container (and attach it to the given parent, if not NULL). + * This function initializes the data structures and creates the appropriate + * X11 IDs using x_con_init(). + * + */ Con *con_new(Con *parent) { Con *new = scalloc(sizeof(Con)); TAILQ_INSERT_TAIL(&all_cons, new, all_cons); @@ -56,6 +61,12 @@ Con *con_new(Con *parent) { return new; } +/* + * Attaches the given container to the given parent. This happens when moving + * a container or when inserting a new container at a specific place in the + * tree. + * + */ void con_attach(Con *con, Con *parent) { con->parent = parent; Con *current = TAILQ_FIRST(&(parent->focus_head)); @@ -72,6 +83,10 @@ void con_attach(Con *con, Con *parent) { TAILQ_INSERT_TAIL(&(parent->focus_head), con, focused); } +/* + * Detaches the given container from its current parent + * + */ void con_detach(Con *con) { if (con->type == CT_FLOATING_CON) { TAILQ_REMOVE(&(con->parent->floating_head), con, floating_windows); @@ -222,6 +237,11 @@ bool con_is_floating(Con *con) { return (con->floating >= FLOATING_AUTO_ON); } +/* + * Returns the container with the given client window ID or NULL if no such + * container exists. + * + */ Con *con_by_window_id(xcb_window_t window) { Con *con; TAILQ_FOREACH(con, &all_cons, all_cons) @@ -230,6 +250,11 @@ Con *con_by_window_id(xcb_window_t window) { return NULL; } +/* + * Returns the container with the given frame ID or NULL if no such container + * exists. + * + */ Con *con_by_frame_id(xcb_window_t frame) { Con *con; TAILQ_FOREACH(con, &all_cons, all_cons) @@ -286,6 +311,11 @@ void con_fix_percent(Con *con, int action) { } } +/* + * Toggles fullscreen mode for the given container. Fullscreen mode will not be + * entered when there already is a fullscreen container on this workspace. + * + */ void con_toggle_fullscreen(Con *con) { Con *workspace, *fullscreen; LOG("toggling fullscreen for %p / %s\n", con, con->name); @@ -324,6 +354,8 @@ void con_toggle_fullscreen(Con *con) { } /* + * Moves the given container to the currently focused container on the given + * workspace. * TODO: is there a better place for this function? * */ diff --git a/src/match.c b/src/match.c index 603be7bf..c384c41a 100644 --- a/src/match.c +++ b/src/match.c @@ -4,10 +4,21 @@ * i3 - an improved dynamic tiling window manager * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) * + * A "match" is a data structure which acts like a mask or expression to match + * certain windows or not. For example, when using commands, you can specify a + * command like this: [title="*Firefox*"] kill. The title member of the match + * data structure will then be filled and i3 will check each window using + * match_matches_window() to find the windows affected by this command. + * */ #include "all.h" +/* + * Check if a match is empty. This is necessary while parsing commands to see + * whether the user specified a match at all. + * + */ bool match_is_empty(Match *match) { /* we cannot simply use memcmp() because the structure is part of a * TAILQ and I don’t want to start with things like assuming that the @@ -22,6 +33,10 @@ bool match_is_empty(Match *match) { match->floating == M_ANY); } +/* + * Check if a match data structure matches the given window. + * + */ bool match_matches_window(Match *match, i3Window *window) { /* TODO: pcre, full matching, … */ if (match->class != NULL && window->class_class != NULL && strcasecmp(match->class, window->class_class) == 0) { diff --git a/src/tree.c b/src/tree.c index 29b85730..9dc777c2 100644 --- a/src/tree.c +++ b/src/tree.c @@ -10,7 +10,7 @@ struct Con *focused; struct all_cons_head all_cons = TAILQ_HEAD_INITIALIZER(all_cons); /* - * Loads tree from ~/.i3/_restart.json + * Loads tree from ~/.i3/_restart.json (used for in-place restarts). * */ bool tree_restore() { @@ -201,6 +201,10 @@ void tree_close(Con *con, bool kill_window) { con_focus(next); } +/* + * Closes the current container using tree_close(). + * + */ void tree_close_con() { assert(focused != NULL); if (focused->type == CT_WORKSPACE) { @@ -248,6 +252,10 @@ void tree_split(Con *con, orientation_t orientation) { con_attach(con, new); } +/* + * Moves focus one level up. + * + */ void level_up() { /* We can focus up to the workspace, but not any higher in the tree */ if (focused->parent->type != CT_CON && @@ -258,6 +266,10 @@ void level_up() { con_focus(focused->parent); } +/* + * Moves focus one level down. + * + */ void level_down() { /* Go down the focus stack of the current node */ Con *next = TAILQ_FIRST(&(focused->focus_head)); @@ -276,6 +288,11 @@ static void mark_unmapped(Con *con) { mark_unmapped(current); } +/* + * Renders the tree, that is rendering all outputs using render_con() and + * pushing the changes to X11 using x_push_changes(). + * + */ void tree_render() { if (croot == NULL) return; @@ -296,6 +313,11 @@ void tree_render() { printf("-- END RENDERING --\n"); } +/* + * Changes focus in the given way (next/previous) and given orientation + * (horizontal/vertical). + * + */ void tree_next(char way, orientation_t orientation) { /* 1: get the first parent with the same orientation */ Con *parent = focused->parent; @@ -333,6 +355,11 @@ void tree_next(char way, orientation_t orientation) { con_focus(next); } +/* + * Moves the current container in the given way (next/previous) and given + * orientation (horizontal/vertical). + * + */ void tree_move(char way, orientation_t orientation) { /* 1: get the first parent with the same orientation */ Con *parent = focused->parent; diff --git a/src/x.c b/src/x.c index 8e879993..8fa52e02 100644 --- a/src/x.c +++ b/src/x.c @@ -105,6 +105,10 @@ void x_reinit(Con *con) { memset(&(state->window_rect), 0, sizeof(Rect)); } +/* + * Kills the window decoration associated with the given container. + * + */ void x_con_kill(Con *con) { con_state *state; @@ -137,6 +141,10 @@ static bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom) { return result; } +/* + * Kills the given X11 window using WM_DELETE_WINDOW (if supported). + * + */ void x_window_kill(xcb_window_t window) { /* if this window does not support WM_DELETE_WINDOW, we kill it the hard way */ if (!window_supports_protocol(window, atoms[WM_DELETE_WINDOW])) { @@ -161,6 +169,10 @@ void x_window_kill(xcb_window_t window) { xcb_flush(conn); } +/* + * Draws the decoration of the given container onto its parent. + * + */ void x_draw_decoration(Con *con) { Con *parent; From 33572b8c4b33d4e095747d9ca8f546051e912018 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Jul 2010 00:54:03 +0200 Subject: [PATCH 148/867] s/con->parent/parent to make it more readable (and necessary for the next commit) --- src/tree.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/tree.c b/src/tree.c index 9dc777c2..6c49c96b 100644 --- a/src/tree.c +++ b/src/tree.c @@ -132,6 +132,8 @@ static void fix_floating_parent(Con *con, Con *vanishing) { * */ void tree_close(Con *con, bool kill_window) { + Con *parent = con->parent; + /* check floating clients and adjust old_parent if necessary */ fix_floating_parent(croot, con); @@ -139,12 +141,12 @@ void tree_close(Con *con, bool kill_window) { Con *next; if (con->type == CT_FLOATING_CON) { next = TAILQ_NEXT(con, floating_windows); - if (next == TAILQ_END(&(con->parent->floating_head))) + if (next == TAILQ_END(&(parent->floating_head))) next = con_get_workspace(con); } else { next = TAILQ_NEXT(con, focused); - if (next == TAILQ_END(&(con->parent->nodes_head))) { - next = con->parent; + if (next == TAILQ_END(&(parent->nodes_head))) { + next = parent; while (!TAILQ_EMPTY(&(next->focus_head)) && TAILQ_FIRST(&(next->focus_head)) != con) next = TAILQ_FIRST(&(next->focus_head)); @@ -176,14 +178,14 @@ void tree_close(Con *con, bool kill_window) { x_con_kill(con); con_detach(con); - con_fix_percent(con->parent, WINDOW_REMOVE); + con_fix_percent(parent, WINDOW_REMOVE); if (con_is_floating(con)) { DLOG("Container was floating, killing floating container\n"); - TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows); - TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused); - tree_close(con->parent, false); + TAILQ_REMOVE(&(parent->parent->floating_head), parent, floating_windows); + TAILQ_REMOVE(&(parent->parent->focus_head), parent, focused); + tree_close(parent, false); next = NULL; } From 09c6b587d3f57bdcfa09a23cc1db53fcc05c337f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Jul 2010 00:54:47 +0200 Subject: [PATCH 149/867] close empty parent containers, add testcase --- src/tree.c | 9 ++++++ testcases/t/30-close-empty-split.t | 48 ++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 testcases/t/30-close-empty-split.t diff --git a/src/tree.c b/src/tree.c index 6c49c96b..54e36c34 100644 --- a/src/tree.c +++ b/src/tree.c @@ -201,6 +201,15 @@ void tree_close(Con *con, bool kill_window) { DLOG("focusing %p / %s\n", next, next->name); /* TODO: check if the container (or one of its children) was focused */ con_focus(next); + + /* check if the parent container is empty now and close it */ + if (parent->type != CT_WORKSPACE && + TAILQ_EMPTY(&(parent->nodes_head))) { + DLOG("Closing empty parent container\n"); + /* TODO: check if this container would swallow any other client and + * don’t close it automatically. */ + tree_close(parent, false); + } } /* diff --git a/testcases/t/30-close-empty-split.t b/testcases/t/30-close-empty-split.t new file mode 100644 index 00000000..31d0ad14 --- /dev/null +++ b/testcases/t/30-close-empty-split.t @@ -0,0 +1,48 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Check if empty split containers are automatically closed. +# +use i3test tests => 4; +use Time::HiRes qw(sleep); + +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + +$i3->command('open')->recv; +my ($nodes, $focus) = get_ws_content($tmp); +my $first = $focus->[0]; + +$i3->command('split v')->recv; + +($nodes, $focus) = get_ws_content($tmp); + +is($nodes->[0]->{focused}, 0, 'split container not focused'); + +# focus the split container +$i3->command('level up')->recv; +($nodes, $focus) = get_ws_content($tmp); +my $split = $focus->[0]; +$i3->command('level down')->recv; + +$i3->command('open')->recv; + +($nodes, $focus) = get_ws_content($tmp); +my $second = $focus->[0]; + +isnt($first, $second, 'different container focused'); + +############################################################## +# close both windows and see if the split container still exists +############################################################## + +$i3->command('kill')->recv; +$i3->command('kill')->recv; +($nodes, $focus) = get_ws_content($tmp); +isnt($nodes->[0]->{id}, $split, 'split container closed'); + +diag( "Testing i3, Perl $], $^X" ); From 69e5c0f6ce4a1d68a7205a0046a189e0aaa2ef35 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Jul 2010 01:27:47 +0200 Subject: [PATCH 150/867] Treat stacking containers as if they are in vertical orientation, add testcase --- include/con.h | 8 +++++ src/con.c | 14 ++++++++ src/tree.c | 4 +-- testcases/t/31-stacking-order.t | 64 +++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 testcases/t/31-stacking-order.t diff --git a/include/con.h b/include/con.h index f2d05dbc..716ebfd3 100644 --- a/include/con.h +++ b/include/con.h @@ -113,4 +113,12 @@ void con_toggle_fullscreen(Con *con); */ void con_move_to_workspace(Con *con, Con *workspace); +/** + * Returns the orientation of the given container (for stacked containers, + * vertical orientation is used regardless of the actual orientation of the + * container). + * + */ +int con_orientation(Con *con); + #endif diff --git a/src/con.c b/src/con.c index dc9e0a54..a28d6633 100644 --- a/src/con.c +++ b/src/con.c @@ -376,3 +376,17 @@ void con_move_to_workspace(Con *con, Con *workspace) { con_detach(con); con_attach(con, next); } + +/* + * Returns the orientation of the given container (for stacked containers, + * vertical orientation is used regardless of the actual orientation of the + * container). + * + */ +int con_orientation(Con *con) { + /* stacking containers behave like they are in vertical orientation */ + if (con->layout == L_STACKED) + return VERT; + + return con->orientation; +} diff --git a/src/tree.c b/src/tree.c index 54e36c34..cf7f60dd 100644 --- a/src/tree.c +++ b/src/tree.c @@ -332,7 +332,7 @@ void tree_render() { void tree_next(char way, orientation_t orientation) { /* 1: get the first parent with the same orientation */ Con *parent = focused->parent; - while (parent->orientation != orientation) { + while (con_orientation(parent) != orientation) { LOG("need to go one level further up\n"); /* if the current parent is an output, we are at a workspace * and the orientation still does not match */ @@ -377,7 +377,7 @@ void tree_move(char way, orientation_t orientation) { if (focused->type == CT_WORKSPACE) return; bool level_changed = false; - while (parent->orientation != orientation) { + while (con_orientation(parent) != orientation) { LOG("need to go one level further up\n"); /* if the current parent is an output, we are at a workspace * and the orientation still does not match */ diff --git a/testcases/t/31-stacking-order.t b/testcases/t/31-stacking-order.t new file mode 100644 index 00000000..caf136fb --- /dev/null +++ b/testcases/t/31-stacking-order.t @@ -0,0 +1,64 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Check if stacking containers can be used independantly of +# the split mode (horizontal/vertical) of the underlying +# container. +# +use i3test tests => 7; +use Time::HiRes qw(sleep); + +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + +# Enforce vertical split mode +$i3->command('split v')->recv; + +$i3->command('open')->recv; +my ($nodes, $focus) = get_ws_content($tmp); +my $first = $focus->[0]; + +$i3->command('open')->recv; +($nodes, $focus) = get_ws_content($tmp); +my $second = $focus->[0]; + +isnt($first, $second, 'two different containers opened'); + +############################################################## +# change mode to stacking and cycle through the containers +############################################################## + +$i3->command('layout stacking')->recv; +($nodes, $focus) = get_ws_content($tmp); +is($focus->[0], $second, 'second container still focused'); + +$i3->command('next v')->recv; +($nodes, $focus) = get_ws_content($tmp); +is($focus->[0], $first, 'first container focused'); + +$i3->command('prev v')->recv; +($nodes, $focus) = get_ws_content($tmp); +is($focus->[0], $second, 'second container focused again'); + +############################################################## +# now change the orientation to horizontal and cycle +############################################################## + +$i3->command('level up')->recv; +$i3->command('split h')->recv; +$i3->command('level down')->recv; + +$i3->command('next v')->recv; +($nodes, $focus) = get_ws_content($tmp); +is($focus->[0], $first, 'first container focused'); + +$i3->command('prev v')->recv; +($nodes, $focus) = get_ws_content($tmp); +is($focus->[0], $second, 'second container focused again'); + + +diag( "Testing i3, Perl $], $^X" ); From 49add4f3e4d275e6767073df1ef16764fb8f5c2a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Jul 2010 01:56:16 +0200 Subject: [PATCH 151/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20go=20further?= =?UTF-8?q?=20when=20switching=20focus=20on=20a=20CT=5FWORKSPACE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tree.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index cf7f60dd..491548da 100644 --- a/src/tree.c +++ b/src/tree.c @@ -332,7 +332,8 @@ void tree_render() { void tree_next(char way, orientation_t orientation) { /* 1: get the first parent with the same orientation */ Con *parent = focused->parent; - while (con_orientation(parent) != orientation) { + while (focused->type != CT_WORKSPACE && + con_orientation(parent) != orientation) { LOG("need to go one level further up\n"); /* if the current parent is an output, we are at a workspace * and the orientation still does not match */ From 099df7f438e3ad4ce3887aeb5b9f5c031813b06d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Jul 2010 13:27:34 +0200 Subject: [PATCH 152/867] Bugfix: Keep focus on the current workspace when moving containers, add testcase --- include/con.h | 8 ++++++ src/con.c | 45 ++++++++++++++++++++++++++++++--- src/tree.c | 15 +---------- testcases/t/32-move-workspace.t | 45 +++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 17 deletions(-) create mode 100644 testcases/t/32-move-workspace.t diff --git a/include/con.h b/include/con.h index 716ebfd3..fb862150 100644 --- a/include/con.h +++ b/include/con.h @@ -121,4 +121,12 @@ void con_move_to_workspace(Con *con, Con *workspace); */ int con_orientation(Con *con); +/** + * Returns the container which will be focused next when the given container + * is not available anymore. Called in tree_close and con_move_to_workspace + * to properly restore focus. + * + */ +Con *con_next_focused(Con *con); + #endif diff --git a/src/con.c b/src/con.c index a28d6633..4eeaf13c 100644 --- a/src/con.c +++ b/src/con.c @@ -360,21 +360,28 @@ void con_toggle_fullscreen(Con *con) { * */ void con_move_to_workspace(Con *con, Con *workspace) { - /* 1: get the focused container of this workspace by going down as far as + /* 1: save the container which is going to be focused after the current + * container is moved away */ + Con *focus_next = con_next_focused(con); + + /* 2: get the focused container of this workspace by going down as far as * possible */ Con *next = workspace; while (!TAILQ_EMPTY(&(next->focus_head))) next = TAILQ_FIRST(&(next->focus_head)); - /* 2: we go up one level, but only when next is a normal container */ + /* 3: we go up one level, but only when next is a normal container */ if (next->type != CT_WORKSPACE) next = next->parent; DLOG("Re-attaching container to %p / %s\n", next, next->name); - /* 3: re-attach the con to the parent of this focused container */ + /* 4: re-attach the con to the parent of this focused container */ con_detach(con); con_attach(con, next); + + /* 5: keep focus on the current workspace */ + con_focus(focus_next); } /* @@ -390,3 +397,35 @@ int con_orientation(Con *con) { return con->orientation; } + +/* + * Returns the container which will be focused next when the given container + * is not available anymore. Called in tree_close and con_move_to_workspace + * to properly restore focus. + * + */ +Con *con_next_focused(Con *con) { + Con *next; + /* floating containers are attached to a workspace, so we focus either the + * next floating container (if any) or the workspace itself. */ + if (con->type == CT_FLOATING_CON) { + next = TAILQ_NEXT(con, floating_windows); + if (next == TAILQ_END(&(parent->floating_head))) + next = con_get_workspace(con); + return next; + } + + /* try to focus the next container on the same level as this one */ + next = TAILQ_NEXT(con, focused); + + /* if none, go up to its parent and go down the focus stack as far as + * possible, excluding the current container */ + if (next == TAILQ_END(&(parent->nodes_head))) { + next = con->parent; + while (!TAILQ_EMPTY(&(next->focus_head)) && + TAILQ_FIRST(&(next->focus_head)) != con) + next = TAILQ_FIRST(&(next->focus_head)); + } + + return next; +} diff --git a/src/tree.c b/src/tree.c index 491548da..f695f944 100644 --- a/src/tree.c +++ b/src/tree.c @@ -138,20 +138,7 @@ void tree_close(Con *con, bool kill_window) { fix_floating_parent(croot, con); /* Get the container which is next focused */ - Con *next; - if (con->type == CT_FLOATING_CON) { - next = TAILQ_NEXT(con, floating_windows); - if (next == TAILQ_END(&(parent->floating_head))) - next = con_get_workspace(con); - } else { - next = TAILQ_NEXT(con, focused); - if (next == TAILQ_END(&(parent->nodes_head))) { - next = parent; - while (!TAILQ_EMPTY(&(next->focus_head)) && - TAILQ_FIRST(&(next->focus_head)) != con) - next = TAILQ_FIRST(&(next->focus_head)); - } - } + Con *next = con_next_focused(con); DLOG("closing %p, kill_window = %d\n", con, kill_window); Con *child; diff --git a/testcases/t/32-move-workspace.t b/testcases/t/32-move-workspace.t new file mode 100644 index 00000000..7889d5a7 --- /dev/null +++ b/testcases/t/32-move-workspace.t @@ -0,0 +1,45 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Checks if the 'move workspace' command works correctly +# +use i3test tests => 7; +use Time::HiRes qw(sleep); + +my $i3 = i3("/tmp/nestedcons"); + +# We move the pointer out of our way to avoid a bug where the focus will +# be set to the window under the cursor +my $x = X11::XCB::Connection->new; +$x->root->warp_pointer(0, 0); + +my $tmp = get_unused_workspace(); +my $tmp2 = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + +$i3->command('open')->recv; +my ($nodes, $focus) = get_ws_content($tmp); +my $first = $focus->[0]; +$i3->command('open')->recv; +($nodes, $focus) = get_ws_content($tmp); +my $second = $focus->[0]; +ok(@{get_ws_content($tmp)} == 2, 'two containers on first ws'); + +$i3->command("workspace $tmp2")->recv; +ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws yet'); + +$i3->command("workspace $tmp")->recv; + +$i3->command("move workspace $tmp2")->recv; +ok(@{get_ws_content($tmp)} == 1, 'one container on first ws anymore'); +ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws'); +($nodes, $focus) = get_ws_content($tmp2); + +is($focus->[0], $second, 'same container on different ws'); + +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[0]->{focused}, 1, 'first container focused on first ws'); + +diag( "Testing i3, Perl $], $^X" ); From 21c45418b39232c15a4ddf10e0b645354fffa69e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Jul 2010 13:37:21 +0200 Subject: [PATCH 153/867] more explanation in t/29-focus-after-close.t --- testcases/t/29-focus-after-close.t | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/testcases/t/29-focus-after-close.t b/testcases/t/29-focus-after-close.t index b28ecf2a..916f6618 100644 --- a/testcases/t/29-focus-after-close.t +++ b/testcases/t/29-focus-after-close.t @@ -33,6 +33,14 @@ my $second = $focus->[0]; isnt($first, $second, 'different container focused'); +# We have the following layout now (con is focused): +# .----------------. +# | split | | +# | .----. | con | +# | | cn | | | +# | `----' | | +# `----------------' + ############################################################## # see if the focus goes down to $first (not to its split parent) # when closing $second From 189635a5dcc03be1e12c8b1d0d35e06e9e172bde Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Jul 2010 15:08:22 +0200 Subject: [PATCH 154/867] Bugfix: Even when not going one level up, we need to travel down the whole focus stack --- src/con.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/con.c b/src/con.c index 4eeaf13c..4b0b2307 100644 --- a/src/con.c +++ b/src/con.c @@ -418,14 +418,15 @@ Con *con_next_focused(Con *con) { /* try to focus the next container on the same level as this one */ next = TAILQ_NEXT(con, focused); - /* if none, go up to its parent and go down the focus stack as far as - * possible, excluding the current container */ - if (next == TAILQ_END(&(parent->nodes_head))) { + /* if that was not possible, go up to its parent */ + if (next == TAILQ_END(&(parent->nodes_head))) next = con->parent; - while (!TAILQ_EMPTY(&(next->focus_head)) && - TAILQ_FIRST(&(next->focus_head)) != con) - next = TAILQ_FIRST(&(next->focus_head)); - } + + /* now go down the focus stack as far as + * possible, excluding the current container */ + while (!TAILQ_EMPTY(&(next->focus_head)) && + TAILQ_FIRST(&(next->focus_head)) != con) + next = TAILQ_FIRST(&(next->focus_head)); return next; } From d066341261ea0ca247df75b07b01c766222c4b0a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Jul 2010 15:15:37 +0200 Subject: [PATCH 155/867] ipc/parser: commands can now return custom JSON replies Also, finally add include/cmdparse.h --- include/all.h | 1 + include/cmdparse.h | 6 ++++++ src/cmdparse.y | 13 ++++++++++--- src/ipc.c | 8 ++++---- 4 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 include/cmdparse.h diff --git a/include/all.h b/include/all.h index 2890b9c6..5851141e 100644 --- a/include/all.h +++ b/include/all.h @@ -49,5 +49,6 @@ #include "render.h" #include "window.h" #include "match.h" +#include "cmdparse.h" #endif diff --git a/include/cmdparse.h b/include/cmdparse.h new file mode 100644 index 00000000..09d56bae --- /dev/null +++ b/include/cmdparse.h @@ -0,0 +1,6 @@ +#ifndef _CMDPARSE_H +#define _CMDPARSE_H + +char *parse_cmd(const char *new); + +#endif diff --git a/src/cmdparse.y b/src/cmdparse.y index f4ad6e07..6718e8b2 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -37,6 +37,10 @@ typedef struct owindow { } owindow; static TAILQ_HEAD(owindows_head, owindow) owindows; +/* Holds the JSON which will be returned via IPC or NULL for the default return + * message */ +static char *json_output; + /* We don’t need yydebug for now, as we got decent error messages using * yyerror(). Should you ever want to extend the parser, it might be handy * to just comment it in again, so it stays here. */ @@ -61,7 +65,7 @@ int cmdyywrap() { return 1; } -void parse_cmd(const char *new) { +char *parse_cmd(const char *new) { //const char *new = "[level-up workspace] attach $output, focus"; @@ -69,14 +73,16 @@ void parse_cmd(const char *new) { context = scalloc(sizeof(struct context)); context->filename = "cmd"; + FREE(json_output); if (cmdyyparse() != 0) { fprintf(stderr, "Could not parse configfile\n"); exit(1); } - printf("done\n"); + printf("done, json output = %s\n", json_output); FREE(context->line_copy); free(context); + return json_output; } %} @@ -392,7 +398,8 @@ open: TOK_OPEN { printf("opening new container\n"); - tree_open_con(NULL); + Con *con = tree_open_con(NULL); + asprintf(&json_output, "{\"success\":true, \"id\":%d}", (long int)con); } ; diff --git a/src/ipc.c b/src/ipc.c index 7c0a0f37..2c105acc 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -115,13 +115,13 @@ IPC_HANDLER(command) { char *command = scalloc(message_size + 1); strncpy(command, (const char*)message, message_size); LOG("IPC: received: *%s*\n", command); - parse_cmd((const char*)command); + const char *reply = parse_cmd((const char*)command); tree_render(); free(command); - /* For now, every command gets a positive acknowledge - * (will change with the new command parser) */ - const char *reply = "{\"success\":true}"; + /* If no reply was provided, we just use the default success message */ + if (reply == NULL) + reply = "{\"success\":true}"; ipc_send_message(fd, (const unsigned char*)reply, I3_IPC_REPLY_TYPE_COMMAND, strlen(reply)); } From 55f695436a860af785aa28a57455f43cf11253a2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Jul 2010 15:17:16 +0200 Subject: [PATCH 156/867] testcases: Implement open_empty_con which directly returns the ID --- testcases/t/lib/i3test.pm | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index cf4baccc..2fcfc064 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -10,7 +10,7 @@ use List::Util qw(first); use v5.10; use Exporter (); -our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content get_ws get_focused); +our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content get_ws get_focused open_empty_con); BEGIN { my $window_count = 0; @@ -53,6 +53,13 @@ sub open_standard_window { return $window; } +sub open_empty_con { + my ($i3) = @_; + + my $reply = $i3->command('open')->recv; + return $reply->{id}; +} + sub get_workspace_names { my $i3 = i3("/tmp/nestedcons"); # TODO: use correct command as soon as AnyEvent::i3 is updated From b4e3563dc1478f9b4696653be81d34e628757d9a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Jul 2010 15:17:33 +0200 Subject: [PATCH 157/867] t/29-focus-after-close.t: add more tests (for a regression) --- testcases/t/29-focus-after-close.t | 61 +++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/testcases/t/29-focus-after-close.t b/testcases/t/29-focus-after-close.t index 916f6618..d189ead6 100644 --- a/testcases/t/29-focus-after-close.t +++ b/testcases/t/29-focus-after-close.t @@ -3,7 +3,8 @@ # # Check if the focus is correctly restored after closing windows. # -use i3test tests => 6; +use i3test tests => 9; +use List::Util qw(first); use Time::HiRes qw(sleep); my $i3 = i3("/tmp/nestedcons"); @@ -13,23 +14,18 @@ $i3->command("workspace $tmp")->recv; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -$i3->command('open')->recv; -my ($nodes, $focus) = get_ws_content($tmp); -my $first = $focus->[0]; +my $first = open_empty_con($i3); $i3->command('split v')->recv; -($nodes, $focus) = get_ws_content($tmp); +my ($nodes, $focus) = get_ws_content($tmp); is($nodes->[0]->{focused}, 0, 'split container not focused'); $i3->command('level up')->recv; ($nodes, $focus) = get_ws_content($tmp); is($nodes->[0]->{focused}, 1, 'split container focused after level up'); -$i3->command('open')->recv; - -($nodes, $focus) = get_ws_content($tmp); -my $second = $focus->[0]; +my $second = open_empty_con($i3); isnt($first, $second, 'different container focused'); @@ -54,4 +50,51 @@ sleep 0.25; is($nodes->[0]->{nodes}->[0]->{id}, $first, 'first container found'); is($nodes->[0]->{nodes}->[0]->{focused}, 1, 'first container focused'); +############################################################## +# another case, using a slightly different layout (regression) +############################################################## + +$tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + +$i3->command('split v')->recv; +$first = open_empty_con($i3); +my $bottom = open_empty_con($i3); + +$i3->command('prev v')->recv; +$i3->command('split h')->recv; +my $middle = open_empty_con($i3); +my $right = open_empty_con($i3); +$i3->command('next v')->recv; + +# We have the following layout now (second is focused): +# .----------------------------. +# | .------------------------. | +# | | first | middle | right | | +# | `------------------------' | +# |----------------------------| +# | | +# | second | +# | | +# `----------------------------' + +# Check if the focus is restored to $right when we close $second +$i3->command('kill')->recv; + +is(get_focused($tmp), $right, 'top right container focused (in focus stack)'); + +($nodes, $focus) = get_ws_content($tmp); +my $tr = first { $_->{id} eq $right } @{$nodes->[0]->{nodes}}; +is($tr->{focused}, 1, 'top right container really has focus'); + +############################################################## +# and now for something completely different: +# check if the pointer position is relevant when restoring focus +# (it should not be relevant, of course) +############################################################## + +# TODO: add test code as soon as I can reproduce it + diag( "Testing i3, Perl $], $^X" ); From b572fea5c6d42c066dd4ebe4a874ef71e829b8b8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Jul 2010 15:52:22 +0200 Subject: [PATCH 158/867] testcase: use open_empty_con in some more testcases --- testcases/t/28-open-order.t | 10 ++-------- testcases/t/30-close-empty-split.t | 9 ++------- testcases/t/31-stacking-order.t | 11 +++-------- testcases/t/32-move-workspace.t | 10 +++------- 4 files changed, 10 insertions(+), 30 deletions(-) diff --git a/testcases/t/28-open-order.t b/testcases/t/28-open-order.t index b48e5ee5..98718e13 100644 --- a/testcases/t/28-open-order.t +++ b/testcases/t/28-open-order.t @@ -14,17 +14,11 @@ $i3->command("workspace $tmp")->recv; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); # Open two new container -$i3->command("open")->recv; +my $first = open_empty_con($i3); ok(@{get_ws_content($tmp)} == 1, 'containers opened'); -my ($nodes, $focus) = get_ws_content($tmp); -my $first = $focus->[0]; - -$i3->command("open")->recv; - -($nodes, $focus) = get_ws_content($tmp); -my $second = $focus->[0]; +my $second = open_empty_con($i3); isnt($first, $second, 'different container focused'); diff --git a/testcases/t/30-close-empty-split.t b/testcases/t/30-close-empty-split.t index 31d0ad14..8685ff7f 100644 --- a/testcases/t/30-close-empty-split.t +++ b/testcases/t/30-close-empty-split.t @@ -13,9 +13,7 @@ $i3->command("workspace $tmp")->recv; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -$i3->command('open')->recv; -my ($nodes, $focus) = get_ws_content($tmp); -my $first = $focus->[0]; +my $first = open_empty_con($i3); $i3->command('split v')->recv; @@ -29,10 +27,7 @@ $i3->command('level up')->recv; my $split = $focus->[0]; $i3->command('level down')->recv; -$i3->command('open')->recv; - -($nodes, $focus) = get_ws_content($tmp); -my $second = $focus->[0]; +my $second = open_empty_con($i3); isnt($first, $second, 'different container focused'); diff --git a/testcases/t/31-stacking-order.t b/testcases/t/31-stacking-order.t index caf136fb..747d5b57 100644 --- a/testcases/t/31-stacking-order.t +++ b/testcases/t/31-stacking-order.t @@ -18,13 +18,8 @@ ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); # Enforce vertical split mode $i3->command('split v')->recv; -$i3->command('open')->recv; -my ($nodes, $focus) = get_ws_content($tmp); -my $first = $focus->[0]; - -$i3->command('open')->recv; -($nodes, $focus) = get_ws_content($tmp); -my $second = $focus->[0]; +my $first = open_empty_con($i3); +my $second = open_empty_con($i3); isnt($first, $second, 'two different containers opened'); @@ -33,7 +28,7 @@ isnt($first, $second, 'two different containers opened'); ############################################################## $i3->command('layout stacking')->recv; -($nodes, $focus) = get_ws_content($tmp); +my ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $second, 'second container still focused'); $i3->command('next v')->recv; diff --git a/testcases/t/32-move-workspace.t b/testcases/t/32-move-workspace.t index 7889d5a7..ceef9887 100644 --- a/testcases/t/32-move-workspace.t +++ b/testcases/t/32-move-workspace.t @@ -19,12 +19,8 @@ $i3->command("workspace $tmp")->recv; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -$i3->command('open')->recv; -my ($nodes, $focus) = get_ws_content($tmp); -my $first = $focus->[0]; -$i3->command('open')->recv; -($nodes, $focus) = get_ws_content($tmp); -my $second = $focus->[0]; +my $first = open_empty_con($i3); +my $second = open_empty_con($i3); ok(@{get_ws_content($tmp)} == 2, 'two containers on first ws'); $i3->command("workspace $tmp2")->recv; @@ -35,7 +31,7 @@ $i3->command("workspace $tmp")->recv; $i3->command("move workspace $tmp2")->recv; ok(@{get_ws_content($tmp)} == 1, 'one container on first ws anymore'); ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws'); -($nodes, $focus) = get_ws_content($tmp2); +my ($nodes, $focus) = get_ws_content($tmp2); is($focus->[0], $second, 'same container on different ws'); From 9c5a8d606c6491c3435495d2407f722176002281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sat, 17 Jul 2010 21:40:42 -0300 Subject: [PATCH 159/867] Don't draw stacked decors overlapping a fs window. When both a fullscreen window and a floating window existed at the same time, we used to configure stack_win as a sibling of the floating window. Now we first check if a fullscreen window exists so that the decorations are always behind it. --- src/layout.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/layout.c b/src/layout.c index 76343aa0..3c7d2ff4 100644 --- a/src/layout.c +++ b/src/layout.c @@ -454,12 +454,12 @@ void render_container(xcb_connection_t *conn, Container *container) { /* Raise the stack window, but keep it below the first floating client * and below the fullscreen client (if any) */ Client *first_floating = TAILQ_FIRST(&(container->workspace->floating_clients)); - if (first_floating != TAILQ_END(&(container->workspace->floating_clients))) { - mask |= XCB_CONFIG_WINDOW_SIBLING; - values[4] = first_floating->frame; - } else if (container->workspace->fullscreen_client != NULL) { + if (container->workspace->fullscreen_client != NULL) { mask |= XCB_CONFIG_WINDOW_SIBLING; values[4] = container->workspace->fullscreen_client->frame; + } else if (first_floating != TAILQ_END(&(container->workspace->floating_clients))) { + mask |= XCB_CONFIG_WINDOW_SIBLING; + values[4] = first_floating->frame; } xcb_configure_window(conn, stack_win->window, mask, values); From 95eb1f22c560ec9fc1a344154f1d2d0426b22e8d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 23 Jul 2010 21:38:42 +0200 Subject: [PATCH 160/867] docs: add reference card (by Zeus Panchenko) --- docs/Makefile | 5 ++- docs/refcard.tex | 107 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 docs/refcard.tex diff --git a/docs/Makefile b/docs/Makefile index 79e5019a..379f07d1 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,5 +1,5 @@ -all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html +all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html refcard.pdf hacking-howto.html: hacking-howto asciidoc -a toc -n $< @@ -19,6 +19,9 @@ multi-monitor.html: multi-monitor wsbar.html: wsbar asciidoc -a toc -n $< +refcard.pdf: refcard.tex + pdflatex refcard.tex && pdflatex refcard.tex + clean: rm -f */*.{aux,log,toc,bm,pdf,dvi} rm -f *.log *.html diff --git a/docs/refcard.tex b/docs/refcard.tex new file mode 100644 index 00000000..c945db5e --- /dev/null +++ b/docs/refcard.tex @@ -0,0 +1,107 @@ +\documentclass[10pt,a4,landscape]{article} + +% \usepackage[cam,a4,center,info,]{crop} % frame +\usepackage[height=20cm,width=25.7cm,noheadfoot,landscape]{geometry} % A4 - 210297 mm + +\usepackage[T1]{fontenc} +\usepackage{multicol} +\usepackage{color} +\usepackage{url} +\usepackage{lastpage} + +\usepackage{hyperref} +\hypersetup{ + pdftitle={i3 Reference Card}, + pdfauthor={\textcopyright\ Zeus Panchenko}, + pdfkeywords={i3, refcard}, + pdfsubject={based on http://i3.zekjur.net/docs/userguide.html}, + pdfpagemode={FullScreen}} + +\definecolor{lightgray}{gray}{0.7} + +\pagestyle{empty} + +\setlength{\parindent}{0in} +\setlength{\columnseprule}{0.5pt} +\setlength{\columnsep}{20pt} + +\newcommand{\RefCardTitle}[2] +{\centering{\Large{\textbf{i3 Reference Card (#1/#2)\\ + {\scriptsize{\url{http://i3.zekjur.net/docs/userguide.html}}}}}} + \vspace{1mm}} + + +\newcommand{\RefCardSec}[1] {\vspace{2mm} \raggedright {\vspace{0.5mm} + \colorbox{lightgray} {\makebox[0.31\textwidth][l] + {\Large{\textsc{\textsf{\textbf{\color{black}#1}}}}}} + \vspace{0.5mm}}} + +\newcommand{\RefCardRow}[2] {\normalsize{\textbf{\texttt{#1}}} + \hspace{\stretch{1}} \raggedleft{\small{\textnormal{#2}}} \\} + +\begin{document} + +\centering + +\begin{multicols}{3} + \RefCardTitle{\thepage}{\pageref{LastPage}} + + \RefCardSec{Moving around} + + \RefCardRow{Mod1+Enter}{open new terminal} + \RefCardRow{Mod1+j}{focus (left)} + \RefCardRow{Mod1+k}{focus (down)} + \RefCardRow{Mod1+l}{focus (up)} + \RefCardRow{Mod1+;}{focus (right)} + \RefCardRow{Mod1+Shift+j}{move window (left)} + \RefCardRow{Mod1+Shift+k}{move window (down)} + \RefCardRow{Mod1+Shift+l}{move window (up)} + \RefCardRow{Mod1+Shift+;}{move window (right)} + \RefCardRow{Mod1+Control+j}{snap (left)} + \RefCardRow{Mod1+Control+k}{snap (down)} + \RefCardRow{Mod1+Control+l}{snap (up)} + \RefCardRow{Mod1+Control+;}{snap (right)} + \RefCardRow{Mod1+Shift+q}{kill a window} + \RefCardRow{Mod1+Shift+}{move a window to another workspace} + + + \RefCardSec{Changing container modes} + + \RefCardRow{Mod1+e}{default} + \RefCardRow{Mod1+h}{stacking} + \RefCardRow{Mod1+w}{tabbed} + \RefCardRow{Mod1+Shift+f}{global fullscreen} + \RefCardRow{Mod1+f}{toggle fullscreen} + \RefCardRow{Mod1+Shift+Space}{toggle floating} + \RefCardRow{Mod1+}{drag floating} + + \RefCardSec{Opening other applications} + + \RefCardRow{Mod1+v}{open application launcher (dmenu)} + + \RefCardSec{Using workspaces} + + \RefCardRow{Mod1+}{switch to another workspace} + + \RefCardSec{Restarting i3 inplace} + + \RefCardRow{Mod1+Shift+r}{restart i3 inplace} + + \RefCardSec{Exiting i3} + + \RefCardRow{Mod1+Shift+e}{exit i3} + + \vspace{1cm} + \tiny{ + \begin{center} + Copyright \copyright 2009, Michael Stapelberg \\ + All rights reserved. \\ + Designed by Zeus Panchenko + \end{center} + Permission is granted to copy, distribute and/or modify this + document provided the copyright notice and this permission + notice are preserved on all copies.} + +\end{multicols} + +\end{document} From 63a9647a2c60ba8543e40f73185d603bdc159b85 Mon Sep 17 00:00:00 2001 From: Christopher Zimmermann Date: Thu, 15 Jul 2010 14:38:29 +0200 Subject: [PATCH 161/867] secure strcpy by replacing with strncpy --- i3-input/ipc.c | 2 +- i3-msg/main.c | 2 +- src/ipc.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/i3-input/ipc.c b/i3-input/ipc.c index 597a86ef..2d11f0e0 100644 --- a/i3-input/ipc.c +++ b/i3-input/ipc.c @@ -60,7 +60,7 @@ int connect_ipc(char *socket_path) { struct sockaddr_un addr; memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_LOCAL; - strcpy(addr.sun_path, socket_path); + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) err(EXIT_FAILURE, "Could not connect to i3"); diff --git a/i3-msg/main.c b/i3-msg/main.c index b22d550e..33bedc7c 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -175,7 +175,7 @@ int main(int argc, char *argv[]) { struct sockaddr_un addr; memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_LOCAL; - strcpy(addr.sun_path, socket_path); + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) err(EXIT_FAILURE, "Could not connect to i3"); diff --git a/src/ipc.c b/src/ipc.c index 1937d55d..3dd95653 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -91,7 +91,7 @@ static void ipc_send_message(int fd, const unsigned char *payload, char msg[buffer_size]; char *walk = msg; - strcpy(walk, "i3-ipc"); + strncpy(walk, "i3-ipc", buffer_size - 1); walk += strlen("i3-ipc"); memcpy(walk, &message_size, sizeof(uint32_t)); walk += sizeof(uint32_t); From ca698c7862b8607cc689a1e434c74ae73eaa0caf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Jul 2010 12:19:31 +0200 Subject: [PATCH 162/867] expand .gitignore (Thanks madroach) --- .gitignore | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.gitignore b/.gitignore index 5761abcf..eec7fb0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,19 @@ *.o +i3 +i3-input/i3-input +i3-msg/i3-msg +include/loglevels.h +loglevels.tmp +src/*.output +src/*.tab.* +src/*.yy.c +man/i3-msg.1 +man/i3-msg.xml +man/i3-msg.html +man/i3-wsbar.1 +man/i3-wsbar.xml +man/i3-wsbar.html +man/i3.1 +man/i3.xml +man/i3.html +tags From a89fa51531d4f4cb520d1f5c9ef9b5ded873786f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Jul 2010 12:19:53 +0200 Subject: [PATCH 163/867] debian: add i3-wm.wm for dh_installwm to repository --- debian/i3-wm.wm | 1 + 1 file changed, 1 insertion(+) create mode 100644 debian/i3-wm.wm diff --git a/debian/i3-wm.wm b/debian/i3-wm.wm new file mode 100644 index 00000000..179b073c --- /dev/null +++ b/debian/i3-wm.wm @@ -0,0 +1 @@ +/usr/bin/i3 From ca8d775487354447dde2b2c5c8c5c82c230fab43 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Jul 2010 12:21:33 +0200 Subject: [PATCH 164/867] =?UTF-8?q?Makefile:=20don=E2=80=99t=20set=20PREFI?= =?UTF-8?q?X/SYSCONFDIR=20(necessary=20for=20OpenBSD=20ports=20framework)?= =?UTF-8?q?=20(Thanks=20madroach)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common.mk | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/common.mk b/common.mk index 349da32f..7ba03769 100644 --- a/common.mk +++ b/common.mk @@ -1,11 +1,15 @@ UNAME=$(shell uname) DEBUG=1 INSTALL=install -PREFIX=/usr -ifeq ($(PREFIX),/usr) -SYSCONFDIR=/etc -else -SYSCONFDIR=$(PREFIX)/etc +ifndef PREFIX + PREFIX=/usr +endif +ifndef SYSCONFDIR + ifeq ($(PREFIX),/usr) + SYSCONFDIR=/etc + else + SYSCONFDIR=$(PREFIX)/etc + endif endif # The escaping is absurd, but we need to escape for shell, sed, make, define GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f .git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' .git/HEAD || echo 'unknown'))" From 4d4ce82b35f521ac582add370bdcabe9dbb6d58a Mon Sep 17 00:00:00 2001 From: Christopher Zimmermann Date: Thu, 15 Jul 2010 14:35:17 +0200 Subject: [PATCH 165/867] don't use wordexp.h for tilde expansion wordexp.h is not supported by OpenBSD. Therefore do tilde expansion only via glob(). rename glob_path() to resolve_tilde() since it should not do globbing. --- include/config.h | 4 +++- src/config.c | 43 ++++++++++++++++++++++++------------------- src/ipc.c | 16 ++++++++-------- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/include/config.h b/include/config.h index 0c790bf2..22b0c472 100644 --- a/include/config.h +++ b/include/config.h @@ -126,9 +126,11 @@ struct Config { /** * 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 *glob_path(const char *path); +char *resolve_tilde(const char *path); /** * Checks if the given path exists by calling stat(). diff --git a/src/config.c b/src/config.c index 972e376c..fa4a6042 100644 --- a/src/config.c +++ b/src/config.c @@ -18,7 +18,6 @@ #include #include #include -#include #include /* We need Xlib for XStringToKeysym */ @@ -39,26 +38,32 @@ struct modes_head modes; /* * 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 *glob_path(const char *path) { +char *resolve_tilde(const char *path) { static glob_t globbuf; - if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) - die("glob() failed"); - char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path); - globfree(&globbuf); + char *head, *tail, *result; - /* If the file does not exist yet, we still may need to resolve tilde, - * so call wordexp */ - if (strcmp(result, path) == 0) { - wordexp_t we; - wordexp(path, &we, WRDE_NOCMD); - if (we.we_wordc > 0) { - free(result); - result = sstrdup(we.we_wordv[0]); - } - wordfree(&we); + tail = strchr(path, '/'); + head = strndup(path, tail ? 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 = smalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1); + strncpy(result, head, strlen(head)); + strncat(result, tail, strlen(tail)); + } + globfree(&globbuf); return result; } @@ -239,7 +244,7 @@ static char *get_config_path() { if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) xdg_config_home = "~/.config"; - xdg_config_home = glob_path(xdg_config_home); + xdg_config_home = resolve_tilde(xdg_config_home); if (asprintf(&config_path, "%s/i3/config", xdg_config_home) == -1) die("asprintf() failed"); free(xdg_config_home); @@ -255,7 +260,7 @@ static char *get_config_path() { char *buf = strdup(xdg_config_dirs); char *tok = strtok(buf, ":"); while (tok != NULL) { - tok = glob_path(tok); + tok = resolve_tilde(tok); if (asprintf(&config_path, "%s/i3/config", tok) == -1) die("asprintf() failed"); free(tok); @@ -269,7 +274,7 @@ static char *get_config_path() { free(buf); /* 3: check traditional paths */ - config_path = glob_path("~/.i3/config"); + config_path = resolve_tilde("~/.i3/config"); if (path_exists(config_path)) return config_path; diff --git a/src/ipc.c b/src/ipc.c index 3dd95653..fcda355e 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -502,20 +502,20 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) { int ipc_create_socket(const char *filename) { int sockfd; - char *globbed = glob_path(filename); - DLOG("Creating IPC-socket at %s\n", globbed); - char *copy = sstrdup(globbed); + char *resolved = resolve_tilde(filename); + DLOG("Creating IPC-socket at %s\n", resolved); + char *copy = sstrdup(resolved); const char *dir = dirname(copy); if (!path_exists(dir)) mkdirp(dir); free(copy); /* Unlink the unix domain socket before */ - unlink(globbed); + unlink(resolved); if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { perror("socket()"); - free(globbed); + free(resolved); return -1; } @@ -524,14 +524,14 @@ int ipc_create_socket(const char *filename) { struct sockaddr_un addr; memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_LOCAL; - strcpy(addr.sun_path, globbed); + strncpy(addr.sun_path, resolved, sizeof(addr.sun_path) - 1); if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) { perror("bind()"); - free(globbed); + free(resolved); return -1; } - free(globbed); + free(resolved); set_nonblock(sockfd); if (listen(sockfd, 5) < 0) { From a6e7894b11bfda84ff573fa81cd783a498d36517 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Jul 2010 12:32:03 +0200 Subject: [PATCH 166/867] Bugfix: Use scalloc to get a null-terminated string --- src/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.c b/src/config.c index fa4a6042..6d5f57f1 100644 --- a/src/config.c +++ b/src/config.c @@ -59,7 +59,7 @@ char *resolve_tilde(const char *path) { } else { head = globbuf.gl_pathv[0]; - result = smalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1); + result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1); strncpy(result, head, strlen(head)); strncat(result, tail, strlen(tail)); } From 6e5e2fe5ee7ea92c93207b8f5ff5f8b674d7a72e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Jul 2010 12:34:27 +0200 Subject: [PATCH 167/867] little style fixes --- src/config.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/config.c b/src/config.c index 6d5f57f1..0b4762df 100644 --- a/src/config.c +++ b/src/config.c @@ -52,11 +52,10 @@ char *resolve_tilde(const char *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) + if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1) result = sstrdup(path); - else if (res != 0) { + else if (res != 0) die("glob() failed"); - } else { head = globbuf.gl_pathv[0]; result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1); From d60a741f7865f05473c56d6284a42bbd191e236f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Jul 2010 12:35:16 +0200 Subject: [PATCH 168/867] little style fixes, part 2 --- src/config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.c b/src/config.c index 0b4762df..e22e7c8e 100644 --- a/src/config.c +++ b/src/config.c @@ -54,9 +54,9 @@ char *resolve_tilde(const char *path) { /* no match, or many wildcard matches are bad */ if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1) result = sstrdup(path); - else if (res != 0) + else if (res != 0) { die("glob() failed"); - else { + } else { head = globbuf.gl_pathv[0]; result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1); strncpy(result, head, strlen(head)); From 06da6d98e75bee02c1ad699d2f684ca36be66041 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 31 Jul 2010 14:57:44 +0200 Subject: [PATCH 169/867] Bugfix: Replay unhandled pointer events (Thanks Marcus) --- src/click.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/click.c b/src/click.c index 9f2a47ff..5efa16c9 100644 --- a/src/click.c +++ b/src/click.c @@ -309,6 +309,8 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ return 1; DLOG("Could not handle this button press\n"); + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + xcb_flush(conn); return 1; } From b628aab7d8c8973996b147955c65533758bf6da0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 31 Jul 2010 15:04:13 +0200 Subject: [PATCH 170/867] Look for $SYSCONFDIR/i3/config instead of hard-coded /etc/i3/config (Thanks Don) --- common.mk | 1 + src/config.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common.mk b/common.mk index 7ba03769..e3a9f80b 100644 --- a/common.mk +++ b/common.mk @@ -24,6 +24,7 @@ CFLAGS += -Wunused-value CFLAGS += -Iinclude CFLAGS += -I/usr/local/include CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" +CFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" # Check if pkg-config is installed, because without pkg-config, the following # check for the version of libxcb cannot be done. diff --git a/src/config.c b/src/config.c index e22e7c8e..cd88da3d 100644 --- a/src/config.c +++ b/src/config.c @@ -277,11 +277,11 @@ static char *get_config_path() { if (path_exists(config_path)) return config_path; - config_path = strdup("/etc/i3/config"); + config_path = strdup(SYSCONFDIR "/i3/config"); if (!path_exists(config_path)) die("Neither $XDG_CONFIG_HOME/i3/config, nor " "$XDG_CONFIG_DIRS/i3/config, nor ~/.i3/config nor " - "/etc/i3/config exist."); + SYSCONFDIR "/i3/config exist."); return config_path; } From 0411299e4c06077b8fe5ec529de4977b5b432041 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 15 Aug 2010 12:18:05 +0200 Subject: [PATCH 171/867] fix typo --- include/data.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/data.h b/include/data.h index ba70de0e..de29e829 100644 --- a/include/data.h +++ b/include/data.h @@ -215,7 +215,7 @@ struct Window { char *class_class; char *class_instance; - /** The name of the window as it will be passod to X11 (in UCS2 if the + /** The name of the window as it will be passed to X11 (in UCS2 if the * application supports _NET_WM_NAME, in COMPOUND_TEXT otherwise). */ char *name_x; From 160c12ed9a1c0ee2f30ed8f71d75fb174242eaf9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 15 Aug 2010 12:18:27 +0200 Subject: [PATCH 172/867] recognize dock windows (and support matching them) --- include/data.h | 4 ++++ include/match.h | 8 ++++++++ src/load_layout.c | 10 +++++++++- src/main.c | 4 +++- src/manage.c | 10 +++++++--- src/match.c | 18 ++++++++++++++++++ 6 files changed, 49 insertions(+), 5 deletions(-) diff --git a/include/data.h b/include/data.h index de29e829..79e0e18a 100644 --- a/include/data.h +++ b/include/data.h @@ -228,6 +228,9 @@ struct Window { /** Whether the application used _NET_WM_NAME */ bool uses_net_wm_name; + + /** Whether the window says it is a dock window */ + bool dock; }; struct Match { @@ -239,6 +242,7 @@ struct Match { char *class; char *instance; char *mark; + int dock; xcb_window_t id; Con *con_id; enum { M_ANY = 0, M_TILING, M_FLOATING } floating; diff --git a/include/match.h b/include/match.h index 923be2e1..ef025172 100644 --- a/include/match.h +++ b/include/match.h @@ -1,6 +1,14 @@ #ifndef _MATCH_H #define _MATCH_H +/* + * Initializes the Match data structure. This function is necessary because the + * members representing boolean values (like dock) need to be initialized with + * -1 instead of 0. + * + */ +void match_init(Match *match); + /** * Check if a match is empty. This is necessary while parsing commands to see * whether the user specified a match at all. diff --git a/src/load_layout.c b/src/load_layout.c index c7e77eef..b88a49be 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -1,3 +1,7 @@ +/* + * vim:ts=4:sw=4:expandtab + * + */ #include #include #include @@ -16,7 +20,8 @@ static int json_start_map(void *ctx) { LOG("start of map\n"); if (parsing_swallows) { LOG("TODO: create new swallow\n"); - current_swallow = scalloc(sizeof(Match)); + current_swallow = smalloc(sizeof(Match)); + match_init(current_swallow); TAILQ_INSERT_TAIL(&(json_node->swallow_head), current_swallow, matches); } else { if (!parsing_rect) @@ -104,6 +109,9 @@ static int json_int(void *ctx, long val) { if (strcasecmp(last_key, "id") == 0) { current_swallow->id = val; } + if (strcasecmp(last_key, "dock") == 0) { + current_swallow->dock = true; + } } return 1; diff --git a/src/main.c b/src/main.c index a5769d17..076aa277 100644 --- a/src/main.c +++ b/src/main.c @@ -296,7 +296,9 @@ int main(int argc, char *argv[]) { /* proof-of-concept for assignments */ Con *ws = workspace_get("3"); - Match *current_swallow = scalloc(sizeof(Match)); + Match *current_swallow = smalloc(sizeof(Match)); + match_init(current_swallow); + TAILQ_INSERT_TAIL(&(ws->swallow_head), current_swallow, matches); current_swallow->insert_where = M_ACTIVE; diff --git a/src/manage.c b/src/manage.c index 48262642..79200882 100644 --- a/src/manage.c +++ b/src/manage.c @@ -145,6 +145,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL)); window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL)); + xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, wm_type_cookie, NULL); + if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DOCK])) { + cwindow->dock = true; + LOG("this window is a dock\n"); + } + + Con *nc; Match *match; @@ -175,9 +182,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki nc->window = cwindow; x_reinit(nc); - xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, wm_type_cookie, NULL); - if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DOCK])) - LOG("this window is a dock\n"); /* set floating if necessary */ if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DIALOG]) || diff --git a/src/match.c b/src/match.c index c384c41a..42eba26e 100644 --- a/src/match.c +++ b/src/match.c @@ -14,6 +14,17 @@ #include "all.h" +/* + * Initializes the Match data structure. This function is necessary because the + * members representing boolean values (like dock) need to be initialized with + * -1 instead of 0. + * + */ +void match_init(Match *match) { + memset(match, 0, sizeof(Match)); + match->dock = -1; +} + /* * Check if a match is empty. This is necessary while parsing commands to see * whether the user specified a match at all. @@ -30,6 +41,7 @@ bool match_is_empty(Match *match) { match->instance == NULL && match->id == XCB_NONE && match->con_id == NULL && + match->dock == -1 && match->floating == M_ANY); } @@ -54,6 +66,12 @@ bool match_matches_window(Match *match, i3Window *window) { return true; } + LOG("match->dock = %d, window->dock = %d\n", match->dock, window->dock); + if (match->dock != -1 && window->dock == match->dock) { + LOG("match made by dock\n"); + return true; + } + LOG("window %d (%s) could not be matched\n", window->id, window->class_class); return false; From c780f5dd0be3963cd04b124645e72123083baf77 Mon Sep 17 00:00:00 2001 From: Lourens Rozema Date: Fri, 20 Aug 2010 21:38:07 +0200 Subject: [PATCH 173/867] Test 16 fixed expectation of the (root) focused field to exist in the tree and always be zero. --- testcases/t/16-nestedcons.t | 1 + 1 file changed, 1 insertion(+) diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t index e3f96911..ecc20d82 100644 --- a/testcases/t/16-nestedcons.t +++ b/testcases/t/16-nestedcons.t @@ -23,6 +23,7 @@ my $expected = { rect => ignore(), layout => 0, focus => ignore(), + focused => 0, urgent => 0, 'floating-nodes' => ignore(), }; From 27ffe9eae13708a44a3c79616b00f8aa992acc0f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 21 Aug 2010 18:25:48 +0200 Subject: [PATCH 174/867] =?UTF-8?q?t/22-split:=20Bugfix:=20don=E2=80=99t?= =?UTF-8?q?=20declare=20old=5Fcount=20twice=20(Thanks=20Lourens)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- testcases/t/22-split.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcases/t/22-split.t b/testcases/t/22-split.t index eca8a4a5..f3942243 100644 --- a/testcases/t/22-split.t +++ b/testcases/t/22-split.t @@ -85,7 +85,7 @@ my $old_count = sum_nodes(\@content); $i3->command('split v')->recv; @content = @{get_ws_content($tmp)}; -my $old_count = sum_nodes(\@content); +$old_count = sum_nodes(\@content); $i3->command('split v')->recv; From ebe878d24c8c525d0806b2edd53f5622f135df7a Mon Sep 17 00:00:00 2001 From: Lourens Rozema Date: Fri, 20 Aug 2010 21:40:22 +0200 Subject: [PATCH 175/867] Indent clean up. --- src/ipc.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index 2c105acc..97fecd81 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -193,13 +193,13 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { if (inplace_restart) { if (con->window != NULL) { - ystr("swallows"); - y(array_open); - y(map_open); - ystr("id"); - y(integer, con->window->id); - y(map_close); - y(array_close); + ystr("swallows"); + y(array_open); + y(map_open); + ystr("id"); + y(integer, con->window->id); + y(map_close); + y(array_close); } } From 8159ffaa06cca36ca4be392e7654561430d53f12 Mon Sep 17 00:00:00 2001 From: Lourens Rozema Date: Fri, 20 Aug 2010 21:41:05 +0200 Subject: [PATCH 176/867] Bugfix for the kill command (used a.o. in test 18). Call to match_init is to be used i.o. a simple memset() to zero. Otherwise the boolean dock field doesn't get initalized to -1. --- src/cmdparse.y | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index 6718e8b2..5dba6f59 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -162,7 +162,7 @@ commands: /* empty */ TAILQ_REMOVE(&owindows, current, owindows); free(current); } - memset(¤t_match, 0, sizeof(Match)); + match_init(¤t_match); } ; @@ -185,7 +185,7 @@ matchstart: '[' { printf("start\n"); - memset(¤t_match, '\0', sizeof(Match)); + match_init(¤t_match); TAILQ_INIT(&owindows); /* copy all_cons */ Con *con; From 042abe20b56e7b599922a04636c90bede11135ea Mon Sep 17 00:00:00 2001 From: Lourens Rozema Date: Fri, 20 Aug 2010 21:33:18 +0200 Subject: [PATCH 177/867] Initial commit of gtk-tree-watch.pl which uses GTK/Perl to visualize the i3 tree. It is based on the dump-asy.pl code. --- gtk-tree-watch.pl | 219 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100755 gtk-tree-watch.pl diff --git a/gtk-tree-watch.pl b/gtk-tree-watch.pl new file mode 100755 index 00000000..6d83d1fb --- /dev/null +++ b/gtk-tree-watch.pl @@ -0,0 +1,219 @@ +#!/usr/bin/env perl +# vim:ts=4:sw=4:expandtab +# renders the layout tree using asymptote + +use strict; +use warnings; + +use JSON::XS; +use Data::Dumper; +use AnyEvent::I3; +use v5.10; + +use Gtk2 '-init'; +use Gtk2::SimpleMenu; +use Glib qw/TRUE FALSE/; + +my $window = Gtk2::Window->new('toplevel'); +$window->signal_connect('delete_event' => sub { Gtk2->main_quit; }); + +my $tree_store = Gtk2::TreeStore->new(qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/); + +my $i3 = i3("/tmp/nestedcons"); + +my $tree_view = Gtk2::TreeView->new($tree_store); + +my $layout_box = undef; + +sub copy_node { + my ($n, $parent, $piter, $pbox) = @_; + + my $o = ($n->{orientation} == 0 ? "u" : ($n->{orientation} == 1 ? "h" : "v")); + my $w = (defined($n->{window}) ? $n->{window} : "N"); + + # convert a rectangle struct to X11 notation (WxH+X+Y) + my $r = $n->{rect}; + my $x = $r->{x}; + my $y = $r->{y}; + my $dim = $r->{width}."x".$r->{height}.($x<0?$x:"+$x").($y<0?$y:"+$y"); + + # add node to the tree with all known properties + my $iter = $tree_store->append($piter); + $tree_store->set($iter, 0 => $n->{name}, 1 => $w, 2 => $o, 3 => sprintf("0x%08x", $n->{id}), 4 => $n->{urgent}, 5 => $n->{focused}, 6 => $n->{layout}, 7 => $dim); + + # also create a box for the node, each node has a vbox + # for combining the title (and properties) with the + # container itself, the container will be empty in case + # of no children, a vbox or hbox + my $box; + if($n->{orientation} == 1) { + $box = Gtk2::HBox->new(1, 5); + } else { + $box = Gtk2::VBox->new(1, 5); + } + + # combine label and container + my $node = Gtk2::Frame->new($n->{name}.",".$o.",".$w); + $node->set_shadow_type('etched-out'); + $node->add($box); + + # the parent is added onto a scrolled window, so add it with a viewport + if(defined($pbox)) { + $pbox->pack_start($node, 1, 1, 0); + } else { + $layout_box = $node; + } + + # recurse into children + copy_node($_, $n, $iter, $box) for @{$n->{nodes}}; + + # if it is a window draw a nice color + if(defined($n->{window})) { + # use a drawing area to fill a colored rectangle + my $area = Gtk2::DrawingArea->new(); + + # the color is stored as hex in the name + $area->{"user-data"} = $n->{name}; + + $area->signal_connect(expose_event => sub { + my ($widget, $event) = @_; + + # fetch a cairo context and it width/height to start drawing nodes + my $cr = Gtk2::Gdk::Cairo::Context->create($widget->window()); + + my $w = $widget->allocation->width; + my $h = $widget->allocation->height; + + my $hc = $widget->{"user-data"}; + my $r = hex(substr($hc, 1, 2)) / 255.0; + my $g = hex(substr($hc, 3, 2)) / 255.0; + my $b = hex(substr($hc, 5, 2)) / 255.0; + + $cr->set_source_rgb($r, $g, $b); + $cr->rectangle(0, 0, $w, $h); + $cr->fill(); + + return FALSE; + }); + + $box->pack_end($area, 1, 1, 0); + } +} + +# Replaced by Gtk2 Boxes: +#sub draw_node { +# my ($n, $cr, $x, $y, $w, $h) = @_; +# +# $cr->set_source_rgb(1.0, 1.0, 1.0); +# $cr->rectangle($x, $y, $w/2, $h/2); +# $cr->fill(); +#} + +my $json_prev = ""; + +my $layout_sw = Gtk2::ScrolledWindow->new(undef, undef); +my $layout_container = Gtk2::HBox->new(0, 0); +$layout_sw->add_with_viewport($layout_container); + +sub copy_tree { + my $tree = $i3->get_workspaces->recv; + + # convert the tree back to json so we only rebuild/redraw when the tree is changed + my $json = encode_json($tree); + if($json ne $json_prev) { + $json_prev = $json; + + # rebuild the tree and the layout + $tree_store->clear(); + if(defined($layout_box)) { + $layout_container->remove($layout_box); + } + copy_node($tree); + $layout_container->add($layout_box); + $layout_container->show_all(); + + # keep things expanded, otherwise the tree collapses every reload which is more annoying then this :-) + $tree_view->expand_all(); + } + + return(TRUE); +} + +sub new_column { + my $tree_column = Gtk2::TreeViewColumn->new(); + $tree_column->set_title(shift); + + my $renderer = Gtk2::CellRendererText->new(); + $tree_column->pack_start($renderer, FALSE); + $tree_column->add_attribute($renderer, text => shift); + + return($tree_column); +} + +my $col = 0; +$tree_view->append_column(new_column("Name", $col++)); +$tree_view->append_column(new_column("Window", $col++)); +$tree_view->append_column(new_column("Orientation", $col++)); +$tree_view->append_column(new_column("ID", $col++)); +$tree_view->append_column(new_column("Urgent", $col++)); +$tree_view->append_column(new_column("Focused", $col++)); +$tree_view->append_column(new_column("Layout", $col++)); +$tree_view->append_column(new_column("Rect", $col++)); + +$tree_view->set_grid_lines("both"); + +my $tree_sw = Gtk2::ScrolledWindow->new(undef, undef); +$tree_sw->add($tree_view); + +# Replaced by Gtk2 Boxes: +#my $area = Gtk2::DrawingArea->new(); +#$area->signal_connect(expose_event => sub { +# my ($widget, $event) = @_; +# +# # fetch a cairo context and it width/height to start drawing nodes +# my $cr = Gtk2::Gdk::Cairo::Context->create($widget->window()); +# +# my $w = $widget->allocation->width; +# my $h = $widget->allocation->height; +# +# draw_node($gtree, $cr, 0, 0, $w, $h); +# +# return FALSE; +#}); + +sub menu_export { + print("TODO: EXPORT\n"); +} + +my $menu_tree = [ + _File => { + item_type => '', + children => [ + _Export => { + callback => \&menu_export, + accelerator => 'E', + }, + _Quit => { + callback => sub { Gtk2->main_quit; }, + accelerator => 'Q', + }, + ], + }, +]; + +my $menu = Gtk2::SimpleMenu->new(menu_tree => $menu_tree); + +my $vbox = Gtk2::VBox->new(0, 0); +$vbox->pack_start($menu->{widget}, 0, 0, 0); +$vbox->pack_end($tree_sw, 1, 1, 0); +$vbox->pack_end($layout_sw, 1, 1, 0); + +$window->add($vbox); +$window->show_all(); +$window->set_size_request(500,500); + +Glib::Timeout->add(1000, "copy_tree", undef, Glib::G_PRIORITY_DEFAULT); +copy_tree(); + +Gtk2->main(); + From 161afa3d01c124fed87391cad09b58748f455e62 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 21 Aug 2010 18:34:51 +0200 Subject: [PATCH 178/867] Fix some indention problems in gtk-tree-watch.pl --- gtk-tree-watch.pl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/gtk-tree-watch.pl b/gtk-tree-watch.pl index 6d83d1fb..b82d25d2 100755 --- a/gtk-tree-watch.pl +++ b/gtk-tree-watch.pl @@ -12,7 +12,7 @@ use v5.10; use Gtk2 '-init'; use Gtk2::SimpleMenu; -use Glib qw/TRUE FALSE/; +use Glib qw/TRUE FALSE/; my $window = Gtk2::Window->new('toplevel'); $window->signal_connect('delete_event' => sub { Gtk2->main_quit; }); @@ -93,10 +93,10 @@ sub copy_node { $cr->rectangle(0, 0, $w, $h); $cr->fill(); - return FALSE; + return FALSE; }); - $box->pack_end($area, 1, 1, 0); + $box->pack_end($area, 1, 1, 0); } } @@ -120,17 +120,17 @@ sub copy_tree { # convert the tree back to json so we only rebuild/redraw when the tree is changed my $json = encode_json($tree); - if($json ne $json_prev) { + if ($json ne $json_prev) { $json_prev = $json; - # rebuild the tree and the layout + # rebuild the tree and the layout $tree_store->clear(); - if(defined($layout_box)) { - $layout_container->remove($layout_box); - } + if(defined($layout_box)) { + $layout_container->remove($layout_box); + } copy_node($tree); - $layout_container->add($layout_box); - $layout_container->show_all(); + $layout_container->add($layout_box); + $layout_container->show_all(); # keep things expanded, otherwise the tree collapses every reload which is more annoying then this :-) $tree_view->expand_all(); From f73252431bc6391e170be782be2e20eac91210b1 Mon Sep 17 00:00:00 2001 From: Lourens Rozema Date: Fri, 20 Aug 2010 21:35:24 +0200 Subject: [PATCH 179/867] Bug fix of invalid presentation of container's split orientation. --- dump-asy.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dump-asy.pl b/dump-asy.pl index 343a39c4..315dcc6f 100755 --- a/dump-asy.pl +++ b/dump-asy.pl @@ -22,7 +22,7 @@ say $tmp "treeLevelStep = 2cm;"; sub dump_node { my ($n, $parent) = @_; - my $o = ($n->{orientation} == 0 ? "h" : "v"); + my $o = ($n->{orientation} == 0 ? "u" : ($n->{orientation} == 1 ? "h" : "v")); my $w = (defined($n->{window}) ? $n->{window} : "N"); my $na = $n->{name}; $na =~ s/#/\\#/g; From 4d12e18571f53f0eb80f0422d79d1044cc7ed92b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 21 Aug 2010 18:36:51 +0200 Subject: [PATCH 180/867] remove proof-of-concept code in main.c --- src/main.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/main.c b/src/main.c index 076aa277..2fe64b80 100644 --- a/src/main.c +++ b/src/main.c @@ -293,17 +293,6 @@ int main(int argc, char *argv[]) { tree_init(); tree_render(); - /* proof-of-concept for assignments */ - Con *ws = workspace_get("3"); - - Match *current_swallow = smalloc(sizeof(Match)); - match_init(current_swallow); - - TAILQ_INSERT_TAIL(&(ws->swallow_head), current_swallow, matches); - - current_swallow->insert_where = M_ACTIVE; - current_swallow->class = strdup("xterm"); - struct ev_loop *loop = ev_loop_new(0); if (loop == NULL) die("Could not initialize libev. Bad LIBEV_FLAGS?\n"); From 5403fac512ea025820be1a062b89b759111ce820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Fri, 27 Aug 2010 20:54:41 -0300 Subject: [PATCH 181/867] Validate the ws number for client assignment. --- src/cfgparse.y | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 234e8fa7..3ba788a9 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -493,13 +493,18 @@ workspace_name: assign: TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target { - printf("assignment of %s\n", $3); + DLOG("assignment of %s\n", $3); struct Assignment *new = $6; - printf(" to %d\n", new->workspace); - printf(" floating = %d\n", new->floating); - new->windowclass_title = $3; - TAILQ_INSERT_TAIL(&assignments, new, assignments); + if (new->floating != ASSIGN_FLOATING_ONLY && new->workspace < 1) { + DLOG("Invalid client assignment, workspace number %d out of range\n", new->workspace); + free(new); + } else { + DLOG(" to %d\n", new->workspace); + DLOG(" floating = %d\n", new->floating); + new->windowclass_title = $3; + TAILQ_INSERT_TAIL(&assignments, new, assignments); + } } ; From 53d6f476bc7289a9a69f8ccc9ff72c480734ada3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 1 Sep 2010 14:31:25 +0200 Subject: [PATCH 182/867] Sanity-check the stack-limit argument (limit needs to be > 0) --- src/cfgparse.y | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 3ba788a9..790ae3be 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -393,18 +393,22 @@ new_container: } | TOKNEWCONTAINER WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER { - DLOG("stack-limit %d with val %d\n", $5, $7); - config.container_stack_limit = $5; - config.container_stack_limit_value = $7; + if ($7 <= 0) { + ELOG("Invalid stack-limit, need a limit which is > 0\n"); + } else { + DLOG("stack-limit %d with val %d\n", $5, $7); + config.container_stack_limit = $5; + config.container_stack_limit_value = $7; - /* See the comment above */ - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->table == NULL) - continue; - Container *con = ws->table[0][0]; - con->stack_limit = config.container_stack_limit; - con->stack_limit_value = config.container_stack_limit_value; + /* See the comment above */ + Workspace *ws; + TAILQ_FOREACH(ws, workspaces, workspaces) { + if (ws->table == NULL) + continue; + Container *con = ws->table[0][0]; + con->stack_limit = config.container_stack_limit; + con->stack_limit_value = config.container_stack_limit_value; + } } } ; From af9cbc126ca0554d1be8ed40198b7341721e1cca Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 1 Sep 2010 14:31:46 +0200 Subject: [PATCH 183/867] Use ELOG instead of DLOG for invalid config directives --- src/cfgparse.y | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 790ae3be..5c3c4a08 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -458,7 +458,7 @@ workspace: { int ws_num = $3; if (ws_num < 1) { - DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); + ELOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { Workspace *ws = workspace_get(ws_num - 1); ws->preferred_output = $7; @@ -472,7 +472,7 @@ workspace: { int ws_num = $3; if (ws_num < 1) { - DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); + ELOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { DLOG("workspace name to: %s\n", $5); if ($5 != NULL) { @@ -501,7 +501,7 @@ assign: struct Assignment *new = $6; if (new->floating != ASSIGN_FLOATING_ONLY && new->workspace < 1) { - DLOG("Invalid client assignment, workspace number %d out of range\n", new->workspace); + ELOG("Invalid client assignment, workspace number %d out of range\n", new->workspace); free(new); } else { DLOG(" to %d\n", new->workspace); From 95243d1967bcf15c50fe983122f645950998291f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 1 Sep 2010 16:23:18 +0200 Subject: [PATCH 184/867] Set some more atoms for _NET_SUPPORTED (Thanks lexszero) --- include/xcb.h | 8 ++++---- src/mainx.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/xcb.h b/include/xcb.h index 78e1373a..8a21fe4a 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -57,14 +57,14 @@ enum { _NET_SUPPORTED = 0, _NET_WM_WINDOW_TYPE_SPLASH, _NET_WM_DESKTOP, _NET_WM_STRUT_PARTIAL, + _NET_CURRENT_DESKTOP, + _NET_ACTIVE_WINDOW, + _NET_WORKAREA, WM_PROTOCOLS, WM_DELETE_WINDOW, UTF8_STRING, WM_STATE, - WM_CLIENT_LEADER, - _NET_CURRENT_DESKTOP, - _NET_ACTIVE_WINDOW, - _NET_WORKAREA + WM_CLIENT_LEADER }; extern unsigned int xcb_numlock_mask; diff --git a/src/mainx.c b/src/mainx.c index 7e1b394b..234f86ef 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -514,7 +514,7 @@ int main(int argc, char *argv[], char *env[]) { /* Set up the atoms we support */ check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], - ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED"); + ATOM, 32, 16, atoms), "Could not set _NET_SUPPORTED"); /* Set up the window manager’s name */ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3"); From 0925e8b7dc76e9a64872b5b25b210ae98ffe3755 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 1 Sep 2010 18:11:01 +0200 Subject: [PATCH 185/867] Implement sticky windows The implementation works like this: Containers can have a 'sticky-group' attribute. Imagine two different containers (on two different workspaces) which have the same sticky-group. Now you open a window in the first container. When you switch to the other workspace, the window will be re-assigned to the other container. An obvious problem which is not covered with the code at the moment is having two containers with the same sticky-group visible at the same time. --- include/data.h | 5 +++ include/x.h | 13 ++++++++ src/load_layout.c | 4 +++ src/workspace.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++ src/x.c | 74 +++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 174 insertions(+), 1 deletion(-) diff --git a/include/data.h b/include/data.h index 79e0e18a..8341e5eb 100644 --- a/include/data.h +++ b/include/data.h @@ -273,6 +273,11 @@ struct Con { char *name; + /* a sticky-group is an identifier which bundles several containers to a + * group. The contents are shared between all of them, that is they are + * displayed on whichever of the containers is currently visible */ + char *sticky_group; + /* user-definable mark to jump to this container later */ char *mark; diff --git a/include/x.h b/include/x.h index 91af5014..1cd83fb8 100644 --- a/include/x.h +++ b/include/x.h @@ -12,6 +12,19 @@ */ void x_con_init(Con *con); +/** + * Moves a child window from Container src to Container dest. + * + */ +void x_move_win(Con *src, Con *dest); + +/** + * Reparents the child window of the given container (necessary for sticky + * containers). The reparenting happens in the next call of x_push_changes(). + * + */ +void x_reparent_child(Con *con, Con *old); + /** * Re-initializes the associated X window state for this container. You have * to call this when you assign a client to an empty container to ensure that diff --git a/src/load_layout.c b/src/load_layout.c index b88a49be..ae957bc6 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -71,6 +71,10 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) { if (strcasecmp(last_key, "name") == 0) { json_node->name = scalloc((len+1) * sizeof(char)); memcpy(json_node->name, val, len); + } else if (strcasecmp(last_key, "sticky_group") == 0) { + json_node->sticky_group = scalloc((len+1) * sizeof(char)); + memcpy(json_node->sticky_group, val, len); + LOG("sticky_group of this container is %s\n", json_node->sticky_group); } } return 1; diff --git a/src/workspace.c b/src/workspace.c index 6a81f30e..81492240 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -92,6 +92,83 @@ bool workspace_is_visible(Workspace *ws) { } #endif +/* + * XXX: we need to clean up all this recursive walking code. + * + */ +Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) { + Con *current; + + TAILQ_FOREACH(current, &(con->nodes_head), nodes) { + if (current != exclude && + current->sticky_group != NULL && + current->window != NULL && + strcmp(current->sticky_group, sticky_group) == 0) + return current; + + Con *recurse = _get_sticky(current, sticky_group, exclude); + if (recurse != NULL) + return recurse; + } + + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) { + if (current != exclude && + current->sticky_group != NULL && + current->window != NULL && + strcmp(current->sticky_group, sticky_group) == 0) + return current; + + Con *recurse = _get_sticky(current, sticky_group, exclude); + if (recurse != NULL) + return recurse; + } + + return NULL; +} + +/* + * Reassigns all child windows in sticky containers. Called when the user + * changes workspaces. + * + * XXX: what about sticky containers which contain containers? + * + */ +static void workspace_reassign_sticky(Con *con) { + Con *current; + /* 1: go through all containers */ + + /* handle all children and floating windows of this node */ + TAILQ_FOREACH(current, &(con->nodes_head), nodes) { + if (current->sticky_group == NULL) { + workspace_reassign_sticky(current); + continue; + } + + LOG("Ah, this one is sticky: %s / %p\n", current->name, current); + /* 2: find a window which we can re-assign */ + Con *output = con_get_output(current); + Con *src = _get_sticky(output, current->sticky_group, current); + + if (src == NULL) { + LOG("No window found for this sticky group\n"); + workspace_reassign_sticky(current); + continue; + } + + x_move_win(src, current); + current->window = src->window; + current->mapped = true; + src->window = NULL; + src->mapped = false; + + x_reparent_child(current, src); + + LOG("re-assigned window from src %p to dest %p\n", src, current); + } + + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) + workspace_reassign_sticky(current); +} /* * Switches to the given workspace @@ -110,6 +187,8 @@ void workspace_show(const char *num) { TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) current->fullscreen_mode = CF_NONE; + workspace_reassign_sticky(workspace); + LOG("switching to %p\n", workspace); Con *next = workspace; diff --git a/src/x.c b/src/x.c index 8fa52e02..a2e9c71e 100644 --- a/src/x.c +++ b/src/x.c @@ -18,6 +18,13 @@ static xcb_window_t focused_id = XCB_NONE; typedef struct con_state { xcb_window_t id; bool mapped; + + /* For reparenting, we have a flag (need_reparent) and the X ID of the old + * frame this window was in. The latter is necessary because we need to + * ignore UnmapNotify events (by changing the window event mask). */ + bool need_reparent; + xcb_window_t old_frame; + Rect rect; Rect window_rect; @@ -105,6 +112,47 @@ void x_reinit(Con *con) { memset(&(state->window_rect), 0, sizeof(Rect)); } +/* + * Reparents the child window of the given container (necessary for sticky + * containers). The reparenting happens in the next call of x_push_changes(). + * + */ +void x_reparent_child(Con *con, Con *old) { + struct con_state *state; + if ((state = state_for_frame(con->frame)) == NULL) { + ELOG("window state for con not found\n"); + return; + } + + state->need_reparent = true; + state->old_frame = old->frame; +} + +/* + * Moves a child window from Container src to Container dest. + * + */ +void x_move_win(Con *src, Con *dest) { + struct con_state *state_src, *state_dest; + + if ((state_src = state_for_frame(src->frame)) == NULL) { + ELOG("window state for src not found\n"); + return; + } + + if ((state_dest = state_for_frame(dest->frame)) == NULL) { + ELOG("window state for dest not found\n"); + return; + } + + Rect zero; + memset(&zero, 0, sizeof(Rect)); + if (memcmp(&(state_dest->window_rect), &(zero), sizeof(Rect)) == 0) { + memcpy(&(state_dest->window_rect), &(state_src->window_rect), sizeof(Rect)); + LOG("COPYING RECT\n"); + } +} + /* * Kills the window decoration associated with the given container. * @@ -233,6 +281,29 @@ static void x_push_node(Con *con) { LOG("Pushing changes for node %p / %s\n", con, con->name); state = state_for_frame(con->frame); + /* reparent the child window (when the window was moved due to a sticky + * container) */ + if (state->need_reparent && con->window != NULL) { + LOG("Reparenting child window\n"); + + /* Temporarily set the event masks to XCB_NONE so that we won’t get + * UnmapNotify events (otherwise the handler would close the container). + * These events are generated automatically when reparenting. */ + uint32_t values[] = { XCB_NONE }; + xcb_change_window_attributes(conn, state->old_frame, XCB_CW_EVENT_MASK, values); + xcb_change_window_attributes(conn, con->window->id, XCB_CW_EVENT_MASK, values); + + xcb_reparent_window(conn, con->window->id, con->frame, 0, 0); + + values[0] = FRAME_EVENT_MASK; + xcb_change_window_attributes(conn, state->old_frame, XCB_CW_EVENT_MASK, values); + values[0] = CHILD_EVENT_MASK; + xcb_change_window_attributes(conn, con->window->id, XCB_CW_EVENT_MASK, values); + + state->old_frame = XCB_NONE; + state->need_reparent = false; + } + /* map/unmap if map state changed, also ensure that the child window * is changed if we are mapped *and* in initial state (meaning the * container was empty before, but now got a child) */ @@ -269,7 +340,8 @@ static void x_push_node(Con *con) { } /* dito, but for child windows */ - if (memcmp(&(state->window_rect), &(con->window_rect), sizeof(Rect)) != 0) { + if (con->window != NULL && + memcmp(&(state->window_rect), &(con->window_rect), sizeof(Rect)) != 0) { LOG("setting window rect (%d, %d, %d, %d)\n", con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height); xcb_set_window_rect(conn, con->window->id, con->window_rect); From ff86f35f0573b230233b83765608b90afd14354e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 9 Sep 2010 15:34:13 +0200 Subject: [PATCH 186/867] =?UTF-8?q?Bugfix:=20use=20the=20global=20root=20v?= =?UTF-8?q?ariable,=20don=E2=80=99t=20get=20the=20first=20one=20(Thanks=20?= =?UTF-8?q?quaec)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The case of an X11 server having multiple displays is handled correctly by the code in src/mainx.c. However, due to some functions not being correctly refactored and still getting the first screen (and also the first root window) from the XCB connection, i3 was operating on the wrong root window. --- src/floating.c | 1 - src/handlers.c | 2 -- src/sighandler.c | 1 - src/workspace.c | 1 - src/xcb.c | 1 - 5 files changed, 6 deletions(-) diff --git a/src/floating.c b/src/floating.c index 7e62eea5..93a5b11c 100644 --- a/src/floating.c +++ b/src/floating.c @@ -379,7 +379,6 @@ void floating_resize_window(xcb_connection_t *conn, Client *client, */ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, xcb_window_t confine_to, border_t border, callback_t callback, void *extra) { - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; uint32_t new_x, new_y; Rect old_rect; if (client != NULL) diff --git a/src/handlers.c b/src/handlers.c index 12e81f71..31eeced6 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -458,8 +458,6 @@ int handle_screen_change(void *prophs, xcb_connection_t *conn, * */ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event) { - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; - add_ignore_event(event->sequence); Client *client = table_get(&by_child, event->window); diff --git a/src/sighandler.c b/src/sighandler.c index 92cbc5cb..d789f30b 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -109,7 +109,6 @@ static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_p * */ static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, uint32_t width, uint32_t height) { - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; xcb_window_t win = xcb_generate_id(conn); uint32_t mask = 0; diff --git a/src/workspace.c b/src/workspace.c index 2ae0a498..291f754a 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -112,7 +112,6 @@ bool workspace_is_visible(Workspace *ws) { */ void workspace_show(xcb_connection_t *conn, int workspace) { bool need_warp = false; - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; /* t_ws (to workspace) is just a convenience pointer to the workspace we’re switching to */ Workspace *t_ws = workspace_get(workspace-1); diff --git a/src/xcb.c b/src/xcb.c index ee3148ed..38b1d5a8 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -92,7 +92,6 @@ uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) { */ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_class, int cursor, bool map, uint32_t mask, uint32_t *values) { - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; xcb_window_t result = xcb_generate_id(conn); xcb_cursor_t cursor_id = xcb_generate_id(conn); From b2eb0cd76917dda141cc18691b527138e350de34 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 22 Sep 2010 23:10:49 +0200 Subject: [PATCH 187/867] Use sstrdup() instead of strdup() --- src/config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.c b/src/config.c index cd88da3d..425a995e 100644 --- a/src/config.c +++ b/src/config.c @@ -256,7 +256,7 @@ static char *get_config_path() { if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL) xdg_config_dirs = "/etc/xdg"; - char *buf = strdup(xdg_config_dirs); + char *buf = sstrdup(xdg_config_dirs); char *tok = strtok(buf, ":"); while (tok != NULL) { tok = resolve_tilde(tok); @@ -277,7 +277,7 @@ static char *get_config_path() { if (path_exists(config_path)) return config_path; - config_path = strdup(SYSCONFDIR "/i3/config"); + config_path = sstrdup(SYSCONFDIR "/i3/config"); if (!path_exists(config_path)) die("Neither $XDG_CONFIG_HOME/i3/config, nor " "$XDG_CONFIG_DIRS/i3/config, nor ~/.i3/config nor " From 046105ff58d7a31bf86633f210bca6f1e3e36f00 Mon Sep 17 00:00:00 2001 From: Stefan Schneider-Kennedy Date: Mon, 16 Aug 2010 13:26:40 +1000 Subject: [PATCH 188/867] Opera flash plugin no longer prevents in-page focus changing. Specifically, clicking a focussed window no longer forces set_focus. --- src/click.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/click.c b/src/click.c index 5efa16c9..d8183269 100644 --- a/src/click.c +++ b/src/click.c @@ -315,7 +315,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ } /* Set focus in any case */ - set_focus(conn, client, true); + set_focus(conn, client, false); /* Let’s see if this was on the borders (= resize). If not, we’re done */ DLOG("press button on x=%d, y=%d\n", event->event_x, event->event_y); From b21d8a94e643a080e2cfd74eb6678ab8ccc1e0f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sun, 12 Sep 2010 02:54:55 -0300 Subject: [PATCH 189/867] Support for custom bg colors for clients. --- include/config.h | 1 + src/cfgparse.l | 1 + src/cfgparse.y | 10 ++++++++++ src/config.c | 1 + src/handlers.c | 4 ++-- src/layout.c | 10 +++++----- 6 files changed, 20 insertions(+), 7 deletions(-) diff --git a/include/config.h b/include/config.h index 22b0c472..b19589bd 100644 --- a/include/config.h +++ b/include/config.h @@ -112,6 +112,7 @@ struct Config { /* Color codes are stored here */ struct config_client { + uint32_t background; struct Colortriple focused; struct Colortriple focused_inactive; struct Colortriple unfocused; diff --git a/src/cfgparse.l b/src/cfgparse.l index 10a13076..0c0cee70 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -101,6 +101,7 @@ stack-limit { return TOKSTACKLIMIT; } cols { yylval.number = STACK_LIMIT_COLS; return TOKSTACKLIMIT; } rows { yylval.number = STACK_LIMIT_ROWS; return TOKSTACKLIMIT; } exec { BEGIN(BIND_AWS_COND); return TOKEXEC; } +client.background { BEGIN(COLOR_COND); yylval.single_color = &config.client.background; return TOKSINGLECOLOR; } client.focused { BEGIN(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; } client.focused_inactive { BEGIN(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; } client.unfocused { BEGIN(COLOR_COND); yylval.color = &config.client.unfocused; return TOKCOLOR; } diff --git a/src/cfgparse.y b/src/cfgparse.y index 5c3c4a08..7818c136 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -197,6 +197,7 @@ void parse_file(const char *f) { %union { int number; char *string; + uint32_t *single_color; struct Colortriple *color; struct Assignment *assignment; struct Binding *binding; @@ -225,6 +226,7 @@ void parse_file(const char *f) { %token TOKSET %token TOKIPCSOCKET "ipc_socket" %token TOKEXEC "exec" +%token TOKSINGLECOLOR %token TOKCOLOR %token TOKARROW "→" %token TOKMODE "mode" @@ -255,6 +257,7 @@ line: | assign | ipcsocket | exec + | single_color | color | terminal | font @@ -577,6 +580,13 @@ font: } ; +single_color: + TOKSINGLECOLOR WHITESPACE colorpixel + { + uint32_t *dest = $1; + *dest = $3; + } + ; color: TOKCOLOR WHITESPACE colorpixel WHITESPACE colorpixel WHITESPACE colorpixel diff --git a/src/config.c b/src/config.c index 425a995e..7e5f83b1 100644 --- a/src/config.c +++ b/src/config.c @@ -371,6 +371,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, x.text = get_colorpixel(conn, ctext); \ } while (0) + config.client.background = get_colorpixel(conn, "#000000"); INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff"); INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff"); INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888"); diff --git a/src/handlers.c b/src/handlers.c index 31eeced6..d531b951 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -817,8 +817,8 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * {client->rect.width-1, 0}}; /* right upper edge */ xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, client->frame, client->titlegc, 4, points); - /* Draw a black background */ - xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); + /* Draw the background */ + xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, config.client.background); if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) { xcb_rectangle_t crect = {1, 0, client->rect.width - (1 + 1), client->rect.height - 1}; xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); diff --git a/src/layout.c b/src/layout.c index 3c7d2ff4..01204254 100644 --- a/src/layout.c +++ b/src/layout.c @@ -135,7 +135,7 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw /* Draw a rectangle in background color around the window */ if (client->borderless && mode == MODE_DEFAULT) - xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); + xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, config.client.background); else xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background); /* In stacking mode, we only render the rect for this specific decoration */ @@ -151,9 +151,9 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw xcb_rectangle_t rect = {0, 0, client->rect.width, client->rect.height}; xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); - /* Draw the inner background to have a black frame around clients (such as mplayer) + /* Draw the inner background to a frame around clients (such as mplayer) which cannot be resized exactly in our frames and therefore are centered */ - xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); + xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, config.client.background); if (client->titlebar_position == TITLEBAR_OFF && client->borderless) { xcb_rectangle_t crect = {0, 0, client->rect.width, client->rect.height}; xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); @@ -547,7 +547,7 @@ void render_container(xcb_connection_t *conn, Container *container) { * amount of windows */ if (container->mode == MODE_STACK) { if (container->stack_limit == STACK_LIMIT_COLS && (current_col % 2) != 0) { - xcb_change_gc_single(conn, stack_win->pixmap.gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); + xcb_change_gc_single(conn, stack_win->pixmap.gc, XCB_GC_FOREGROUND, config.client.background); int offset_x = current_col * (stack_win->rect.width / container->stack_limit_value); int offset_y = current_row * decoration_height; @@ -556,7 +556,7 @@ void render_container(xcb_connection_t *conn, Container *container) { offset_y + decoration_height }; xcb_poly_fill_rectangle(conn, stack_win->pixmap.id, stack_win->pixmap.gc, 1, &rect); } else if (container->stack_limit == STACK_LIMIT_ROWS && (current_row % 2) != 0) { - xcb_change_gc_single(conn, stack_win->pixmap.gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); + xcb_change_gc_single(conn, stack_win->pixmap.gc, XCB_GC_FOREGROUND, config.client.background); int offset_x = current_col * wrap; int offset_y = current_row * decoration_height; From 556e59449c71c1b10f285d243f369e53ce812f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sun, 12 Sep 2010 16:54:18 -0300 Subject: [PATCH 190/867] Fix possible rounding errors. --- src/layout.c | 47 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/layout.c b/src/layout.c index 01204254..44501a01 100644 --- a/src/layout.c +++ b/src/layout.c @@ -702,6 +702,43 @@ void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) { ignore_enter_notify_forall(conn, r_ws, true); + /* Get the width of the cols */ + int col_width[r_ws->cols]; + int unoccupied_x = get_unoccupied_x(r_ws); + int default_col_width = unoccupied_x / r_ws->cols; + int total_col_width = 0; + for (int i = 0; i < r_ws->cols; ++i) { + col_width[i] = r_ws->width_factor[i] == 0 ? default_col_width : unoccupied_x * r_ws->width_factor[i]; + total_col_width += col_width[i]; + } + + /* Correct rounding errors */ + int error = r_ws->rect.width - total_col_width, error_index = r_ws->cols - 1; + while (error) { + ++col_width[error_index]; + --error; + error_index = error_index == 0 ? r_ws->cols - 1 : error_index - 1; + } + + /* Get the height of the rows */ + int row_height[r_ws->rows]; + int unoccupied_y = get_unoccupied_y(r_ws); + int default_row_height = unoccupied_y / r_ws->rows; + int total_row_height = 0; + for (int i = 0; i < r_ws->rows; ++i) { + row_height[i] = r_ws->height_factor[i] == 0 ? default_row_height : unoccupied_y * r_ws->height_factor[i]; + total_row_height += row_height[i]; + } + + /* Correct rounding errors */ + error = workspace_height(r_ws) - total_row_height; + error_index = r_ws->rows - 1; + while (error) { + ++row_height[error_index]; + --error; + error_index = error_index == 0 ? r_ws->rows - 1 : error_index - 1; + } + /* Go through the whole table and render what’s necessary */ FOR_TABLE(r_ws) { Container *container = r_ws->table[cols][rows]; @@ -716,10 +753,7 @@ void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) { container->width = 0; for (int c = 0; c < container->colspan; c++) { - if (r_ws->width_factor[cols+c] == 0) - container->width += (width / r_ws->cols); - else container->width += get_unoccupied_x(r_ws) * r_ws->width_factor[cols+c]; - + container->width += col_width[cols + c]; if (single_width == -1) single_width = container->width; } @@ -729,10 +763,7 @@ void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) { container->height = 0; for (int c = 0; c < container->rowspan; c++) { - if (r_ws->height_factor[rows+c] == 0) - container->height += (height / r_ws->rows); - else container->height += get_unoccupied_y(r_ws) * r_ws->height_factor[rows+c]; - + container->height += row_height[rows + c]; if (single_height == -1) single_height = container->height; } From 4ca46d131cb0a0d4064d3c672ecbc53dae426f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Fri, 24 Sep 2010 22:01:56 -0300 Subject: [PATCH 191/867] Info about client.background for the user guide. --- docs/userguide | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/userguide b/docs/userguide index 78279565..f3d71fcd 100644 --- a/docs/userguide +++ b/docs/userguide @@ -487,6 +487,21 @@ bar.unfocused:: bar.urgent:: A workspace which has at least one client with an activated urgency hint. +You can also specify the color to be used to paint the background of the client +windows. This color will be used to paint the window on top of which the client +will be rendered. + +*Syntax*: +----------------------- +client.background color +----------------------- + +Only clients that do not cover the whole area of this window expose the color +used to paint it. If you use a color other than black for your terminals, you +most likely want to set the client background color to the same color as your +terminal program's background color to avoid black gaps between the rendered +area of the termianal and the i3 border. + Colors are in HTML hex format (#rrggbb), see the following example: *Examples*: From b904c9a34aa986ebde868c9d22cff0b7643ad916 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Oct 2010 20:18:31 +0200 Subject: [PATCH 192/867] Bugfix: Initialize variable 'root' earlier Fixes a regression introduced in commit f2896d3 (create_window(), invoked indirectly in expand_table_{rows,cols}, uses the root variable). --- src/mainx.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/mainx.c b/src/mainx.c index 234f86ef..17f2bec8 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -283,6 +283,11 @@ int main(int argc, char *argv[], char *env[]) { if (xcb_connection_has_error(conn)) die("Cannot open display\n"); + /* Get the root window */ + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); + root = root_screen->root; + root_depth = root_screen->root_depth; + load_configuration(conn, override_configpath, false); if (only_check_config) { LOG("Done checking configuration file. Exiting.\n"); @@ -441,11 +446,7 @@ int main(int argc, char *argv[], char *env[]) { /* Watch size hints (to obey correct aspect ratio) */ xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL); - /* Get the root window and set the event mask */ - xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); - root = root_screen->root; - root_depth = root_screen->root_depth; - + /* set event mask */ uint32_t mask = XCB_CW_EVENT_MASK; uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video From 5c2758af26587b04eb3ac22f3a7fb5f8a89a805f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Oct 2010 21:32:04 +0200 Subject: [PATCH 193/867] Implement support for size hints (including test case) --- include/all.h | 1 + include/data.h | 13 +++ include/handlers.h | 2 +- src/handlers.c | 200 +++++++++++++++++------------------- src/main.c | 3 + src/manage.c | 2 +- src/render.c | 38 +++++++ testcases/t/33-size-hints.t | 44 ++++++++ 8 files changed, 195 insertions(+), 108 deletions(-) create mode 100644 testcases/t/33-size-hints.t diff --git a/include/all.h b/include/all.h index 5851141e..2096016d 100644 --- a/include/all.h +++ b/include/all.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include diff --git a/include/data.h b/include/data.h index 8341e5eb..3d32a396 100644 --- a/include/data.h +++ b/include/data.h @@ -283,6 +283,19 @@ struct Con { double percent; + /* proportional width/height, calculated from WM_NORMAL_HINTS, used to + * apply an aspect ratio to windows (think of MPlayer) */ + int proportional_width; + int proportional_height; + /* the wanted size of the window, used in combination with size + * increments (see below). */ + int base_width; + int base_height; + + /* minimum increment size specified for the window (in pixels) */ + int width_increment; + int height_increment; + struct Window *window; /* Should this container be marked urgent? This gets set when the window diff --git a/include/handlers.h b/include/handlers.h index 73cafe4b..b4a9486a 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -158,6 +158,7 @@ int handle_client_message(void *data, xcb_connection_t *conn, int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property); +#endif /** * Handles the size hints set by a window, but currently only the part @@ -171,7 +172,6 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *reply); -#endif /** * Handles the WM_HINTS property for extracting the urgency state of the window. * diff --git a/src/handlers.c b/src/handlers.c index 500bdb7f..0e497b04 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -693,6 +693,7 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi ELOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n"); return 0; } +#endif /* * Handles the size hints set by a window, but currently only the part necessary for displaying @@ -703,114 +704,101 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi */ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *reply) { - Client *client = table_get(&by_child, window); - if (client == NULL) { - DLOG("Received WM_SIZE_HINTS for unknown client\n"); - return 1; - } - xcb_size_hints_t size_hints; - - CLIENT_LOG(client); - - /* If the hints were already in this event, use them, if not, request them */ - if (reply != NULL) - xcb_get_wm_size_hints_from_reply(&size_hints, reply); - else - xcb_get_wm_normal_hints_reply(conn, xcb_get_wm_normal_hints_unchecked(conn, client->child), &size_hints, NULL); - - if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) { - // TODO: Minimum size is not yet implemented - DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height); - } - - bool changed = false; - if ((size_hints.flags & XCB_SIZE_HINT_P_RESIZE_INC)) { - if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) - if (client->width_increment != size_hints.width_inc) { - client->width_increment = size_hints.width_inc; - changed = true; - } - if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF) - if (client->height_increment != size_hints.height_inc) { - client->height_increment = size_hints.height_inc; - changed = true; - } - - if (changed) - DLOG("resize increments changed\n"); - } - - int base_width = 0, base_height = 0; - - /* base_width/height are the desired size of the window. - We check if either the program-specified size or the program-specified - min-size is available */ - if (size_hints.flags & XCB_SIZE_HINT_BASE_SIZE) { - base_width = size_hints.base_width; - base_height = size_hints.base_height; - } else if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) { - /* TODO: is this right? icccm says not */ - base_width = size_hints.min_width; - base_height = size_hints.min_height; - } - - if (base_width != client->base_width || - base_height != client->base_height) { - client->base_width = base_width; - client->base_height = base_height; - DLOG("client's base_height changed to %d\n", base_height); - DLOG("client's base_width changed to %d\n", base_width); - changed = true; - } - - if (changed) { - if (client->fullscreen) - DLOG("Not resizing client, it is in fullscreen mode\n"); - else { - resize_client(conn, client); - xcb_flush(conn); - } - } - - /* If no aspect ratio was set or if it was invalid, we ignore the hints */ - if (!(size_hints.flags & XCB_SIZE_HINT_P_ASPECT) || - (size_hints.min_aspect_num <= 0) || - (size_hints.min_aspect_den <= 0)) { - return 1; - } - - double width = client->rect.width - base_width; - double height = client->rect.height - base_height; - /* Convert numerator/denominator to a double */ - double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den; - double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den; - - DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect); - DLOG("width = %f, height = %f\n", width, height); - - /* Sanity checks, this is user-input, in a way */ - if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0) - return 1; - - /* Check if we need to set proportional_* variables using the correct ratio */ - if ((width / height) < min_aspect) { - client->proportional_width = width; - client->proportional_height = width / min_aspect; - } else if ((width / height) > max_aspect) { - client->proportional_width = width; - client->proportional_height = width / max_aspect; - } else return 1; - - client->force_reconfigure = true; - - if (client->container != NULL) { - render_container(conn, client->container); - xcb_flush(conn); - } - + Con *con = con_by_window_id(window); + if (con == NULL) { + DLOG("Received WM_NORMAL_HINTS for unknown client\n"); return 1; + } + + xcb_size_hints_t size_hints; + + //CLIENT_LOG(client); + + /* If the hints were already in this event, use them, if not, request them */ + if (reply != NULL) + xcb_get_wm_size_hints_from_reply(&size_hints, reply); + else + xcb_get_wm_normal_hints_reply(conn, xcb_get_wm_normal_hints_unchecked(conn, con->window->id), &size_hints, NULL); + + if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) { + // TODO: Minimum size is not yet implemented + DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height); + } + + bool changed = false; + if ((size_hints.flags & XCB_SIZE_HINT_P_RESIZE_INC)) { + if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) + if (con->width_increment != size_hints.width_inc) { + con->width_increment = size_hints.width_inc; + changed = true; + } + if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF) + if (con->height_increment != size_hints.height_inc) { + con->height_increment = size_hints.height_inc; + changed = true; + } + + if (changed) + DLOG("resize increments changed\n"); + } + + int base_width = 0, base_height = 0; + + /* base_width/height are the desired size of the window. + We check if either the program-specified size or the program-specified + min-size is available */ + if (size_hints.flags & XCB_SIZE_HINT_BASE_SIZE) { + base_width = size_hints.base_width; + base_height = size_hints.base_height; + } else if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) { + /* TODO: is this right? icccm says not */ + base_width = size_hints.min_width; + base_height = size_hints.min_height; + } + + if (base_width != con->base_width || + base_height != con->base_height) { + con->base_width = base_width; + con->base_height = base_height; + DLOG("client's base_height changed to %d\n", base_height); + DLOG("client's base_width changed to %d\n", base_width); + changed = true; + } + + /* If no aspect ratio was set or if it was invalid, we ignore the hints */ + if (!(size_hints.flags & XCB_SIZE_HINT_P_ASPECT) || + (size_hints.min_aspect_num <= 0) || + (size_hints.min_aspect_den <= 0)) { + goto render_and_return; + } + + /* XXX: do we really use rect here, not window_rect? */ + double width = con->rect.width - base_width; + double height = con->rect.height - base_height; + /* Convert numerator/denominator to a double */ + double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den; + double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den; + + DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect); + DLOG("width = %f, height = %f\n", width, height); + + /* Sanity checks, this is user-input, in a way */ + if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0) + goto render_and_return; + + /* Check if we need to set proportional_* variables using the correct ratio */ + if ((width / height) < min_aspect) { + con->proportional_width = width; + con->proportional_height = width / min_aspect; + } else if ((width / height) > max_aspect) { + con->proportional_width = width; + con->proportional_height = width / max_aspect; + } else goto render_and_return; + +render_and_return: + tree_render(); + return 1; } -#endif /* * Handles the WM_HINTS property for extracting the urgency state of the window. diff --git a/src/main.c b/src/main.c index 2fe64b80..a0eb9192 100644 --- a/src/main.c +++ b/src/main.c @@ -261,6 +261,9 @@ int main(int argc, char *argv[]) { /* Watch WM_NAME (title of the window encoded in COMPOUND_TEXT) */ xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL); + /* Watch WM_NORMAL_HINTS (aspect ratio, size increments, …) */ + xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL); + /* Set up the atoms we support */ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 7, atoms); /* Set up the window manager’s name */ diff --git a/src/manage.c b/src/manage.c index 79200882..134abe9e 100644 --- a/src/manage.c +++ b/src/manage.c @@ -88,7 +88,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki leader_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[WM_CLIENT_LEADER], UINT32_MAX); title_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_NAME, 128); class_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_CLASS, 128); - + /* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */ geomc = xcb_get_geometry(conn, d); diff --git a/src/render.c b/src/render.c index 943ec895..e40b43eb 100644 --- a/src/render.c +++ b/src/render.c @@ -47,6 +47,44 @@ void render_con(Con *con) { inset->x += 2; inset->width -= 2 * 2; inset->height -= 2; + + /* Obey the aspect ratio, if any */ + if (con->proportional_height != 0 && + con->proportional_width != 0) { + DLOG("proportional height = %d, width = %d\n", con->proportional_height, con->proportional_width); + double new_height = inset->height + 1; + int new_width = inset->width; + + while (new_height > inset->height) { + new_height = ((double)con->proportional_height / con->proportional_width) * new_width; + + if (new_height > inset->height) + new_width--; + } + /* Center the window */ + inset->y += ceil(inset->height / 2) - floor(new_height / 2); + inset->x += ceil(inset->width / 2) - floor(new_width / 2); + + inset->height = new_height; + inset->width = new_width; + DLOG("new_height = %f, new_width = %d\n", new_height, new_width); + } + + if (con->height_increment > 1) { + int old_height = inset->height; + inset->height -= (inset->height - con->base_height) % con->height_increment; + DLOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n", + old_height - inset->height, con->height_increment, con->base_height); + } + + if (con->width_increment > 1) { + int old_width = inset->width; + inset->width -= (inset->width - con->base_width) % con->width_increment; + DLOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n", + old_width - inset->width, con->width_increment, con->base_width); + } + + DLOG("child will be at %dx%d with size %dx%d\n", inset->x, inset->y, inset->width, inset->height); } /* Check for fullscreen nodes */ diff --git a/testcases/t/33-size-hints.t b/testcases/t/33-size-hints.t new file mode 100644 index 00000000..c4bd72af --- /dev/null +++ b/testcases/t/33-size-hints.t @@ -0,0 +1,44 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Checks if size hints are interpreted correctly. +# +use i3test tests => 2; +use Time::HiRes qw(sleep); + +my $i3 = i3("/tmp/nestedcons"); + +my $x = X11::XCB::Connection->new; + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + +my $win = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30), + background_color => '#C0C0C0', +); + +# XXX: we should check screen size. in screens with an AR of 2.0, +# this is not a good idea. +my $aspect = X11::XCB::Sizehints::Aspect->new; +$aspect->min_num(600); +$aspect->min_den(300); +$aspect->max_num(600); +$aspect->max_den(300); +$win->_create; +$win->map; +sleep 0.25; +$win->hints->aspect($aspect); +$x->flush; + +sleep 0.25; + +my $rect = $win->rect; +my $ar = $rect->width / $rect->height; +diag("Aspect ratio = $ar"); +ok(($ar > 1.90) && ($ar < 2.10), 'Aspect ratio about 2.0'); + +diag( "Testing i3, Perl $], $^X" ); From 8279eb3a4e9d6a29550d0395ce751061271616b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20Hamb=C3=BCchen?= Date: Thu, 14 Oct 2010 22:57:15 +0100 Subject: [PATCH 194/867] Fix wallpaper flickering on workspace switch by mapping new clients before unmapping old clients --- src/workspace.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/workspace.c b/src/workspace.c index 291f754a..40f68bf8 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -175,17 +175,18 @@ void workspace_show(xcb_connection_t *conn, int workspace) { Workspace *old_workspace = c_ws; c_ws = t_ws->output->current_workspace = workspace_get(workspace-1); - /* Unmap all clients of the old workspace */ - workspace_unmap_clients(conn, old_workspace); - current_row = c_ws->current_row; current_col = c_ws->current_col; DLOG("new current row = %d, current col = %d\n", current_row, current_col); - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); - + /* Map new clients before unmapping old clients to prevent wallpaper flickering */ workspace_map_clients(conn, c_ws); + /* Unmap all clients of the old workspace */ + workspace_unmap_clients(conn, old_workspace); + + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); + /* POTENTIAL TO IMPROVE HERE: due to the call to _map_clients first and * render_layout afterwards, there is a short flickering on the source * workspace (assign ws 3 to output 0, ws 4 to output 1, create single From 24472361103c05da634ab086240fb8e56cb59419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sun, 12 Sep 2010 16:17:21 -0300 Subject: [PATCH 195/867] Fixed overflow and underflow bugs when resizing. --- src/layout.c | 5 +- src/resize.c | 186 +++++++++------------------------------------------ 2 files changed, 34 insertions(+), 157 deletions(-) diff --git a/src/layout.c b/src/layout.c index 44501a01..9af1ffd9 100644 --- a/src/layout.c +++ b/src/layout.c @@ -39,13 +39,12 @@ */ int get_unoccupied_x(Workspace *workspace) { double unoccupied = workspace->rect.width; - double default_factor = ((float)workspace->rect.width / workspace->cols) / workspace->rect.width; + double default_factor = 1.0 / workspace->cols; DLOG("get_unoccupied_x(), starting with %f, default_factor = %f\n", unoccupied, default_factor); for (int cols = 0; cols < workspace->cols; cols++) { DLOG("width_factor[%d] = %f, unoccupied = %f\n", cols, workspace->width_factor[cols], unoccupied); - if (workspace->width_factor[cols] == 0) unoccupied -= workspace->rect.width * default_factor; } @@ -58,7 +57,7 @@ int get_unoccupied_x(Workspace *workspace) { int get_unoccupied_y(Workspace *workspace) { int height = workspace_height(workspace); double unoccupied = height; - double default_factor = ((float)height / workspace->rows) / height; + double default_factor = 1.0 / workspace->rows; DLOG("get_unoccupied_y(), starting with %f, default_factor = %f\n", unoccupied, default_factor); diff --git a/src/resize.c b/src/resize.c index d48dbdbb..f6b6da74 100644 --- a/src/resize.c +++ b/src/resize.c @@ -155,6 +155,32 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i return 1; } +/* + * Adjusts the container size factors according to the resizing parameters. + * This is an abstraction used by resize_container. + */ +static void adjust_container_factors(float *factors, int ws_size, int unoccupied_size, + int num_items, int first, int second, int pixels) { + /* Find the current sizes */ + int sizes[num_items]; + for (int i = 0; i < num_items; ++i) + sizes[i] = factors[i] == 0 ? ws_size / num_items : unoccupied_size * factors[i]; + + /* Adjust them */ + sizes[first] += pixels; + sizes[second] -= pixels; + + /* Calculate the new unoccupied size */ + if (factors[first] == 0) unoccupied_size += ws_size / num_items; + if (factors[second] == 0) unoccupied_size += ws_size / num_items; + + /* Calculate the new factors */ + for (int i = 0; i < num_items; ++i) { + if (factors[i] != 0 || i == first || i == second) + factors[i] = (float)sizes[i] / unoccupied_size; + } +} + /* * Resizes a column/row by the given amount of pixels. Called by * resize_graphical_handler (the user clicked) or parse_resize_command (the @@ -163,161 +189,13 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i */ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int second, resize_orientation_t orientation, int pixels) { - - /* TODO: refactor this, both blocks are very identical */ if (orientation == O_VERTICAL) { - int default_width = ws->rect.width / ws->cols; - int old_unoccupied_x = get_unoccupied_x(ws); - - /* We pre-calculate the unoccupied space to see if we need to adapt sizes before - * doing the resize */ - int new_unoccupied_x = old_unoccupied_x; - - if (old_unoccupied_x == 0) - old_unoccupied_x = ws->rect.width; - - if (ws->width_factor[first] == 0) - new_unoccupied_x += default_width; - - if (ws->width_factor[second] == 0) - new_unoccupied_x += default_width; - - DLOG("\n\n\n"); - DLOG("old = %d, new = %d\n", old_unoccupied_x, new_unoccupied_x); - - int cols_without_wf = 0; - int old_width, old_second_width; - for (int col = 0; col < ws->cols; col++) - if (ws->width_factor[col] == 0) - cols_without_wf++; - - DLOG("old_unoccupied_x = %d\n", old_unoccupied_x); - - DLOG("Updating first (before = %f)\n", ws->width_factor[first]); - /* Convert 0 (for default width_factor) to actual numbers */ - if (ws->width_factor[first] == 0) - old_width = (old_unoccupied_x / max(cols_without_wf, 1)); - else old_width = ws->width_factor[first] * old_unoccupied_x; - - DLOG("second (before = %f)\n", ws->width_factor[second]); - if (ws->width_factor[second] == 0) - old_second_width = (old_unoccupied_x / max(cols_without_wf, 1)); - else old_second_width = ws->width_factor[second] * old_unoccupied_x; - - DLOG("middle = %f\n", ws->width_factor[first]); - - /* If the space used for customly resized columns has changed we need to adapt the - * other customly resized columns, if any */ - if (new_unoccupied_x != old_unoccupied_x) - for (int col = 0; col < ws->cols; col++) { - if (ws->width_factor[col] == 0) - continue; - - DLOG("Updating other column (%d) (current width_factor = %f)\n", col, ws->width_factor[col]); - ws->width_factor[col] = (ws->width_factor[col] * old_unoccupied_x) / new_unoccupied_x; - DLOG("to %f\n", ws->width_factor[col]); - } - - DLOG("Updating first (before = %f)\n", ws->width_factor[first]); - /* Convert 0 (for default width_factor) to actual numbers */ - if (ws->width_factor[first] == 0) - ws->width_factor[first] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; - - DLOG("first->width = %d, pixels = %d\n", old_width, pixels); - ws->width_factor[first] *= (float)(old_width + pixels) / old_width; - DLOG("-> %f\n", ws->width_factor[first]); - - - DLOG("Updating second (before = %f)\n", ws->width_factor[second]); - if (ws->width_factor[second] == 0) - ws->width_factor[second] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; - - DLOG("middle = %f\n", ws->width_factor[second]); - DLOG("second->width = %d, pixels = %d\n", old_second_width, pixels); - ws->width_factor[second] *= (float)(old_second_width - pixels) / old_second_width; - DLOG("-> %f\n", ws->width_factor[second]); - - DLOG("new unoccupied_x = %d\n", get_unoccupied_x(ws)); - - DLOG("\n\n\n"); - } else { - int ws_height = workspace_height(ws); - int default_height = ws_height / ws->rows; - int old_unoccupied_y = get_unoccupied_y(ws); - - /* We pre-calculate the unoccupied space to see if we need to adapt sizes before - * doing the resize */ - int new_unoccupied_y = old_unoccupied_y; - - if (old_unoccupied_y == 0) - old_unoccupied_y = ws_height; - - if (ws->height_factor[first] == 0) - new_unoccupied_y += default_height; - - if (ws->height_factor[second] == 0) - new_unoccupied_y += default_height; - - int cols_without_hf = 0; - int old_height, old_second_height; - for (int row = 0; row < ws->rows; row++) - if (ws->height_factor[row] == 0) - cols_without_hf++; - - DLOG("old_unoccupied_y = %d\n", old_unoccupied_y); - - DLOG("Updating first (before = %f)\n", ws->height_factor[first]); - - /* Convert 0 (for default width_factor) to actual numbers */ - if (ws->height_factor[first] == 0) - old_height = (old_unoccupied_y / max(cols_without_hf, 1)); - else old_height = ws->height_factor[first] * old_unoccupied_y; - - DLOG("second (before = %f)\n", ws->height_factor[second]); - if (ws->height_factor[second] == 0) - old_second_height = (old_unoccupied_y / max(cols_without_hf, 1)); - else old_second_height = ws->height_factor[second] * old_unoccupied_y; - - DLOG("middle = %f\n", ws->height_factor[first]); - - - DLOG("\n\n\n"); - DLOG("old = %d, new = %d\n", old_unoccupied_y, new_unoccupied_y); - - /* If the space used for customly resized columns has changed we need to adapt the - * other customly resized columns, if any */ - if (new_unoccupied_y != old_unoccupied_y) - for (int row = 0; row < ws->rows; row++) { - if (ws->height_factor[row] == 0) - continue; - - DLOG("Updating other column (%d) (current width_factor = %f)\n", row, ws->height_factor[row]); - ws->height_factor[row] = (ws->height_factor[row] * old_unoccupied_y) / new_unoccupied_y; - DLOG("to %f\n", ws->height_factor[row]); - } - - - DLOG("Updating first (before = %f)\n", ws->height_factor[first]); - /* Convert 0 (for default width_factor) to actual numbers */ - if (ws->height_factor[first] == 0) - ws->height_factor[first] = ((float)ws_height / ws->rows) / new_unoccupied_y; - - DLOG("first->width = %d, pixels = %d\n", old_height, pixels); - ws->height_factor[first] *= (float)(old_height + pixels) / old_height; - DLOG("-> %f\n", ws->height_factor[first]); - - - DLOG("Updating second (before = %f)\n", ws->height_factor[second]); - if (ws->height_factor[second] == 0) - ws->height_factor[second] = ((float)ws_height / ws->rows) / new_unoccupied_y; - DLOG("middle = %f\n", ws->height_factor[second]); - DLOG("second->width = %d, pixels = %d\n", old_second_height, pixels); - ws->height_factor[second] *= (float)(old_second_height - pixels) / old_second_height; - DLOG("-> %f\n", ws->height_factor[second]); - - DLOG("new unoccupied_y = %d\n", get_unoccupied_y(ws)); - - DLOG("\n\n\n"); + adjust_container_factors(ws->width_factor, ws->rect.width, + get_unoccupied_x(ws), ws->cols, first, second, pixels); + } + else { + adjust_container_factors(ws->height_factor, workspace_height(ws), + get_unoccupied_y(ws), ws->rows, first, second, pixels); } render_layout(conn); From af9749b1b92f1703b175e79f2d0fc5557978993a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 23 Oct 2010 17:18:59 +0200 Subject: [PATCH 196/867] Bugfix: Properly call init_workspaces() when RandR is known but not present --- src/randr.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/randr.c b/src/randr.c index 7ce856d2..5a48b176 100644 --- a/src/randr.c +++ b/src/randr.c @@ -523,9 +523,13 @@ void initialize_randr(xcb_connection_t *conn, int *event_base) { const xcb_query_extension_reply_t *extreply; extreply = xcb_get_extension_data(conn, &xcb_randr_id); - if (!extreply->present) + if (!extreply->present) { disable_randr(conn); - else randr_query_outputs(conn); + init_workspaces(); + return; + } + + randr_query_outputs(conn); if (event_base != NULL) *event_base = extreply->first_event; From 2f6d2d09667497918040877b868459076d6291a0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 23 Oct 2010 17:18:41 +0200 Subject: [PATCH 197/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20setup=20event?= =?UTF-8?q?=20handler=20when=20RandR=20base=20event=20is=20not=20set?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainx.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/mainx.c b/src/mainx.c index 17f2bec8..4126fe58 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -527,17 +527,18 @@ int main(int argc, char *argv[], char *env[]) { translate_keysyms(); grab_all_keys(conn, false); - int randr_base; + int randr_base = -1; if (force_xinerama) { initialize_xinerama(conn); } else { DLOG("Checking for XRandR...\n"); initialize_randr(conn, &randr_base); - xcb_event_set_handler(&evenths, - randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY, - handle_screen_change, - NULL); + if (randr_base != -1) + xcb_event_set_handler(&evenths, + randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY, + handle_screen_change, + NULL); } xcb_flush(conn); From 582ec2a071a55c58e17fd19b3a44cd9b37ef16f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sat, 23 Oct 2010 18:20:08 -0200 Subject: [PATCH 198/867] More sensible lookup order loading the config. --- man/i3.man | 8 ++++---- src/config.c | 34 ++++++++++++++++++---------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/man/i3.man b/man/i3.man index 109248e5..00bad74b 100644 --- a/man/i3.man +++ b/man/i3.man @@ -156,10 +156,10 @@ Exits i3. When starting, i3 looks for configuration files in the following order: -1. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set) -2. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set) -3. ~/.i3/config -4. /etc/i3/config +1. ~/.i3/config +2. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set) +3. /etc/i3/config +4. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set) You can specify a custom path using the -c option. diff --git a/src/config.c b/src/config.c index 7e5f83b1..b1456e04 100644 --- a/src/config.c +++ b/src/config.c @@ -232,14 +232,20 @@ void switch_mode(xcb_connection_t *conn, const char *new_mode) { } /* - * Get the path of the first configuration file found. Checks the XDG folders - * first ($XDG_CONFIG_HOME, $XDG_CONFIG_DIRS), then the traditional paths. + * Get the path of the first configuration file found. 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() { - /* 1: check for $XDG_CONFIG_HOME/i3/config */ char *xdg_config_home, *xdg_config_dirs, *config_path; + /* 1: check the traditional path under the home directory */ + config_path = resolve_tilde("~/.i3/config"); + if (path_exists(config_path)) + return config_path; + + /* 2: check for $XDG_CONFIG_HOME/i3/config */ if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) xdg_config_home = "~/.config"; @@ -252,7 +258,12 @@ static char *get_config_path() { return config_path; free(config_path); - /* 2: check for $XDG_CONFIG_DIRS/i3/config */ + /* 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"; @@ -272,18 +283,9 @@ static char *get_config_path() { } free(buf); - /* 3: check traditional paths */ - config_path = resolve_tilde("~/.i3/config"); - if (path_exists(config_path)) - return config_path; - - config_path = sstrdup(SYSCONFDIR "/i3/config"); - if (!path_exists(config_path)) - die("Neither $XDG_CONFIG_HOME/i3/config, nor " - "$XDG_CONFIG_DIRS/i3/config, nor ~/.i3/config nor " - SYSCONFDIR "/i3/config exist."); - - return config_path; + 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)"); } /* From 1f7a53927ea4301b287d0ae111ea4fdc72e0ccd8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 6 Nov 2010 14:40:18 +0100 Subject: [PATCH 199/867] update debian/ to include all documentation/bump standars version --- debian/changelog | 6 ++++++ debian/control | 4 ++-- debian/i3-wm.docs | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 4eff0f88..e20d2690 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i3-wm (3.e-bf1-3) unstable; urgency=low + + * include keyboard-layer{1,2}.png in docs (Closes: #595295) + + -- Michael Stapelberg Wed, 03 Nov 2010 20:32:42 +0100 + i3-wm (3.e-bf1-2) unstable; urgency=low * debian: call dh_installwm to register as alternative for x-window-manager diff --git a/debian/control b/debian/control index 8b183d5d..d396ea77 100644 --- a/debian/control +++ b/debian/control @@ -3,8 +3,8 @@ Section: utils Priority: extra Maintainer: Michael Stapelberg DM-Upload-Allowed: yes -Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl -Standards-Version: 3.8.4 +Build-Depends: debhelper (>= 6), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl +Standards-Version: 3.9.1 Homepage: http://i3.zekjur.net/ Package: i3 diff --git a/debian/i3-wm.docs b/debian/i3-wm.docs index f283829c..d59c5736 100644 --- a/debian/i3-wm.docs +++ b/debian/i3-wm.docs @@ -12,3 +12,5 @@ docs/ipc.html docs/multi-monitor.html docs/wsbar.html docs/wsbar.png +docs/keyboard-layer1.png +docs/keyboard-layer2.png From c1d574f84ecab0e6e90e4d73329cc914da01b5c9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Nov 2010 17:33:59 +0100 Subject: [PATCH 200/867] enumerate workspaces when initializing outputs --- src/tree.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index f695f944..f170386e 100644 --- a/src/tree.c +++ b/src/tree.c @@ -59,6 +59,7 @@ void tree_init() { croot->type = CT_ROOT; Con *ws; + int c = 1; /* add the outputs */ TAILQ_FOREACH(output, &outputs, outputs) { if (!output->active) @@ -72,7 +73,8 @@ void tree_init() { /* add a workspace to this output */ ws = con_new(oc); ws->type = CT_WORKSPACE; - ws->name = strdup("1"); + asprintf(&(ws->name), "%d", c); + c++; ws->fullscreen_mode = CF_OUTPUT; ws->orientation = HORIZ; } From c6c084faa5024a227c77a55397896b35b38f88fc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Nov 2010 17:34:13 +0100 Subject: [PATCH 201/867] =?UTF-8?q?don=E2=80=99t=20close=20workspaces=20wh?= =?UTF-8?q?ich=20are=20still=20visible=20(multi-monitor)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/workspace.h | 4 ++-- src/workspace.c | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/include/workspace.h b/include/workspace.h index 28b7e571..a2c1836e 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -34,6 +34,7 @@ Con *workspace_get(const char *num); * */ void workspace_set_name(Workspace *ws, const char *name); +#endif /** * Returns true if the workspace is currently visible. Especially important for @@ -41,9 +42,8 @@ void workspace_set_name(Workspace *ws, const char *name); * workspaces. * */ -bool workspace_is_visible(Workspace *ws); +bool workspace_is_visible(Con *ws); -#endif /** Switches to the given workspace */ void workspace_show(const char *num); diff --git a/src/workspace.c b/src/workspace.c index 81492240..06f742d7 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -80,6 +80,7 @@ void workspace_set_name(Workspace *ws, const char *name) { else ws->text_width = 0; ws->utf8_name = label; } +#endif /* * Returns true if the workspace is currently visible. Especially important for @@ -87,10 +88,14 @@ void workspace_set_name(Workspace *ws, const char *name) { * workspaces. * */ -bool workspace_is_visible(Workspace *ws) { - return (ws->output != NULL && ws->output->current_workspace == ws); +bool workspace_is_visible(Con *ws) { + Con *output = con_get_output(ws); + if (output == NULL) + return false; + Con *fs = con_get_fullscreen_con(output); + LOG("workspace visible? fs = %p, ws = %p\n", fs, ws); + return (fs == ws); } -#endif /* * XXX: we need to clean up all this recursive walking code. @@ -197,8 +202,11 @@ void workspace_show(const char *num) { if (TAILQ_EMPTY(&(old->nodes_head))) { - LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); - tree_close(old, false); + /* check if this workspace is currently visible */ + if (!workspace_is_visible(old)) { + LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); + tree_close(old, false); + } } con_focus(next); From 6eb7f2a01d881470148952d18d42073dc873d6a8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Nov 2010 18:41:54 +0100 Subject: [PATCH 202/867] lexer/parser: implement 'border' command --- include/data.h | 1 + src/cmdparse.l | 1 + src/cmdparse.y | 29 +++++++++++++++++++++++++++-- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/include/data.h b/include/data.h index 3d32a396..ae499126 100644 --- a/include/data.h +++ b/include/data.h @@ -316,6 +316,7 @@ struct Con { enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode; enum { L_DEFAULT = 0, L_STACKED = 1, L_TABBED = 2 } layout; + enum { BS_NORMAL = 0, BS_NONE = 1, BS_1PIXEL = 3 } border_style; /** floating? (= not in tiling layout) This cannot be simply a bool * because we want to keep track of whether the status was set by the * application (by setting _NET_WM_WINDOW_TYPE appropriately) or by the diff --git a/src/cmdparse.l b/src/cmdparse.l index 3b3aefe2..9a236efc 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -91,6 +91,7 @@ stacked { return TOK_STACKED; } stacking { return TOK_STACKED; } tabbed { return TOK_TABBED; } border { return TOK_BORDER; } +normal { return TOK_NORMAL; } none { return TOK_NONE; } 1pixel { return TOK_1PIXEL; } mode { return TOK_MODE; } diff --git a/src/cmdparse.y b/src/cmdparse.y index 5dba6f59..a4ed00bb 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -109,6 +109,7 @@ char *parse_cmd(const char *new) { %token TOK_STACKED "stacked" %token TOK_TABBED "tabbed" %token TOK_BORDER "border" +%token TOK_NORMAL "normal" %token TOK_NONE "none" %token TOK_1PIXEL "1pixel" %token TOK_MODE "mode" @@ -284,8 +285,7 @@ operation: | exit | restart | reload - /* - | border */ + | border | layout | restore | move @@ -479,6 +479,31 @@ window_mode: | TOK_TOGGLE { $$ = TOK_TOGGLE; } ; +border: + TOK_BORDER WHITESPACE border_style + { + printf("border style should be changed to %d\n", $3); + owindow *current; + + /* check if the match is empty, not if the result is empty */ + if (match_is_empty(¤t_match)) + focused->border_style = $3; + else { + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + current->con->border_style = $3; + } + } + } + ; + +border_style: + TOK_NORMAL { $$ = BS_NORMAL; } + | TOK_NONE { $$ = BS_NONE; } + | TOK_1PIXEL { $$ = BS_1PIXEL; } + ; + + level: TOK_LEVEL WHITESPACE level_direction { From ea4e3e7682723510bbe9dd009112e198bc149d2f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Nov 2010 19:07:35 +0100 Subject: [PATCH 203/867] parser: bugfix: initialize match when parsing new command --- src/cmdparse.y | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cmdparse.y b/src/cmdparse.y index a4ed00bb..bcf144cd 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -71,6 +71,7 @@ char *parse_cmd(const char *new) { cmdyy_scan_string(new); + match_init(¤t_match); context = scalloc(sizeof(struct context)); context->filename = "cmd"; FREE(json_output); From d248f352677f654e7a197f2b28320ce91064fe0c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Nov 2010 19:16:38 +0100 Subject: [PATCH 204/867] include border style in ipc tree dump --- src/ipc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ipc.c b/src/ipc.c index 97fecd81..eed63bd4 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -146,6 +146,9 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("layout"); y(integer, con->layout); + ystr("border"); + y(integer, con->border_style); + ystr("rect"); y(map_open); ystr("x"); From 66dc8883fd26a448ff061ae47e7a9be443430f9a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Nov 2010 19:37:32 +0100 Subject: [PATCH 205/867] make borders around every container configurable for debugging --- src/render.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/render.c b/src/render.c index e40b43eb..16b2cf29 100644 --- a/src/render.c +++ b/src/render.c @@ -4,6 +4,10 @@ #include "all.h" +/* change this to 'true' if you want to have additional borders around every + * container (for debugging purposes) */ +static bool show_debug_borders = false; + /* * "Renders" the given container (and its children), meaning that all rects are * updated correctly. Note that this function does not call any xcb_* @@ -24,10 +28,15 @@ void render_con(Con *con) { /* Copy container rect, subtract container border */ /* This is the actually usable space inside this container for clients */ Rect rect = con->rect; - rect.x += 2; - rect.y += 2; - rect.width -= 2 * 2; - rect.height -= 2 * 2; + + /* Display a border if this is a leaf node. For container nodes, we don’t + * draw borders (except when in debug mode) */ + if (show_debug_borders) { + rect.x += 2; + rect.y += 2; + rect.width -= 2 * 2; + rect.height -= 2 * 2; + } int x = rect.x; int y = rect.y; From 0e264cb5c41c7b314964d8b25e54076d97ed8bf8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Nov 2010 19:37:43 +0100 Subject: [PATCH 206/867] implement different border styles Wow, that actually was easy :). --- src/render.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/render.c b/src/render.c index 16b2cf29..469ceaa8 100644 --- a/src/render.c +++ b/src/render.c @@ -51,11 +51,12 @@ void render_con(Con *con) { /* depending on the border style, the rect of the child window * needs to be smaller */ Rect *inset = &(con->window_rect); - *inset = (Rect){0, 0, con->rect.width, con->rect.height}; - /* TODO: different border styles */ - inset->x += 2; - inset->width -= 2 * 2; - inset->height -= 2; + + if (con->border_style == BS_NORMAL) + *inset = (Rect){2, 0, con->rect.width - (2 * 2), con->rect.height - 2}; + else if (con->border_style == BS_1PIXEL) + *inset = (Rect){1, 1, con->rect.width - 2, con->rect.height - 1}; + else *inset = (Rect){0, 0, con->rect.width, con->rect.height}; /* Obey the aspect ratio, if any */ if (con->proportional_height != 0 && @@ -131,7 +132,7 @@ void render_con(Con *con) { } /* first we have the decoration, if this is a leaf node */ - if (con_is_leaf(child)) { + if (con_is_leaf(child) && child->border_style == BS_NORMAL) { printf("that child is a leaf node, subtracting deco\n"); /* TODO: make a function for relative coords? */ child->deco_rect.x = child->rect.x - con->rect.x; From e07fee447232d375aa913754875dd3bff9cc9a88 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Nov 2010 20:22:55 +0100 Subject: [PATCH 207/867] =?UTF-8?q?parser:=20don=E2=80=99t=20exit(1)=20on?= =?UTF-8?q?=20invalid=20command,=20use=20better=20error=20message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cmdparse.y | 12 +++++------- testcases/Makefile | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index bcf144cd..d79698e4 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -49,8 +49,7 @@ static char *json_output; void cmdyyerror(const char *error_message) { ELOG("\n"); ELOG("CMD: %s\n", error_message); - ELOG("CMD: in file \"%s\", line %d:\n", - context->filename, context->line_number); + ELOG("CMD: in command:\n"); ELOG("CMD: %s\n", context->line_copy); ELOG("CMD: "); for (int c = 1; c <= context->last_column; c++) @@ -66,9 +65,6 @@ int cmdyywrap() { } char *parse_cmd(const char *new) { - - //const char *new = "[level-up workspace] attach $output, focus"; - cmdyy_scan_string(new); match_init(¤t_match); @@ -76,8 +72,10 @@ char *parse_cmd(const char *new) { context->filename = "cmd"; FREE(json_output); if (cmdyyparse() != 0) { - fprintf(stderr, "Could not parse configfile\n"); - exit(1); + fprintf(stderr, "Could not parse command\n"); + FREE(context->line_copy); + free(context); + return; } printf("done, json output = %s\n", json_output); diff --git a/testcases/Makefile b/testcases/Makefile index 462ca397..06a7cfa1 100644 --- a/testcases/Makefile +++ b/testcases/Makefile @@ -1,5 +1,5 @@ test: - PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/*.t + PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/34*.t all: test From df2ded08d87d6b65e591e8f502ab97e826848816 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Nov 2010 20:26:53 +0100 Subject: [PATCH 208/867] add testcase for invalid commands --- testcases/t/34-invalid-command.t | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 testcases/t/34-invalid-command.t diff --git a/testcases/t/34-invalid-command.t b/testcases/t/34-invalid-command.t new file mode 100644 index 00000000..12380d46 --- /dev/null +++ b/testcases/t/34-invalid-command.t @@ -0,0 +1,16 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# +# +use i3test tests => 1; + +my $i3 = i3("/tmp/nestedcons"); + +$i3->command("blargh!")->recv; + +my $tree = $i3->get_workspaces->recv; +my @nodes = @{$tree->{nodes}}; +ok(@nodes > 0, 'i3 still lives'); + +diag( "Testing i3, Perl $], $^X" ); From 6c699801ab6382f599127b3873c99808266f64cc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Nov 2010 20:36:37 +0100 Subject: [PATCH 209/867] bugfix: need to use window_type instead of type in t/04-floating.t --- testcases/t/04-floating.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testcases/t/04-floating.t b/testcases/t/04-floating.t index 4463bcbe..9607b2e2 100644 --- a/testcases/t/04-floating.t +++ b/testcases/t/04-floating.t @@ -17,7 +17,7 @@ my $window = $x->root->create_child( rect => [ 0, 0, 30, 30], background_color => '#C0C0C0', # replace the type with 'utility' as soon as the coercion works again in X11::XCB - type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), ); isa_ok($window, 'X11::XCB::Window'); @@ -40,7 +40,7 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 1, 1, 80, 90], background_color => '#C0C0C0', - type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), ); isa_ok($window, 'X11::XCB::Window'); From 56c6ba03598e8b386377735a7f523213e28df086 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Nov 2010 20:36:37 +0100 Subject: [PATCH 210/867] bugfix: need to use window_type instead of type in t/04-floating.t --- testcases/t/04-floating.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testcases/t/04-floating.t b/testcases/t/04-floating.t index 9049b9dc..65bd09ad 100644 --- a/testcases/t/04-floating.t +++ b/testcases/t/04-floating.t @@ -22,7 +22,7 @@ my $window = $x->root->create_child( rect => [ 0, 0, 30, 30], background_color => '#C0C0C0', # replace the type with 'utility' as soon as the coercion works again in X11::XCB - type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), ); isa_ok($window, 'X11::XCB::Window'); @@ -45,7 +45,7 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 1, 1, 80, 90], background_color => '#C0C0C0', - type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), ); isa_ok($window, 'X11::XCB::Window'); From 57e602a97ce1157c0d3c2d9fb049263d967c4cd5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Nov 2010 20:55:26 +0100 Subject: [PATCH 211/867] respect position in geometry of floating windows --- src/manage.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/manage.c b/src/manage.c index 134abe9e..e81d906a 100644 --- a/src/manage.c +++ b/src/manage.c @@ -190,10 +190,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_SPLASH])) { LOG("This window is a dialog window, setting floating\n"); + nc->rect.x = geom->x; + nc->rect.y = geom->y; /* We respect the geometry wishes of floating windows, as long as they * are bigger than our minimal useful size (75x50). */ nc->rect.width = max(geom->width, 75); nc->rect.height = max(geom->height, 50); + LOG("geometry = %d x %d\n", nc->rect.width, nc->rect.height); floating_enable(nc, false); } From 0723876429839435385a1131944239f3dd02c614 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Nov 2010 20:55:43 +0100 Subject: [PATCH 212/867] only re-position floating clients mapped to (0, 0), add pixels for decoration --- src/floating.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/floating.c b/src/floating.c index 8f144113..ab41ed60 100644 --- a/src/floating.c +++ b/src/floating.c @@ -34,6 +34,11 @@ void floating_enable(Con *con, bool automatic) { Con *nc = con_new(NULL); nc->parent = con_get_workspace(con); nc->rect = con->rect; + /* add pixels for the decoration */ + /* TODO: don’t add them when the user automatically puts new windows into + * 1pixel/borderless mode */ + nc->rect.height += 17 + 2; + nc->rect.width += 4; nc->orientation = NO_ORIENTATION; nc->type = CT_FLOATING_CON; TAILQ_INSERT_TAIL(&(nc->parent->floating_head), nc, floating_windows); @@ -43,8 +48,16 @@ void floating_enable(Con *con, bool automatic) { con->old_parent = con->parent; con->parent = nc; con->floating = FLOATING_USER_ON; - nc->rect.x = 400; - nc->rect.y = 400; + + /* Some clients (like GIMP’s color picker window) get mapped + * to (0, 0), so we push them to a reasonable position + * (centered over their leader) */ + if (nc->rect.x == 0 && nc->rect.y == 0) { + /* TODO: client_leader support */ + nc->rect.x = 400; + nc->rect.y = 400; + } + TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes); TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused); } From 8f7bd538d89d2a366a9224632ab522afeeaa8f64 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Nov 2010 21:41:10 +0100 Subject: [PATCH 213/867] implement configure requests, adapt testcase testcase does not pass 100% due to clients not being reparented correctly yet. --- include/con.h | 8 ++ include/handlers.h | 3 +- include/util.h | 1 + src/con.c | 17 ++++ src/handlers.c | 150 ++++++++++++------------------- src/main.c | 3 + src/render.c | 8 +- src/util.c | 7 ++ testcases/Makefile | 2 +- testcases/t/04-floating.t | 13 +-- testcases/t/12-floating-resize.t | 11 +-- 11 files changed, 108 insertions(+), 115 deletions(-) diff --git a/include/con.h b/include/con.h index fb862150..c4c4a270 100644 --- a/include/con.h +++ b/include/con.h @@ -129,4 +129,12 @@ int con_orientation(Con *con); */ Con *con_next_focused(Con *con); +/* + * Returns a "relative" Rect which contains the amount of pixels that need to + * be added to the original Rect to get the final position (obviously the + * amount of pixels for normal, 1pixel and borderless are different). + * + */ +Rect con_border_style_rect(Con *con); + #endif diff --git a/include/handlers.h b/include/handlers.h index b4a9486a..185320de 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -79,6 +79,7 @@ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_n */ int handle_screen_change(void *prophs, xcb_connection_t *conn, xcb_generic_event_t *e); +#endif /** * Configure requests are received when the application wants to resize @@ -90,7 +91,7 @@ int handle_screen_change(void *prophs, xcb_connection_t *conn, */ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event); -#endif + /** * Our window decorations were unmapped. That means, the window will be killed * now, so we better clean up before. diff --git a/include/util.h b/include/util.h index cca6985d..8a399d45 100644 --- a/include/util.h +++ b/include/util.h @@ -37,6 +37,7 @@ while (0) int min(int a, int b); int max(int a, int b); bool rect_contains(Rect rect, uint32_t x, uint32_t y); +Rect rect_add(Rect a, Rect b); /** * Updates *destination with new_value and returns true if it was changed or false diff --git a/src/con.c b/src/con.c index 4b0b2307..02d46db5 100644 --- a/src/con.c +++ b/src/con.c @@ -430,3 +430,20 @@ Con *con_next_focused(Con *con) { return next; } + +/* + * Returns a "relative" Rect which contains the amount of pixels that need to + * be added to the original Rect to get the final position (obviously the + * amount of pixels for normal, 1pixel and borderless are different). + * + */ +Rect con_border_style_rect(Con *con) { + if (con->border_style == BS_NORMAL) + return (Rect){2, 0, -(2 * 2), -2}; + + if (con->border_style == BS_1PIXEL) + return (Rect){1, 1, -2, -2}; + + if (con->border_style == BS_NONE) + return (Rect){0, 0, 0, 0}; +} diff --git a/src/handlers.c b/src/handlers.c index 0e497b04..a339abdf 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -274,7 +274,7 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve x_push_changes(croot); return 1; } -#if 0 + /* * Configure requests are received when the application wants to resize windows on their own. * @@ -282,108 +282,70 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve * */ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event) { - DLOG("window 0x%08x wants to be at %dx%d with %dx%d\n", - event->window, event->x, event->y, event->width, event->height); + Con *con; - Client *client = table_get(&by_child, event->window); - if (client == NULL) { - uint32_t mask = 0; - uint32_t values[7]; - int c = 0; + DLOG("window 0x%08x wants to be at %dx%d with %dx%d\n", + event->window, event->x, event->y, event->width, event->height); + + /* For unmanaged windows, we just execute the configure request. As soon as + * it gets mapped, we will take over anyways. */ + if ((con = con_by_window_id(event->window)) == NULL) { + DLOG("Configure request for unmanaged window, can do that.\n"); + + uint32_t mask = 0; + uint32_t values[7]; + int c = 0; #define COPY_MASK_MEMBER(mask_member, event_member) do { \ - if (event->value_mask & mask_member) { \ - mask |= mask_member; \ - values[c++] = event->event_member; \ - } \ + if (event->value_mask & mask_member) { \ + mask |= mask_member; \ + values[c++] = event->event_member; \ + } \ } while (0) - COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_X, x); - COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_Y, y); - COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_WIDTH, width); - COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_HEIGHT, height); - COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_BORDER_WIDTH, border_width); - COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_SIBLING, sibling); - COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_STACK_MODE, stack_mode); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_X, x); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_Y, y); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_WIDTH, width); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_HEIGHT, height); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_BORDER_WIDTH, border_width); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_SIBLING, sibling); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_STACK_MODE, stack_mode); - xcb_configure_window(conn, event->window, mask, values); - xcb_flush(conn); + xcb_configure_window(conn, event->window, mask, values); + xcb_flush(conn); - return 1; + return 1; + } + + DLOG("Configure request!\n"); + if (con_is_floating(con) && con_is_leaf(con)) { + /* we actually need to apply the size/position changes to the *parent* + * container */ + Rect bsr = con_border_style_rect(con); + if (con->border_style == BS_NORMAL) + bsr.height -= 17; + con = con->parent; + DLOG("Container is a floating leaf node, will do that.\n"); + if (event->value_mask & XCB_CONFIG_WINDOW_X) { + con->rect.x = event->x + (-1) * bsr.x; + DLOG("proposed x = %d, new x is %d\n", event->x, con->rect.x); } - - if (client->fullscreen) { - DLOG("Client is in fullscreen mode\n"); - - Rect child_rect = client->workspace->rect; - child_rect.x = child_rect.y = 0; - fake_configure_notify(conn, child_rect, client->child); - - return 1; + if (event->value_mask & XCB_CONFIG_WINDOW_Y) { + con->rect.y = event->y + (-1) * bsr.y; + DLOG("proposed y = %d, new y is %d\n", event->y, con->rect.y); } - - /* Floating clients can be reconfigured */ - if (client_is_floating(client)) { - i3Font *font = load_font(conn, config.font); - int mode = (client->container != NULL ? client->container->mode : MODE_DEFAULT); - /* TODO: refactor this code. we need a function to translate - * coordinates of child_rect/rect. */ - - if (event->value_mask & XCB_CONFIG_WINDOW_X) { - if (mode == MODE_STACK || mode == MODE_TABBED) { - client->rect.x = event->x - 2; - } else { - if (client->titlebar_position == TITLEBAR_OFF && client->borderless) - client->rect.x = event->x; - else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) - client->rect.x = event->x - 1; - else client->rect.x = event->x - 2; - } - } - if (event->value_mask & XCB_CONFIG_WINDOW_Y) { - if (mode == MODE_STACK || mode == MODE_TABBED) { - client->rect.y = event->y - 2; - } else { - if (client->titlebar_position == TITLEBAR_OFF && client->borderless) - client->rect.y = event->y; - else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) - client->rect.y = event->y - 1; - else client->rect.y = event->y - font->height - 2 - 2; - } - } - if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH) { - if (mode == MODE_STACK || mode == MODE_TABBED) { - client->rect.width = event->width + 2 + 2; - } else { - if (client->titlebar_position == TITLEBAR_OFF && client->borderless) - client->rect.width = event->width; - else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) - client->rect.width = event->width + (1 + 1); - else client->rect.width = event->width + (2 + 2); - } - } - if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT) { - if (mode == MODE_STACK || mode == MODE_TABBED) { - client->rect.height = event->height + 2; - } else { - if (client->titlebar_position == TITLEBAR_OFF && client->borderless) - client->rect.height = event->height; - else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) - client->rect.height = event->height + (1 + 1); - else client->rect.height = event->height + (font->height + 2 + 2) + 2; - } - } - - DLOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n", - client->rect.x, client->rect.y, client->rect.width, client->rect.height); - - /* Push the new position/size to X11 */ - reposition_client(conn, client); - resize_client(conn, client); - xcb_flush(conn); - - return 1; + if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH) { + con->rect.width = event->width + (-1) * bsr.width; + DLOG("proposed width = %d, new width is %d\n", event->width, con->rect.width); } + if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT) { + con->rect.height = event->height + (-1) * bsr.height; + DLOG("proposed height = %d, new height is %d\n", event->height, con->rect.height); + } + tree_render(); + } + return 1; +#if 0 /* Dock clients can be reconfigured in their height */ if (client->dock) { DLOG("Reconfiguring height of this dock client\n"); @@ -413,7 +375,9 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure fake_absolute_configure_notify(conn, client); return 1; +#endif } +#if 0 /* * Configuration notifies are only handled because we need to set up ignore for diff --git a/src/main.c b/src/main.c index a0eb9192..417c2b27 100644 --- a/src/main.c +++ b/src/main.c @@ -218,6 +218,9 @@ int main(int argc, char *argv[]) { for us is _NET_WM_STATE, we honour _NET_WM_STATE_FULLSCREEN */ xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL); + /* Configure request = window tried to change size on its own */ + xcb_event_set_configure_request_handler(&evenths, handle_configure_request, NULL); + /* Setup NetWM atoms */ #define GET_ATOM(name) \ do { \ diff --git a/src/render.c b/src/render.c index 469ceaa8..523b9188 100644 --- a/src/render.c +++ b/src/render.c @@ -51,12 +51,8 @@ void render_con(Con *con) { /* depending on the border style, the rect of the child window * needs to be smaller */ Rect *inset = &(con->window_rect); - - if (con->border_style == BS_NORMAL) - *inset = (Rect){2, 0, con->rect.width - (2 * 2), con->rect.height - 2}; - else if (con->border_style == BS_1PIXEL) - *inset = (Rect){1, 1, con->rect.width - 2, con->rect.height - 1}; - else *inset = (Rect){0, 0, con->rect.width, con->rect.height}; + *inset = (Rect){0, 0, con->rect.width, con->rect.height}; + *inset = rect_add(*inset, con_border_style_rect(con)); /* Obey the aspect ratio, if any */ if (con->proportional_height != 0 && diff --git a/src/util.c b/src/util.c index c8f4ee38..fc3ada5f 100644 --- a/src/util.c +++ b/src/util.c @@ -38,6 +38,13 @@ bool rect_contains(Rect rect, uint32_t x, uint32_t y) { y <= (rect.y + rect.height)); } +Rect rect_add(Rect a, Rect b) { + return (Rect){a.x + b.x, + a.y + b.y, + a.width + b.width, + a.height + b.height}; +} + /* * Updates *destination with new_value and returns true if it was changed or false * if it was the same diff --git a/testcases/Makefile b/testcases/Makefile index 06a7cfa1..6d7898b8 100644 --- a/testcases/Makefile +++ b/testcases/Makefile @@ -1,5 +1,5 @@ test: - PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/34*.t + PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/12*.t all: test diff --git a/testcases/t/04-floating.t b/testcases/t/04-floating.t index 9607b2e2..d16fa8b7 100644 --- a/testcases/t/04-floating.t +++ b/testcases/t/04-floating.t @@ -1,7 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 10; +use i3test tests => 11; use X11::XCB qw(:all); use Time::HiRes qw(sleep); @@ -29,8 +29,8 @@ sleep(0.25); my ($absolute, $top) = $window->rect; ok($window->mapped, 'Window is mapped'); -ok($absolute->{width} >= 75, 'i3 raised the width to 75'); -ok($absolute->{height} >= 50, 'i3 raised the height to 50'); +cmp_ok($absolute->{width}, '>=', 75, 'i3 raised the width to 75'); +cmp_ok($absolute->{height}, '>=', 50, 'i3 raised the height to 50'); ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)'); @@ -51,10 +51,11 @@ sleep(0.25); ($absolute, $top) = $window->rect; -ok($absolute->{width} == 80, "i3 let the width at 80"); -ok($absolute->{height} == 90, "i3 let the height at 90"); +cmp_ok($absolute->{width}, '==', 80, "i3 let the width at 80"); +cmp_ok($absolute->{height}, '==', 90, "i3 let the height at 90"); -ok($top->{x} == 1 && $top->{y} == 1, "i3 mapped it to (1,1)"); +cmp_ok($top->{x}, '==', 1, 'i3 mapped it to x=1'); +cmp_ok($top->{y}, '==', 1, 'i3 mapped it to y=1'); $window->unmap; diff --git a/testcases/t/12-floating-resize.t b/testcases/t/12-floating-resize.t index a5fb3c57..42ca43c0 100644 --- a/testcases/t/12-floating-resize.t +++ b/testcases/t/12-floating-resize.t @@ -12,9 +12,6 @@ BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } -SKIP: { - skip "border styles not yet implemented", 14; - my $x = X11::XCB::Connection->new; my $i3 = i3("/tmp/nestedcons"); @@ -31,7 +28,7 @@ my $window = $x->root->create_child( rect => [ 0, 0, 30, 30], background_color => '#C0C0C0', # replace the type with 'utility' as soon as the coercion works again in X11::XCB - type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), ); isa_ok($window, 'X11::XCB::Window'); @@ -70,13 +67,11 @@ sub test_resize { test_resize; # Test borderless -$i3->command('bb')->recv; +$i3->command('border none')->recv; test_resize; # Test with 1-px-border -$i3->command('bp')->recv; +$i3->command('border 1pixel')->recv; test_resize; - -} From 1bb6906c56f22a7510211e66311869581d8ee2d1 Mon Sep 17 00:00:00 2001 From: Baptiste Daroussin Date: Fri, 12 Nov 2010 23:04:30 +0100 Subject: [PATCH 214/867] Fix build on freebsd (uint32_t unknown if stdint.h not included) --- src/cfgparse.l | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cfgparse.l b/src/cfgparse.l index fedf286e..7d583b94 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -10,6 +10,7 @@ */ #include #include +#include #include "cfgparse.tab.h" #include From ad9be5402a47691486f0b55528a7501432ff31e5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Nov 2010 23:46:03 +0100 Subject: [PATCH 215/867] Implement support for WM_CLIENT_LEADER --- include/data.h | 4 ++++ include/handlers.h | 2 +- include/window.h | 6 ++++++ src/floating.c | 15 ++++++++++++--- src/handlers.c | 32 +++++++++++++------------------- src/main.c | 3 +++ src/manage.c | 4 +--- src/window.c | 19 +++++++++++++++++++ 8 files changed, 59 insertions(+), 26 deletions(-) diff --git a/include/data.h b/include/data.h index ae499126..7acbb52c 100644 --- a/include/data.h +++ b/include/data.h @@ -212,6 +212,10 @@ struct xoutput { struct Window { xcb_window_t id; + /** Holds the xcb_window_t (just an ID) for the leader window (logical + * parent for toolwindows and similar floating windows) */ + xcb_window_t leader; + char *class_class; char *class_instance; diff --git a/include/handlers.h b/include/handlers.h index 185320de..80096e2e 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -191,6 +191,7 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *reply); +#endif /** * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a @@ -200,6 +201,5 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *prop); -#endif #endif diff --git a/include/window.h b/include/window.h index 044bde70..071f4e28 100644 --- a/include/window.h +++ b/include/window.h @@ -24,4 +24,10 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop); */ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop); +/** + * Updates the CLIENT_LEADER (logical parent window). + * + */ +void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop); + #endif diff --git a/src/floating.c b/src/floating.c index ab41ed60..ea39f507 100644 --- a/src/floating.c +++ b/src/floating.c @@ -53,9 +53,18 @@ void floating_enable(Con *con, bool automatic) { * to (0, 0), so we push them to a reasonable position * (centered over their leader) */ if (nc->rect.x == 0 && nc->rect.y == 0) { - /* TODO: client_leader support */ - nc->rect.x = 400; - nc->rect.y = 400; + Con *leader; + 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); + } else { + /* center the window on workspace as fallback */ + Con *ws = nc->parent; + 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); + } } TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes); diff --git a/src/handlers.c b/src/handlers.c index a339abdf..d8b230f8 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -846,6 +846,7 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_ return 1; } +#endif /* * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a @@ -854,25 +855,18 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_ */ int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *prop) { - if (prop == NULL) { - prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, - false, window, WM_CLIENT_LEADER, WINDOW, 0, 32), NULL); - if (prop == NULL) - return 1; - } - - Client *client = table_get(&by_child, window); - if (client == NULL) - return 1; - - xcb_window_t *leader = xcb_get_property_value(prop); - if (leader == NULL) - return 1; - - DLOG("Client leader changed to %08x\n", *leader); - - client->leader = *leader; + if (prop == NULL) { + prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, + false, window, WM_CLIENT_LEADER, WINDOW, 0, 32), NULL); + if (prop == NULL) + return 1; + } + Con *con; + if ((con = con_by_window_id(window)) == NULL || con->window == NULL) return 1; + + window_update_leader(con->window, prop); + + return 1; } -#endif diff --git a/src/main.c b/src/main.c index 417c2b27..6ca3075e 100644 --- a/src/main.c +++ b/src/main.c @@ -267,6 +267,9 @@ int main(int argc, char *argv[]) { /* Watch WM_NORMAL_HINTS (aspect ratio, size increments, …) */ xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL); + /* Watch WM_CLIENT_LEADER (= logical parent window for toolbars etc.) */ + xcb_property_set_handler(&prophs, atoms[WM_CLIENT_LEADER], UINT_MAX, handle_clientleader_change, NULL); + /* Set up the atoms we support */ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 7, atoms); /* Set up the window manager’s name */ diff --git a/src/manage.c b/src/manage.c index e81d906a..0fb2fc9b 100644 --- a/src/manage.c +++ b/src/manage.c @@ -144,6 +144,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL)); window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL)); window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL)); + window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL)); xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, wm_type_cookie, NULL); if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DOCK])) { @@ -321,9 +322,6 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, preply = xcb_get_property_reply(conn, class_cookie, NULL); handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply); - preply = xcb_get_property_reply(conn, leader_cookie, NULL); - handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply); - /* if WM_CLIENT_LEADER is set, we put the new window on the * same window as its leader. This might be overwritten by * assignments afterwards. */ diff --git a/src/window.c b/src/window.c index 93812b3f..e22fb1c0 100644 --- a/src/window.c +++ b/src/window.c @@ -96,3 +96,22 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop) { win->name_json = strdup(new_name); win->name_len = strlen(new_name); } + +/** + * Updates the CLIENT_LEADER (logical parent window). + * + */ +void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop) { + if (prop == NULL || xcb_get_property_value_length(prop) == 0) { + DLOG("prop == NULL\n"); + return; + } + + xcb_window_t *leader = xcb_get_property_value(prop); + if (leader == NULL) + return; + + DLOG("Client leader changed to %08x\n", *leader); + + win->leader = *leader; +} From 432073dbe5f89660f727789ee824d9a0b05479da Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Nov 2010 01:19:21 +0100 Subject: [PATCH 216/867] implement support for WM_TRANSIENT_FOR, expand testcase --- include/data.h | 1 + include/handlers.h | 2 - include/window.h | 6 +++ src/handlers.c | 57 +++++++++++----------- src/main.c | 3 ++ src/manage.c | 11 ++++- src/window.c | 19 ++++++++ testcases/t/14-client-leader.t | 87 +++++++++++++++++++++++++++++++++- 8 files changed, 153 insertions(+), 33 deletions(-) diff --git a/include/data.h b/include/data.h index 7acbb52c..d34fd733 100644 --- a/include/data.h +++ b/include/data.h @@ -215,6 +215,7 @@ struct Window { /** Holds the xcb_window_t (just an ID) for the leader window (logical * parent for toolwindows and similar floating windows) */ xcb_window_t leader; + xcb_window_t transient_for; char *class_class; char *class_instance; diff --git a/include/handlers.h b/include/handlers.h index 80096e2e..71e2bc92 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -179,7 +179,6 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, */ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *reply); -#if 0 /** * Handles the transient for hints set by a window, signalizing that this @@ -191,7 +190,6 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *reply); -#endif /** * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a diff --git a/include/window.h b/include/window.h index 071f4e28..6621a169 100644 --- a/include/window.h +++ b/include/window.h @@ -30,4 +30,10 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop); */ void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop); +/** + * Updates the TRANSIENT_FOR (logical parent window). + * + */ +void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop); + #endif diff --git a/src/handlers.c b/src/handlers.c index d8b230f8..72cc68c5 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -811,8 +811,6 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t return 1; } -#if 0 - /* * Handles the transient for hints set by a window, signalizing that this window is a popup window * for some other window. @@ -821,33 +819,34 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t * */ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, - xcb_atom_t name, xcb_get_property_reply_t *reply) { - Client *client = table_get(&by_child, window); - if (client == NULL) { - DLOG("No such client\n"); - return 1; - } - - xcb_window_t transient_for; - - if (reply != NULL) { - if (!xcb_get_wm_transient_for_from_reply(&transient_for, reply)) - return 1; - } else { - if (!xcb_get_wm_transient_for_reply(conn, xcb_get_wm_transient_for_unchecked(conn, window), - &transient_for, NULL)) - return 1; - } - - if (client->floating == FLOATING_AUTO_OFF) { - DLOG("This is a popup window, putting into floating\n"); - toggle_floating_mode(conn, client, true); - } + xcb_atom_t name, xcb_get_property_reply_t *prop) { + Con *con; + if ((con = con_by_window_id(window)) == NULL || con->window == NULL) { + DLOG("No such window\n"); return 1; -} + } + + if (prop == NULL) { + prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, + false, window, WM_TRANSIENT_FOR, WINDOW, 0, 32), NULL); + if (prop == NULL) + return 1; + } + + window_update_transient_for(con->window, prop); + + // TODO: put window in floating mode if con->window->transient_for != XCB_NONE: +#if 0 + if (client->floating == FLOATING_AUTO_OFF) { + DLOG("This is a popup window, putting into floating\n"); + toggle_floating_mode(conn, client, true); + } #endif + return 1; +} + /* * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a * toolwindow (or similar) and to which window it belongs (logical parent). @@ -855,6 +854,10 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_ */ int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *prop) { + Con *con; + if ((con = con_by_window_id(window)) == NULL || con->window == NULL) + return 1; + if (prop == NULL) { prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, false, window, WM_CLIENT_LEADER, WINDOW, 0, 32), NULL); @@ -862,10 +865,6 @@ int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state return 1; } - Con *con; - if ((con = con_by_window_id(window)) == NULL || con->window == NULL) - return 1; - window_update_leader(con->window, prop); return 1; diff --git a/src/main.c b/src/main.c index 6ca3075e..787ff6fb 100644 --- a/src/main.c +++ b/src/main.c @@ -270,6 +270,9 @@ int main(int argc, char *argv[]) { /* Watch WM_CLIENT_LEADER (= logical parent window for toolbars etc.) */ xcb_property_set_handler(&prophs, atoms[WM_CLIENT_LEADER], UINT_MAX, handle_clientleader_change, NULL); + /* Watch WM_TRANSIENT_FOR property (to which client this popup window belongs) */ + xcb_property_set_handler(&prophs, WM_TRANSIENT_FOR, UINT_MAX, handle_transient_for, NULL); + /* Set up the atoms we support */ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 7, atoms); /* Set up the window manager’s name */ diff --git a/src/manage.c b/src/manage.c index 0fb2fc9b..0307ea7a 100644 --- a/src/manage.c +++ b/src/manage.c @@ -79,13 +79,14 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie, utf8_title_cookie, title_cookie, - class_cookie, leader_cookie; + class_cookie, leader_cookie, transient_cookie; wm_type_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX); strut_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX); state_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STATE], UINT32_MAX); utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_NAME], 128); leader_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[WM_CLIENT_LEADER], UINT32_MAX); + transient_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_TRANSIENT_FOR, UINT32_MAX); title_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_NAME, 128); class_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_CLASS, 128); /* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */ @@ -145,6 +146,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL)); window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL)); window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL)); + window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL)); xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, wm_type_cookie, NULL); if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DOCK])) { @@ -185,12 +187,19 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* set floating if necessary */ + bool want_floating = false; if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DIALOG]) || xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_UTILITY]) || xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_TOOLBAR]) || xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_SPLASH])) { LOG("This window is a dialog window, setting floating\n"); + want_floating = true; + } + if (cwindow->transient_for != XCB_NONE) + want_floating = true; + + if (want_floating) { nc->rect.x = geom->x; nc->rect.y = geom->y; /* We respect the geometry wishes of floating windows, as long as they diff --git a/src/window.c b/src/window.c index e22fb1c0..1cf167d8 100644 --- a/src/window.c +++ b/src/window.c @@ -115,3 +115,22 @@ void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop) { win->leader = *leader; } + +/** + * Updates the TRANSIENT_FOR (logical parent window). + * + */ +void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop) { + if (prop == NULL || xcb_get_property_value_length(prop) == 0) { + DLOG("prop == NULL\n"); + return; + } + + xcb_window_t transient_for; + if (!xcb_get_wm_transient_for_from_reply(&transient_for, prop)) + return; + + DLOG("Transient for changed to %08x\n", transient_for); + + win->transient_for = transient_for; +} diff --git a/testcases/t/14-client-leader.t b/testcases/t/14-client-leader.t index fb330d96..ef488f5a 100644 --- a/testcases/t/14-client-leader.t +++ b/testcases/t/14-client-leader.t @@ -1,7 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 3; +use i3test tests => 7; use X11::XCB qw(:all); use Time::HiRes qw(sleep); @@ -15,6 +15,89 @@ my $i3 = i3("/tmp/nestedcons"); my $tmp = get_unused_workspace(); $i3->command("workspace $tmp")->recv; +#################################################################################### +# first part: test if a floating window will be correctly positioned above its leader +# +# This is verified by opening two windows, then opening a floating window above the +# right one, then above the left one. If the floating windows are all positioned alike, +# one of both (depending on your screen resolution) will be positioned wrong. +#################################################################################### + +my $left = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [0, 0, 30, 30], + background_color => '#FF0000', +); + +$left->name('Left'); +$left->map; + +my $right = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [0, 0, 30, 30], + background_color => '#FF0000', +); + +$right->name('Right'); +$right->map; + +sleep 0.25; + +my ($abs, $rgeom) = $right->rect; + +my $child = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#C0C0C0', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), +); + +$child->name('Child window'); +$child->client_leader($right); +$child->map; + +sleep 0.25; + +my $cgeom; +($abs, $cgeom) = $child->rect; +cmp_ok($cgeom->x, '>=', $rgeom->x, 'Child X >= right container X'); + +my $child2 = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#C0C0C0', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), +); + +$child2->name('Child window 2'); +$child2->client_leader($left); +$child2->map; + +sleep 0.25; + +($abs, $cgeom) = $child2->rect; +cmp_ok(($cgeom->x + $cgeom->width), '<', $rgeom->x, 'child above left window'); + +# check wm_transient_for + + +my $fwindow = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#FF0000', +); + +$fwindow->transient_for($right); +$fwindow->map; + +sleep 0.25; + +my ($absolute, $top) = $fwindow->rect; +ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)'); + +SKIP: { + skip "(workspace placement by client_leader not yet implemented)", 3; + ##################################################################### # Create a parent window ##################################################################### @@ -55,3 +138,5 @@ isnt($x->input_focus, $child->id, "Child window focused"); $i3->command("workspace $tmp")->recv; is($x->input_focus, $child->id, "Child window focused"); + +} From 4aef09ab348f1f7dcb18b6d4cdadbf6093d706c6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Nov 2010 14:33:58 +0100 Subject: [PATCH 217/867] t/16-nestedcons.t: add 'border' key --- testcases/t/16-nestedcons.t | 1 + 1 file changed, 1 insertion(+) diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t index ecc20d82..1e57994f 100644 --- a/testcases/t/16-nestedcons.t +++ b/testcases/t/16-nestedcons.t @@ -25,6 +25,7 @@ my $expected = { focus => ignore(), focused => 0, urgent => 0, + border => 0, 'floating-nodes' => ignore(), }; From dc10c670603e3b2bc3a1e98ecf60336ac90399a6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Nov 2010 14:55:11 +0100 Subject: [PATCH 218/867] Bugfix: Close containers which are empty due to a move (Thanks fernando) --- include/con.h | 6 ++++++ src/con.c | 20 +++++++++++++++++--- src/render.c | 6 ++---- src/tree.c | 6 ++++++ testcases/t/24-move.t | 18 +++++++++++++++++- 5 files changed, 48 insertions(+), 8 deletions(-) diff --git a/include/con.h b/include/con.h index c4c4a270..65e64212 100644 --- a/include/con.h +++ b/include/con.h @@ -75,6 +75,12 @@ Con *con_by_frame_id(xcb_window_t frame); */ Con *con_for_window(i3Window *window, Match **store_match); +/** + * Returns the number of children of this container. + * + */ +int con_num_children(Con *con); + /** * Attaches the given container to the given parent. This happens when moving * a container or when inserting a new container at a specific place in the diff --git a/src/con.c b/src/con.c index 02d46db5..beaf82ce 100644 --- a/src/con.c +++ b/src/con.c @@ -286,6 +286,20 @@ Con *con_for_window(i3Window *window, Match **store_match) { return NULL; } +/* + * Returns the number of children of this container. + * + */ +int con_num_children(Con *con) { + Con *child; + int children = 0; + + TAILQ_FOREACH(child, &(con->nodes_head), nodes) + children++; + + return children; +} + /* * Updates the percent attribute of the children of the given container. This * function needs to be called when a window is added or removed from a @@ -294,9 +308,7 @@ Con *con_for_window(i3Window *window, Match **store_match) { */ void con_fix_percent(Con *con, int action) { Con *child; - int children = 0; - TAILQ_FOREACH(child, &(con->nodes_head), nodes) - children++; + int children = con_num_children(con); /* TODO: better document why this math works */ double fix; if (action == WINDOW_ADD) @@ -446,4 +458,6 @@ Rect con_border_style_rect(Con *con) { if (con->border_style == BS_NONE) return (Rect){0, 0, 0, 0}; + + assert(false); } diff --git a/src/render.c b/src/render.c index 523b9188..dcd5f58e 100644 --- a/src/render.c +++ b/src/render.c @@ -19,10 +19,7 @@ static bool show_debug_borders = false; void render_con(Con *con) { printf("currently rendering node %p / %s / layout %d\n", con, con->name, con->layout); - int children = 0; - Con *child; - TAILQ_FOREACH(child, &(con->nodes_head), nodes) - children++; + int children = con_num_children(con); printf("children: %d, orientation = %d\n", children, con->orientation); /* Copy container rect, subtract container border */ @@ -103,6 +100,7 @@ void render_con(Con *con) { return; } + Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) { /* default layout */ diff --git a/src/tree.c b/src/tree.c index f170386e..244b5ba9 100644 --- a/src/tree.c +++ b/src/tree.c @@ -364,6 +364,7 @@ void tree_next(char way, orientation_t orientation) { void tree_move(char way, orientation_t orientation) { /* 1: get the first parent with the same orientation */ Con *parent = focused->parent; + Con *old_parent = parent; if (focused->type == CT_WORKSPACE) return; bool level_changed = false; @@ -431,4 +432,9 @@ void tree_move(char way, orientation_t orientation) { TAILQ_INSERT_HEAD(&(next->parent->focus_head), focused, focused); /* TODO: don’t influence focus handling? */ } + + if (con_num_children(old_parent) == 0) { + DLOG("Old container empty after moving. Let's close it\n"); + tree_close(old_parent, false); + } } diff --git a/testcases/t/24-move.t b/testcases/t/24-move.t index a98622f2..e316db13 100644 --- a/testcases/t/24-move.t +++ b/testcases/t/24-move.t @@ -7,7 +7,7 @@ # 3) move a container inside another container # 4) move a container in a different direction so that we need to go up in tree # -use i3test tests => 16; +use i3test tests => 17; use X11::XCB qw(:all); my $i3 = i3("/tmp/nestedcons"); @@ -109,4 +109,20 @@ $i3->command('move after h')->recv; $content = get_ws_content($tmp); is(@{$content}, 2, 'two nodes on this workspace'); +###################################################################### +# 4) Move a container horizontally when inside a vertical split container. +# The container will be moved to the workspace level and the old vsplit +# container needs to be closed. Verify that it will be closed. +###################################################################### + +my $otmp = get_unused_workspace(); +$i3->command("workspace $otmp")->recv; + +$i3->command("open")->recv; +$i3->command("split v")->recv; +$i3->command("move after h")->recv; + +$content = get_ws_content($otmp); +is(@{$content}, 1, 'only one nodes on this workspace'); + diag( "Testing i3, Perl $], $^X" ); From 1a2134d4c3aa6d1ee6a4fcfaa68e5ec580ddf4ae Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Nov 2010 17:29:20 +0100 Subject: [PATCH 219/867] add testcases/complete-run.pl, a script for conveniently running the testsuite (or parts of it) --- testcases/complete-run.pl | 56 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100755 testcases/complete-run.pl diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl new file mode 100755 index 00000000..aeacc284 --- /dev/null +++ b/testcases/complete-run.pl @@ -0,0 +1,56 @@ +#!/usr/bin/env perl +# vim:ts=4:sw=4:expandtab + +use strict; +use warnings; +use v5.10; +use DateTime; +use Data::Dumper; +use Cwd qw(abs_path getcwd); +use Proc::Background; +use TAP::Harness; +use TAP::Parser::Aggregator; +use File::Basename qw(basename); + +my $i3cmd = "export DISPLAY=:0; exec " . abs_path("../i3") . " -V -d all -c " . abs_path("../i3.config"); + +# 1: get a list of all testcases +my $curdir = getcwd(); +my @testfiles = @ARGV; + +# if no files were passed on command line, run all tests +if (@testfiles == 0) { + chdir "t"; + push @testfiles, "t/$_" while (<*.t>); + chdir $curdir; +} + +# 2: create an output directory for this test-run +my $outdir = "testsuite-"; +$outdir .= DateTime->now->strftime("%Y-%m-%d-%H-%M-%S-"); +$outdir .= `git describe --tags`; +chomp($outdir); +mkdir($outdir) or die "Could not create $outdir"; +unlink("latest") if -e "latest"; +symlink("$outdir", "latest") or die "Could not symlink latest to $outdir"; + +# 3: run all tests +my $harness = TAP::Harness->new({ + verbosity => 1, + lib => [ 't/lib' ] +}); +my $aggregator = TAP::Parser::Aggregator->new(); +$aggregator->start(); +for my $t (@testfiles) { + my $logpath = "$outdir/i3-log-for-" . basename($t); + my $cmd = "$i3cmd >$logpath 2>&1"; + + my $process = Proc::Background->new($cmd); + say "testing $t with logfile $logpath"; + $harness->aggregate_tests($aggregator, [ $t ]); + kill(9, $process->pid) or die "could not kill i3"; +} +$aggregator->stop(); + +# 4: print summary +$harness->summary($aggregator); From 53b98fdc7e5f71fd05dcf8ac92d4883b05b919c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sat, 13 Nov 2010 15:15:15 -0200 Subject: [PATCH 220/867] Paint the window decorations using the theme. --- src/x.c | 60 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/src/x.c b/src/x.c index a2e9c71e..ae24f35b 100644 --- a/src/x.c +++ b/src/x.c @@ -222,30 +222,54 @@ void x_window_kill(xcb_window_t window) { * */ void x_draw_decoration(Con *con) { - Con *parent; + if (!con_is_leaf(con) || (con->type != CT_CON && con->type != CT_FLOATING_CON)) + return; - parent = con->parent; + /* 1: find out which colors to use */ + struct Colortriple *color; + if (con->urgent) + color = &config.client.urgent; + else if (con == focused) + color = &config.client.focused; + else if (con == TAILQ_FIRST(&(con->parent->focus_head))) + color = &config.client.focused_inactive; + else + color = &config.client.unfocused; - if (con == focused) - xcb_change_gc_single(conn, parent->gc, XCB_GC_FOREGROUND, get_colorpixel("#FF0000")); - else xcb_change_gc_single(conn, parent->gc, XCB_GC_FOREGROUND, get_colorpixel("#0C0C0C")); + Con *parent = con->parent; + + /* 2: draw a rectangle in border color around the client */ + if (con->border_style != BS_NONE) { + xcb_change_gc_single(conn, con->gc, XCB_GC_FOREGROUND, color->background); + xcb_rectangle_t rect = { 0, 0, con->rect.width, con->rect.height }; + xcb_poly_fill_rectangle(conn, con->frame, con->gc, 1, &rect); + } + if (con->border_style != BS_NORMAL) + return; + + /* 3: paint the bar */ + xcb_change_gc_single(conn, parent->gc, XCB_GC_FOREGROUND, color->background); xcb_rectangle_t drect = { con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height }; xcb_poly_fill_rectangle(conn, parent->frame, parent->gc, 1, &drect); - if (con->window == NULL) + /* 4: draw the two lines in border color */ + xcb_draw_line(conn, parent->frame, parent->gc, color->border, + con->deco_rect.x, /* x */ + con->deco_rect.y, /* y */ + con->deco_rect.x + con->deco_rect.width, /* to_x */ + con->deco_rect.y); /* to_y */ + xcb_draw_line(conn, parent->frame, parent->gc, color->border, + con->deco_rect.x, /* x */ + con->deco_rect.y + con->deco_rect.height - 1, /* y */ + con->deco_rect.x + con->deco_rect.width, /* to_x */ + con->deco_rect.y + con->deco_rect.height - 1); /* to_y */ + + /* 5: draw the title */ + struct Window *win = con->window; + if (win == NULL || win ->name_x == NULL) return; - - i3Window *win = con->window; - - if (win->name_x == NULL) { - LOG("not rendering decoration, not yet known\n"); - return; - } - - - LOG("should render text %s onto %p / %s\n", win->name_json, parent, parent->name); - - xcb_change_gc_single(conn, parent->gc, XCB_GC_FOREGROUND, get_colorpixel("#FFFFFF")); + xcb_change_gc_single(conn, parent->gc, XCB_GC_BACKGROUND, color->background); + xcb_change_gc_single(conn, parent->gc, XCB_GC_FOREGROUND, color->text); if (win->uses_net_wm_name) xcb_image_text_16( conn, From 4cd6dd0303b07d921b304ac5136b169a2d0a2b26 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Nov 2010 20:07:49 +0100 Subject: [PATCH 221/867] =?UTF-8?q?port=20fernando=E2=80=99s=20custom=20ba?= =?UTF-8?q?ckground=20color=20patch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/config.h | 1 + src/cfgparse.l | 1 + src/cfgparse.y | 10 ++++++++++ src/con.c | 5 ++++- src/config.c | 1 + 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/include/config.h b/include/config.h index 36011f45..a435cb5b 100644 --- a/include/config.h +++ b/include/config.h @@ -112,6 +112,7 @@ struct Config { /* Color codes are stored here */ struct config_client { + uint32_t background; struct Colortriple focused; struct Colortriple focused_inactive; struct Colortriple unfocused; diff --git a/src/cfgparse.l b/src/cfgparse.l index 7d583b94..eb9c25c7 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -102,6 +102,7 @@ stack-limit { return TOKSTACKLIMIT; } cols { /* yylval.number = STACK_LIMIT_COLS; */return TOKSTACKLIMIT; } rows { /* yylval.number = STACK_LIMIT_ROWS; */return TOKSTACKLIMIT; } exec { BEGIN(BIND_AWS_COND); return TOKEXEC; } +client.background { BEGIN(COLOR_COND); yylval.single_color = &config.client.background; return TOKSINGLECOLOR; } client.focused { BEGIN(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; } client.focused_inactive { BEGIN(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; } client.unfocused { BEGIN(COLOR_COND); yylval.color = &config.client.unfocused; return TOKCOLOR; } diff --git a/src/cfgparse.y b/src/cfgparse.y index 99367adb..5de08bde 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -182,6 +182,7 @@ void parse_file(const char *f) { %union { int number; char *string; + uint32_t *single_color; struct Colortriple *color; struct Assignment *assignment; struct Binding *binding; @@ -210,6 +211,7 @@ void parse_file(const char *f) { %token TOKSET %token TOKIPCSOCKET "ipc_socket" %token TOKEXEC "exec" +%token TOKSINGLECOLOR %token TOKCOLOR %token TOKARROW "→" %token TOKMODE "mode" @@ -240,6 +242,7 @@ line: | assign | ipcsocket | exec + | single_color | color | terminal | font @@ -569,6 +572,13 @@ font: } ; +single_color: + TOKSINGLECOLOR WHITESPACE colorpixel + { + uint32_t *dest = $1; + *dest = $3; + } + ; color: TOKCOLOR WHITESPACE colorpixel WHITESPACE colorpixel WHITESPACE colorpixel diff --git a/src/con.c b/src/con.c index beaf82ce..8121bf0e 100644 --- a/src/con.c +++ b/src/con.c @@ -41,14 +41,17 @@ Con *con_new(Con *parent) { /* TODO: remove window coloring after test-phase */ LOG("color %s\n", colors[cnt]); new->name = strdup(colors[cnt]); +#if 0 uint32_t cp = get_colorpixel(colors[cnt]); cnt++; if ((cnt % (sizeof(colors) / sizeof(char*))) == 0) cnt = 0; +#endif x_con_init(new); - xcb_change_window_attributes(conn, new->frame, XCB_CW_BACK_PIXEL, &cp); + // TODO: this needs to be integrated into src/x.c and updated on config file reloads + xcb_change_window_attributes(conn, new->frame, XCB_CW_BACK_PIXEL, &config.client.background); TAILQ_INIT(&(new->floating_head)); TAILQ_INIT(&(new->nodes_head)); diff --git a/src/config.c b/src/config.c index 2e611e95..b37f013c 100644 --- a/src/config.c +++ b/src/config.c @@ -348,6 +348,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, x.text = get_colorpixel(ctext); \ } while (0) + config.client.background = get_colorpixel("#000000"); INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff"); INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff"); INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888"); From e6d1f181fdaa44d9735f67218fd74a753b5bc8f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Fri, 24 Sep 2010 22:01:56 -0300 Subject: [PATCH 222/867] Info about client.background for the user guide. --- docs/userguide | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/userguide b/docs/userguide index 5cb1fe58..7e2438f0 100644 --- a/docs/userguide +++ b/docs/userguide @@ -485,6 +485,21 @@ bar.unfocused:: bar.urgent:: A workspace which has at least one client with an activated urgency hint. +You can also specify the color to be used to paint the background of the client +windows. This color will be used to paint the window on top of which the client +will be rendered. + +*Syntax*: +----------------------- +client.background color +----------------------- + +Only clients that do not cover the whole area of this window expose the color +used to paint it. If you use a color other than black for your terminals, you +most likely want to set the client background color to the same color as your +terminal program's background color to avoid black gaps between the rendered +area of the termianal and the i3 border. + Colors are in HTML hex format (#rrggbb), see the following example: *Examples*: From 7c6f2dbfc65de1cf59960f62f4d212023a2978f1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Nov 2010 22:39:59 +0100 Subject: [PATCH 223/867] Rendering fixes for stacking mode --- include/con.h | 12 ++++++- src/con.c | 30 +++++++++++++---- src/render.c | 7 +++- src/tree.c | 3 +- src/x.c | 89 +++++++++++++++++++++++++++++++++++++++++++++------ 5 files changed, 121 insertions(+), 20 deletions(-) diff --git a/include/con.h b/include/con.h index 65e64212..bdf0b2a2 100644 --- a/include/con.h +++ b/include/con.h @@ -135,7 +135,7 @@ int con_orientation(Con *con); */ Con *con_next_focused(Con *con); -/* +/** * Returns a "relative" Rect which contains the amount of pixels that need to * be added to the original Rect to get the final position (obviously the * amount of pixels for normal, 1pixel and borderless are different). @@ -143,4 +143,14 @@ Con *con_next_focused(Con *con); */ Rect con_border_style_rect(Con *con); +/** + * Use this function to get a container’s border style. This is important + * because when inside a stack, the border style is always BS_NORMAL. + * For tabbed mode, the same applies, with one exception: when the container is + * borderless and the only element in the tabbed container, the border is not + * rendered. + * + */ +int con_border_style(Con *con); + #endif diff --git a/src/con.c b/src/con.c index 8121bf0e..e9232273 100644 --- a/src/con.c +++ b/src/con.c @@ -41,12 +41,10 @@ Con *con_new(Con *parent) { /* TODO: remove window coloring after test-phase */ LOG("color %s\n", colors[cnt]); new->name = strdup(colors[cnt]); -#if 0 - uint32_t cp = get_colorpixel(colors[cnt]); + //uint32_t cp = get_colorpixel(colors[cnt]); cnt++; if ((cnt % (sizeof(colors) / sizeof(char*))) == 0) cnt = 0; -#endif x_con_init(new); @@ -453,14 +451,32 @@ Con *con_next_focused(Con *con) { * */ Rect con_border_style_rect(Con *con) { - if (con->border_style == BS_NORMAL) + switch (con_border_style(con)) { + case BS_NORMAL: return (Rect){2, 0, -(2 * 2), -2}; - if (con->border_style == BS_1PIXEL) + case BS_1PIXEL: return (Rect){1, 1, -2, -2}; - if (con->border_style == BS_NONE) + case BS_NONE: return (Rect){0, 0, 0, 0}; - assert(false); + default: + assert(false); + } +} + +/* + * Use this function to get a container’s border style. This is important + * because when inside a stack, the border style is always BS_NORMAL. + * For tabbed mode, the same applies, with one exception: when the container is + * borderless and the only element in the tabbed container, the border is not + * rendered. + * + */ +int con_border_style(Con *con) { + if (con->parent->layout == L_STACKED) + return BS_NORMAL; + + return con->border_style; } diff --git a/src/render.c b/src/render.c index dcd5f58e..ddb3751a 100644 --- a/src/render.c +++ b/src/render.c @@ -167,8 +167,13 @@ void render_con(Con *con) { /* in a stacking container, we ensure the focused client is raised */ if (con->layout == L_STACKED) { Con *foc = TAILQ_FIRST(&(con->focus_head)); - if (foc != TAILQ_END(&(con->focus_head))) + if (foc != TAILQ_END(&(con->focus_head))) { + LOG("con %p is stacking, raising %p\n", con, foc); x_raise_con(foc); + /* by rendering the stacked container again, we handle the case + * that we have a non-leaf-container inside the stack. */ + render_con(foc); + } } TAILQ_FOREACH(child, &(con->floating_head), floating_windows) { diff --git a/src/tree.c b/src/tree.c index 244b5ba9..33788576 100644 --- a/src/tree.c +++ b/src/tree.c @@ -233,7 +233,7 @@ void tree_split(Con *con, orientation_t orientation) { /* if we are in a container whose parent contains only one * child and has the same orientation like we are trying to * set, this operation is a no-op to not confuse the user */ - if (parent->orientation == orientation && + if (con_orientation(parent) == orientation && TAILQ_NEXT(con, nodes) == TAILQ_END(&(parent->nodes_head))) { DLOG("Not splitting the same way again\n"); return; @@ -353,6 +353,7 @@ void tree_next(char way, orientation_t orientation) { while (!TAILQ_EMPTY(&(next->focus_head))) next = TAILQ_FIRST(&(next->focus_head)); + DLOG("focusing %p\n", next); con_focus(next); } diff --git a/src/x.c b/src/x.c index ae24f35b..566864ce 100644 --- a/src/x.c +++ b/src/x.c @@ -222,8 +222,13 @@ void x_window_kill(xcb_window_t window) { * */ void x_draw_decoration(Con *con) { - if (!con_is_leaf(con) || (con->type != CT_CON && con->type != CT_FLOATING_CON)) + /* this code needs to run for: + * • leaf containers + * • non-leaf containers which are in a stacking container + */ + if (!con_is_leaf(con) && con->parent->layout != L_STACKED) return; + DLOG("decoration should be rendered for con %p\n", con); /* 1: find out which colors to use */ struct Colortriple *color; @@ -237,15 +242,48 @@ void x_draw_decoration(Con *con) { color = &config.client.unfocused; Con *parent = con->parent; + int border_style = con_border_style(con); /* 2: draw a rectangle in border color around the client */ - if (con->border_style != BS_NONE) { + if (border_style != BS_NONE && con_is_leaf(con)) { + Rect br = con_border_style_rect(con); + Rect *r = &(con->rect); +#if 0 + DLOG("con->rect spans %d x %d\n", con->rect.width, con->rect.height); + DLOG("border_rect spans (%d, %d) with %d x %d\n", border_rect.x, border_rect.y, border_rect.width, border_rect.height); + DLOG("window_rect spans (%d, %d) with %d x %d\n", con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height); +#endif + + /* This polygon represents the border around the child window (left, + * bottom and right part). We don’t just fill the whole rectangle + * because some childs are not freely resizable and we want their + * background color to "shine through". */ xcb_change_gc_single(conn, con->gc, XCB_GC_FOREGROUND, color->background); - xcb_rectangle_t rect = { 0, 0, con->rect.width, con->rect.height }; - xcb_poly_fill_rectangle(conn, con->frame, con->gc, 1, &rect); + xcb_point_t points[] = { + { 0, 0 }, + { 0, r->height }, + { r->width, r->height }, + { r->width, 0 }, + { r->width + br.width + br.x, 0 }, + { r->width + br.width + br.x, r->height + br.height + br.y }, + { br.x, r->height + br.height }, + { br.x, 0 } + }; + xcb_fill_poly(conn, con->frame, con->gc, XCB_POLY_SHAPE_COMPLEX, XCB_COORD_MODE_ORIGIN, 8, points); + + /* 1pixel border needs an additional line at the top */ + if (border_style == BS_1PIXEL) { + xcb_rectangle_t topline = { br.x, 0, con->rect.width + br.width + br.x, br.y }; + xcb_poly_fill_rectangle(conn, con->frame, con->gc, 1, &topline); + } } - if (con->border_style != BS_NORMAL) + + /* if this is a borderless/1pixel window, we don’t * need to render the + * decoration. */ + if (border_style != BS_NORMAL) { + DLOG("border style not BS_NORMAL, aborting rendering of decoration\n"); return; + } /* 3: paint the bar */ xcb_change_gc_single(conn, parent->gc, XCB_GC_FOREGROUND, color->background); @@ -265,18 +303,49 @@ void x_draw_decoration(Con *con) { con->deco_rect.y + con->deco_rect.height - 1); /* to_y */ /* 5: draw the title */ - struct Window *win = con->window; - if (win == NULL || win ->name_x == NULL) - return; xcb_change_gc_single(conn, parent->gc, XCB_GC_BACKGROUND, color->background); xcb_change_gc_single(conn, parent->gc, XCB_GC_FOREGROUND, color->text); + + struct Window *win = con->window; + if (win == NULL || win->name_x == NULL) { + /* this is a non-leaf container, we need to make up a good description */ + // TODO: use a good description instead of just "another container" + xcb_image_text_8( + conn, + strlen("another container"), + parent->frame, + parent->gc, + con->deco_rect.x + 2, + con->deco_rect.y + 14, /* TODO: hardcoded */ + "another container" + ); + return; + } + + int indent_level = 0, + indent_mult = 0; + Con *il_parent = con->parent; + if (il_parent->type != L_STACKED) { + while (1) { + DLOG("il_parent = %p, layout = %d\n", il_parent, il_parent->layout); + if (il_parent->layout == L_STACKED) + indent_level++; + if (il_parent->type == CT_WORKSPACE) + break; + il_parent = il_parent->parent; + indent_mult++; + } + } + DLOG("indent_level = %d, indent_mult = %d\n", indent_level, indent_mult); + int indent_px = (indent_level * 5) * indent_mult; + if (win->uses_net_wm_name) xcb_image_text_16( conn, win->name_len, parent->frame, parent->gc, - con->deco_rect.x, + con->deco_rect.x + 2 + indent_px, con->deco_rect.y + 14, /* TODO: hardcoded */ (xcb_char2b_t*)win->name_x ); @@ -286,7 +355,7 @@ void x_draw_decoration(Con *con) { win->name_len, parent->frame, parent->gc, - con->deco_rect.x, + con->deco_rect.x + 2 + indent_px, con->deco_rect.y + 14, /* TODO: hardcoded */ win->name_x ); From a415d56048a18720bfdab8144652b87cb39ce626 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Nov 2010 01:45:05 +0100 Subject: [PATCH 224/867] parser: return a proper JSON reply on parse errors --- include/config.h | 2 ++ src/cmdparse.y | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/config.h b/include/config.h index a435cb5b..ab2dac87 100644 --- a/include/config.h +++ b/include/config.h @@ -35,6 +35,8 @@ struct context { char *line_copy; const char *filename; + const char *compact_error; + /* These are the same as in YYLTYPE */ int first_column; int last_column; diff --git a/src/cmdparse.y b/src/cmdparse.y index d79698e4..e5c9aa5c 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -58,6 +58,7 @@ void cmdyyerror(const char *error_message) { else printf(" "); printf("\n"); ELOG("\n"); + context->compact_error = sstrdup(error_message); } int cmdyywrap() { @@ -73,13 +74,17 @@ char *parse_cmd(const char *new) { FREE(json_output); if (cmdyyparse() != 0) { fprintf(stderr, "Could not parse command\n"); + asprintf(&json_output, "{\"success\":false, \"error\":\"%s at position %d\"}", + context->compact_error, context->first_column); FREE(context->line_copy); + FREE(context->compact_error); free(context); - return; + return json_output; } printf("done, json output = %s\n", json_output); FREE(context->line_copy); + FREE(context->compact_error); free(context); return json_output; } From e85bb09017c4b276259be37fe3c24da26c9b99bd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Nov 2010 13:53:47 +0100 Subject: [PATCH 225/867] set withdrawn/normal state when unmapping/mapping (for xprop/java) --- src/x.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/x.c b/src/x.c index 566864ce..25e132f3 100644 --- a/src/x.c +++ b/src/x.c @@ -403,18 +403,35 @@ static void x_push_node(Con *con) { if (state->mapped != con->mapped || (con->mapped && state->initial)) { if (!con->mapped) { xcb_void_cookie_t cookie; + if (con->window != NULL) { + /* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */ + long data[] = { XCB_WM_STATE_WITHDRAWN, XCB_NONE }; + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, + atoms[WM_STATE], atoms[WM_STATE], 32, 2, data); + } + cookie = xcb_unmap_window(conn, con->frame); LOG("unmapping container (serial %d)\n", cookie.sequence); /* Ignore enter_notifies which are generated when unmapping */ add_ignore_event(cookie.sequence); } else { xcb_void_cookie_t cookie; + + if (con->window != NULL) { + /* Set WM_STATE_NORMAL because GTK applications don’t want to + * drag & drop if we don’t. Also, xprop(1) needs it. */ + long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE }; + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, + atoms[WM_STATE], atoms[WM_STATE], 32, 2, data); + } + if (state->initial && con->window != NULL) { cookie = xcb_map_window(conn, con->window->id); LOG("mapping child window (serial %d)\n", cookie.sequence); /* Ignore enter_notifies which are generated when mapping */ add_ignore_event(cookie.sequence); } + cookie = xcb_map_window(conn, con->frame); LOG("mapping container (serial %d)\n", cookie.sequence); /* Ignore enter_notifies which are generated when mapping */ From 945632ddcb978db34cb0db3b5900b707af3750c7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Nov 2010 16:41:46 +0100 Subject: [PATCH 226/867] Implement setting the WM_NAME of i3 container windows for debugging --- include/x.h | 8 ++++++++ src/floating.c | 9 +++++++++ src/manage.c | 4 ++++ src/tree.c | 10 ++++++++++ src/workspace.c | 4 ++++ src/x.c | 28 ++++++++++++++++++++++++++++ 6 files changed, 63 insertions(+) diff --git a/include/x.h b/include/x.h index 1cd83fb8..43b0814b 100644 --- a/include/x.h +++ b/include/x.h @@ -65,4 +65,12 @@ void x_push_changes(Con *con); */ void x_raise_con(Con *con); +/** + * Sets the WM_NAME property (so, no UTF8, but used only for debugging anyways) + * of the given name. Used for properly tagging the windows for easily spotting + * i3 windows in xwininfo -root -all. + * + */ +void x_set_name(Con *con, const char *name); + #endif diff --git a/src/floating.c b/src/floating.c index ea39f507..756102b7 100644 --- a/src/floating.c +++ b/src/floating.c @@ -32,7 +32,16 @@ void floating_enable(Con *con, bool automatic) { /* 2: create a new container to render the decoration on, add * it as a floating window to the workspace */ Con *nc = con_new(NULL); + /* we need to set the parent afterwards instead of passing it as an + * argument to con_new() because nc would be inserted into the tiling layer + * otherwise. */ nc->parent = con_get_workspace(con); + + char *name; + asprintf(&name, "[i3 con] floatingcon around %p", con); + x_set_name(nc, name); + free(name); + nc->rect = con->rect; /* add pixels for the decoration */ /* TODO: don’t add them when the user automatically puts new windows into diff --git a/src/manage.c b/src/manage.c index 0307ea7a..344da4cc 100644 --- a/src/manage.c +++ b/src/manage.c @@ -185,6 +185,10 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki nc->window = cwindow; x_reinit(nc); + char *name; + asprintf(&name, "[i3 con] container around %p", cwindow); + x_set_name(nc, name); + free(name); /* set floating if necessary */ bool want_floating = false; diff --git a/src/tree.c b/src/tree.c index 33788576..696f77cc 100644 --- a/src/tree.c +++ b/src/tree.c @@ -70,11 +70,21 @@ void tree_init() { oc->type = CT_OUTPUT; oc->rect = output->rect; + char *name; + asprintf(&name, "[i3 con] output %s", oc->name); + x_set_name(oc, name); + free(name); + /* add a workspace to this output */ ws = con_new(oc); ws->type = CT_WORKSPACE; asprintf(&(ws->name), "%d", c); c++; + + asprintf(&name, "[i3 con] workspace %s", ws->name); + x_set_name(ws, name); + free(name); + ws->fullscreen_mode = CF_OUTPUT; ws->orientation = HORIZ; } diff --git a/src/workspace.c b/src/workspace.c index 06f742d7..d98c7562 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -39,6 +39,10 @@ Con *workspace_get(const char *num) { output = con_get_output(focused); LOG("got output %p\n", output); workspace = con_new(output); + char *name; + asprintf(&name, "[i3 con] workspace %s", num); + x_set_name(workspace, name); + free(name); workspace->type = CT_WORKSPACE; workspace->name = strdup(num); workspace->orientation = HORIZ; diff --git a/src/x.c b/src/x.c index 25e132f3..fe7ce8e0 100644 --- a/src/x.c +++ b/src/x.c @@ -30,6 +30,8 @@ typedef struct con_state { bool initial; + char *name; + CIRCLEQ_ENTRY(con_state) state; CIRCLEQ_ENTRY(con_state) old_state; } con_state; @@ -374,6 +376,14 @@ static void x_push_node(Con *con) { LOG("Pushing changes for node %p / %s\n", con, con->name); state = state_for_frame(con->frame); + if (state->name != NULL) { + DLOG("pushing name %s\n", state->name); + + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->frame, + WM_NAME, STRING, 8, strlen(state->name), state->name); + FREE(state->name); + } + /* reparent the child window (when the window was moved due to a sticky * container) */ if (state->need_reparent && con->window != NULL) { @@ -543,3 +553,21 @@ void x_raise_con(Con *con) { CIRCLEQ_REMOVE(&state_head, state, state); CIRCLEQ_INSERT_HEAD(&state_head, state, state); } + +/* + * Sets the WM_NAME property (so, no UTF8, but used only for debugging anyways) + * of the given name. Used for properly tagging the windows for easily spotting + * i3 windows in xwininfo -root -all. + * + */ +void x_set_name(Con *con, const char *name) { + struct con_state *state; + + if ((state = state_for_frame(con->frame)) == NULL) { + ELOG("window state not found\n"); + return; + } + + FREE(state->name); + state->name = sstrdup(name); +} From 510d1f78a15909dfc14d62f92a84f1db0724238f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Nov 2010 16:42:13 +0100 Subject: [PATCH 227/867] modify t/04-floating.t to reflect the new way we are doing decorations --- testcases/t/04-floating.t | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/testcases/t/04-floating.t b/testcases/t/04-floating.t index d16fa8b7..de104f2f 100644 --- a/testcases/t/04-floating.t +++ b/testcases/t/04-floating.t @@ -54,8 +54,10 @@ sleep(0.25); cmp_ok($absolute->{width}, '==', 80, "i3 let the width at 80"); cmp_ok($absolute->{height}, '==', 90, "i3 let the height at 90"); +# We need to compare the position with decorations due to the way +# we do decoration rendering (on the parent frame) in the tree branch cmp_ok($top->{x}, '==', 1, 'i3 mapped it to x=1'); -cmp_ok($top->{y}, '==', 1, 'i3 mapped it to y=1'); +cmp_ok($top->{y}, '==', 18, 'i3 mapped it to y=18'); $window->unmap; From 5d7344af8af0fe06fa3d07e25daa0c3577e881f9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Nov 2010 18:52:40 +0100 Subject: [PATCH 228/867] resize/unmap container x11 windows on demand (makes background images visible again) --- src/x.c | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/x.c b/src/x.c index fe7ce8e0..41707bf7 100644 --- a/src/x.c +++ b/src/x.c @@ -372,6 +372,7 @@ void x_draw_decoration(Con *con) { static void x_push_node(Con *con) { Con *current; con_state *state; + Rect rect = con->rect; LOG("Pushing changes for node %p / %s\n", con, con->name); state = state_for_frame(con->frame); @@ -384,6 +385,28 @@ static void x_push_node(Con *con) { FREE(state->name); } + if (con->window == NULL) { + /* Calculate the height of all window decorations which will be drawn on to + * this frame. */ + uint32_t max_y = 0, max_height = 0; + TAILQ_FOREACH(current, &(con->nodes_head), nodes) { + DLOG("Child's decoration is %d x %d, from (%d, %d)\n", + current->deco_rect.width, current->deco_rect.height, + current->deco_rect.x, current->deco_rect.y); + Rect *dr = &(current->deco_rect); + if (dr->y >= max_y && dr->height >= max_height) { + max_y = dr->y; + max_height = dr->height; + } + } + DLOG("bottom of decorations is %d\n", max_y + max_height); + rect.height = max_y + max_height; + if (rect.height == 0) { + DLOG("Unmapping container because it does not contain anything atm.\n"); + con->mapped = false; + } + } + /* reparent the child window (when the window was moved due to a sticky * container) */ if (state->need_reparent && con->window != NULL) { @@ -452,10 +475,10 @@ static void x_push_node(Con *con) { bool fake_notify = false; /* set new position if rect changed */ - if (memcmp(&(state->rect), &(con->rect), sizeof(Rect)) != 0) { - LOG("setting rect (%d, %d, %d, %d)\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height); - xcb_set_window_rect(conn, con->frame, con->rect); - memcpy(&(state->rect), &(con->rect), sizeof(Rect)); + if (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0) { + LOG("setting rect (%d, %d, %d, %d)\n", rect.x, rect.y, rect.width, rect.height); + xcb_set_window_rect(conn, con->frame, rect); + memcpy(&(state->rect), &rect, sizeof(Rect)); fake_notify = true; } From f0efb3737e83e4600a55007c9705c2e6a7cd1743 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Nov 2010 20:11:46 +0100 Subject: [PATCH 229/867] =?UTF-8?q?don=E2=80=99t=20remove=20floating=20con?= =?UTF-8?q?tainer=20twice=20(it=E2=80=99s=20already=20removed=20in=20con?= =?UTF-8?q?=5Fdetach)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tree.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/tree.c b/src/tree.c index 696f77cc..f7f7a759 100644 --- a/src/tree.c +++ b/src/tree.c @@ -181,10 +181,7 @@ void tree_close(Con *con, bool kill_window) { if (con_is_floating(con)) { DLOG("Container was floating, killing floating container\n"); - - TAILQ_REMOVE(&(parent->parent->floating_head), parent, floating_windows); - TAILQ_REMOVE(&(parent->parent->focus_head), parent, focused); - tree_close(parent, false); + tree_close(parent, false, false); next = NULL; } From d760a1c7b2c3afbdad5f60dfe53a40171dedb67e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Nov 2010 20:14:09 +0100 Subject: [PATCH 230/867] =?UTF-8?q?Bugfix:=20don=E2=80=99t=20kill=20parent?= =?UTF-8?q?=20when=20currently=20in=20tree=5Fclose()=20for=20a=20child=20o?= =?UTF-8?q?f=20this=20parent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/tree.h | 2 +- src/cmdparse.y | 2 +- src/floating.c | 2 +- src/handlers.c | 2 +- src/tree.c | 14 ++++++++------ src/workspace.c | 2 +- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/include/tree.h b/include/tree.h index 449f67ef..f04e9e62 100644 --- a/include/tree.h +++ b/include/tree.h @@ -76,7 +76,7 @@ void tree_move(char way, orientation_t orientation); * Closes the given container including all children * */ -void tree_close(Con *con, bool kill_window); +void tree_close(Con *con, bool kill_window, bool dont_kill_parent); /** * Loads tree from ~/.i3/_restart.json (used for in-place restarts). diff --git a/src/cmdparse.y b/src/cmdparse.y index e5c9aa5c..7f9dfd46 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -382,7 +382,7 @@ kill: else { TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); - tree_close(current->con, true); + tree_close(current->con, true, false); } } diff --git a/src/floating.c b/src/floating.c index 756102b7..005f2092 100644 --- a/src/floating.c +++ b/src/floating.c @@ -95,7 +95,7 @@ void floating_disable(Con *con, bool automatic) { /* 2: kill parent container */ TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows); TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused); - tree_close(con->parent, false); + tree_close(con->parent, false, false); /* 3: re-attach to previous parent */ con->parent = con->old_parent; diff --git a/src/handlers.c b/src/handlers.c index 72cc68c5..f5324dfe 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -436,7 +436,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti return 1; } - tree_close(con, false); + tree_close(con, false, false); tree_render(); x_push_changes(croot); return 1; diff --git a/src/tree.c b/src/tree.c index f7f7a759..3f860923 100644 --- a/src/tree.c +++ b/src/tree.c @@ -143,7 +143,7 @@ static void fix_floating_parent(Con *con, Con *vanishing) { * Closes the given container including all children * */ -void tree_close(Con *con, bool kill_window) { +void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { Con *parent = con->parent; /* check floating clients and adjust old_parent if necessary */ @@ -158,7 +158,8 @@ void tree_close(Con *con, bool kill_window) { * in their parent’s nodes_head */ while (!TAILQ_EMPTY(&(con->nodes_head))) { child = TAILQ_FIRST(&(con->nodes_head)); - tree_close(child, kill_window); + DLOG("killing child=%p\n", child); + tree_close(child, kill_window, true); } if (con->window != NULL) { @@ -199,12 +200,13 @@ void tree_close(Con *con, bool kill_window) { con_focus(next); /* check if the parent container is empty now and close it */ - if (parent->type != CT_WORKSPACE && + if (!dont_kill_parent && + parent->type != CT_WORKSPACE && TAILQ_EMPTY(&(parent->nodes_head))) { DLOG("Closing empty parent container\n"); /* TODO: check if this container would swallow any other client and * don’t close it automatically. */ - tree_close(parent, false); + tree_close(parent, false, false); } } @@ -220,7 +222,7 @@ void tree_close_con() { } /* Kill con */ - tree_close(focused, true); + tree_close(focused, true, false); } /* @@ -443,6 +445,6 @@ void tree_move(char way, orientation_t orientation) { if (con_num_children(old_parent) == 0) { DLOG("Old container empty after moving. Let's close it\n"); - tree_close(old_parent, false); + tree_close(old_parent, false, false); } } diff --git a/src/workspace.c b/src/workspace.c index d98c7562..cdea9827 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -209,7 +209,7 @@ void workspace_show(const char *num) { /* check if this workspace is currently visible */ if (!workspace_is_visible(old)) { LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); - tree_close(old, false); + tree_close(old, false, false); } } From 055bd18142ebab2499d443be17a23b2c9e1df31f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Nov 2010 20:15:12 +0100 Subject: [PATCH 231/867] Bugfix: after the first UnmapNotify, unignore the event --- src/handlers.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/handlers.c b/src/handlers.c index f5324dfe..7dd73acb 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -28,6 +28,30 @@ void add_ignore_event(const int sequence) { SLIST_INSERT_HEAD(&ignore_events, event, ignore_events); } +/* + * Unignores the given sequence. Called when unmap events (generated by + * reparenting) should be ignored and the unmap event actually happens, in + * order to not ignore too many unmap events (leading to ghost window + * decorations). + * + */ +static void unignore_event(const int sequence) { + struct Ignore_Event *event; + for (event = SLIST_FIRST(&ignore_events); + event != SLIST_END(&ignore_events); + event = SLIST_NEXT(event, ignore_events)) { + if (event->sequence != sequence) + continue; + + DLOG("Unignoring sequence number %d\n", sequence); + struct Ignore_Event *save = event; + event = SLIST_NEXT(event, ignore_events); + SLIST_REMOVE(&ignore_events, save, Ignore_Event, ignore_events); + free(save); + break; + } +} + /* * Checks if the given sequence is ignored and returns true if so. * @@ -428,6 +452,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti DLOG("UnmapNotify for 0x%08x (received from 0x%08x), serial %d\n", event->window, event->event, event->sequence); if (ignored) { DLOG("Ignoring UnmapNotify (generated by reparenting)\n"); + unignore_event(event->sequence); return 1; } Con *con = con_by_window_id(event->window); From 33eb00d6aec0d9e6e2b65ea6c6b41fc9573172f7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Nov 2010 20:15:51 +0100 Subject: [PATCH 232/867] automatically set windows with client_leader to floating --- src/manage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manage.c b/src/manage.c index 344da4cc..4724d704 100644 --- a/src/manage.c +++ b/src/manage.c @@ -200,7 +200,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki want_floating = true; } - if (cwindow->transient_for != XCB_NONE) + if (cwindow->transient_for != XCB_NONE || cwindow->leader != XCB_NONE) want_floating = true; if (want_floating) { From d4017031352381c2d345d32a4a84a176a3b5fce8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Nov 2010 21:17:27 +0100 Subject: [PATCH 233/867] Bugfix: use rectangles instead of a polygon to avoid strange rendering errors With the polygon, when using pidgin, having the buddy list in the middle of the screen, 200 px width, full screen high, then opening the manage accounts window, the decorations of the buddy list were visible on the accounts window. --- src/x.c | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/x.c b/src/x.c index 41707bf7..fe83a848 100644 --- a/src/x.c +++ b/src/x.c @@ -256,23 +256,17 @@ void x_draw_decoration(Con *con) { DLOG("window_rect spans (%d, %d) with %d x %d\n", con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height); #endif - /* This polygon represents the border around the child window (left, - * bottom and right part). We don’t just fill the whole rectangle - * because some childs are not freely resizable and we want their - * background color to "shine through". */ + /* These rectangles represents the border around the child window + * (left, bottom and right part). We don’t just fill the whole + * rectangle because some childs are not freely resizable and we want + * their background color to "shine through". */ xcb_change_gc_single(conn, con->gc, XCB_GC_FOREGROUND, color->background); - xcb_point_t points[] = { - { 0, 0 }, - { 0, r->height }, - { r->width, r->height }, - { r->width, 0 }, - { r->width + br.width + br.x, 0 }, - { r->width + br.width + br.x, r->height + br.height + br.y }, - { br.x, r->height + br.height }, - { br.x, 0 } + xcb_rectangle_t borders[] = { + { 0, 0, br.x, r->height }, + { 0, r->height + br.height + br.y, r->width, r->height }, + { r->width + br.width + br.x, 0, r->width, r->height } }; - xcb_fill_poly(conn, con->frame, con->gc, XCB_POLY_SHAPE_COMPLEX, XCB_COORD_MODE_ORIGIN, 8, points); - + xcb_poly_fill_rectangle(conn, con->frame, con->gc, 3, borders); /* 1pixel border needs an additional line at the top */ if (border_style == BS_1PIXEL) { xcb_rectangle_t topline = { br.x, 0, con->rect.width + br.width + br.x, br.y }; From ffff4b159f468e171d875424afccc47b5df4d36f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Nov 2010 21:43:31 +0100 Subject: [PATCH 234/867] add testcase for focus problem when toggling floating/tiling mode Test 3 does not yet pass. --- testcases/t/35-floating-focus.t | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 testcases/t/35-floating-focus.t diff --git a/testcases/t/35-floating-focus.t b/testcases/t/35-floating-focus.t new file mode 100644 index 00000000..7efe2472 --- /dev/null +++ b/testcases/t/35-floating-focus.t @@ -0,0 +1,23 @@ +#!perl +# vim:ts=4:sw=4:expandtab + +use i3test tests => 3; +use Time::HiRes qw(sleep); + +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +$i3->command("open")->recv; +$i3->command("open")->recv; + +my @content = @{get_ws_content($tmp)}; +cmp_ok(@content, '==', 2, 'two containers opened'); +cmp_ok($content[1]->{focused}, '==', 1, 'Second container focused'); + +$i3->command("mode floating")->recv; +$i3->command("mode tiling")->recv; + +@content = @{get_ws_content($tmp)}; +cmp_ok($content[1]->{focused}, '==', 1, 'Second container still focused after mode toggle'); From a27af527066fb5c05a8064a8342864f33def0d99 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Nov 2010 22:35:44 +0100 Subject: [PATCH 235/867] fix floating focus behaviour, extend testcase --- src/con.c | 19 ++++++++++-- src/floating.c | 4 +++ src/x.c | 2 +- testcases/t/35-floating-focus.t | 51 ++++++++++++++++++++++++++++++++- 4 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/con.c b/src/con.c index e9232273..2487dd83 100644 --- a/src/con.c +++ b/src/con.c @@ -422,9 +422,24 @@ Con *con_next_focused(Con *con) { /* floating containers are attached to a workspace, so we focus either the * next floating container (if any) or the workspace itself. */ if (con->type == CT_FLOATING_CON) { + DLOG("selecting next for CT_FLOATING_CON\n"); next = TAILQ_NEXT(con, floating_windows); - if (next == TAILQ_END(&(parent->floating_head))) - next = con_get_workspace(con); + if (next == TAILQ_END(&(parent->floating_head))) { + Con *ws = con_get_workspace(con); + next = ws; + DLOG("no more floating containers for next = %p, restoring workspace focus\n", next); + while (next != TAILQ_END(&(ws->focus_head)) && !TAILQ_EMPTY(&(next->focus_head))) { + next = TAILQ_FIRST(&(next->focus_head)); + if (next == con) { + DLOG("skipping container itself, we want the next client\n"); + next = TAILQ_NEXT(next, focused); + } + } + if (next == TAILQ_END(&(ws->focus_head))) { + DLOG("Focus list empty, returning NULL\n"); + next = NULL; + } + } return next; } diff --git a/src/floating.c b/src/floating.c index 005f2092..2292d89b 100644 --- a/src/floating.c +++ b/src/floating.c @@ -78,6 +78,8 @@ void floating_enable(Con *con, bool automatic) { TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes); TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused); + // TODO: don’t influence focus handling when Con was not focused before. + con_focus(con); } void floating_disable(Con *con, bool automatic) { @@ -105,6 +107,8 @@ void floating_disable(Con *con, bool automatic) { con->floating = FLOATING_USER_OFF; con_fix_percent(con->parent, WINDOW_ADD); + // TODO: don’t influence focus handling when Con was not focused before. + con_focus(con); } diff --git a/src/x.c b/src/x.c index fe83a848..5c854d01 100644 --- a/src/x.c +++ b/src/x.c @@ -372,7 +372,7 @@ static void x_push_node(Con *con) { state = state_for_frame(con->frame); if (state->name != NULL) { - DLOG("pushing name %s\n", state->name); + DLOG("pushing name %s for con %p\n", state->name, con); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->frame, WM_NAME, STRING, 8, strlen(state->name), state->name); diff --git a/testcases/t/35-floating-focus.t b/testcases/t/35-floating-focus.t index 7efe2472..3b6cfdae 100644 --- a/testcases/t/35-floating-focus.t +++ b/testcases/t/35-floating-focus.t @@ -1,7 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 3; +use i3test tests => 9; use Time::HiRes qw(sleep); my $i3 = i3("/tmp/nestedcons"); @@ -9,6 +9,10 @@ my $i3 = i3("/tmp/nestedcons"); my $tmp = get_unused_workspace(); $i3->command("workspace $tmp")->recv; +############################################################################# +# 1: see if focus stays the same when toggling tiling/floating mode +############################################################################# + $i3->command("open")->recv; $i3->command("open")->recv; @@ -21,3 +25,48 @@ $i3->command("mode tiling")->recv; @content = @{get_ws_content($tmp)}; cmp_ok($content[1]->{focused}, '==', 1, 'Second container still focused after mode toggle'); + +############################################################################# +# 2: see if the focus gets reverted correctly when closing floating clients +# (first to the next floating client, then to the last focused tiling client) +############################################################################# + +$tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +$i3->command("open")->recv; +$i3->command("open")->recv; +$i3->command("open")->recv; + +@content = @{get_ws_content($tmp)}; +cmp_ok(@content, '==', 3, 'two containers opened'); +cmp_ok($content[2]->{focused}, '==', 1, 'Last container focused'); + +my $last_id = $content[2]->{id}; +my $second_id = $content[1]->{id}; +my $first_id = $content[0]->{id}; +diag("last_id = $last_id, second_id = $second_id, first_id = $first_id"); + +$i3->command(qq|[con_id="$second_id"] focus|)->recv; +@content = @{get_ws_content($tmp)}; +cmp_ok($content[1]->{focused}, '==', 1, 'Second container focused'); + +$i3->command("mode floating")->recv; + +$i3->command(qq|[con_id="$last_id"] focus|)->recv; +@content = @{get_ws_content($tmp)}; +cmp_ok($content[1]->{focused}, '==', 1, 'Last container focused'); + +$i3->command("mode floating")->recv; + +diag("focused = " . get_focused($tmp)); + +$i3->command("kill")->recv; + +diag("focused = " . get_focused($tmp)); +# TODO: this test result is actually not right. the focus reverts to where the mouse pointer is. +cmp_ok(get_focused($tmp), '==', $second_id, 'Focus reverted to second floating container'); + +$i3->command("kill")->recv; +@content = @{get_ws_content($tmp)}; +cmp_ok($content[0]->{focused}, '==', 1, 'Focus reverted to tiling container'); From e8b5a802e23f7f5ec517b2ca2a230278d578ef01 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Nov 2010 22:49:05 +0100 Subject: [PATCH 236/867] Bugfix: only set clients to floating which have a leader that is not their own window (Thanks fernandotcl) --- src/manage.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/manage.c b/src/manage.c index 4724d704..d0ecf1eb 100644 --- a/src/manage.c +++ b/src/manage.c @@ -200,7 +200,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki want_floating = true; } - if (cwindow->transient_for != XCB_NONE || cwindow->leader != XCB_NONE) + if (cwindow->transient_for != XCB_NONE || + (cwindow->leader != XCB_NONE && cwindow->leader != cwindow->id)) want_floating = true; if (want_floating) { From 39b378b0a49bb3c177ad493468f3d369dc0b94b7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Nov 2010 23:18:39 +0100 Subject: [PATCH 237/867] =?UTF-8?q?don=E2=80=99t=20allow=20useless=20split?= =?UTF-8?q?s,=20change=20orientation=20of=20existing=20split=20container?= =?UTF-8?q?=20instead?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tree.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tree.c b/src/tree.c index 3f860923..6ba83ea7 100644 --- a/src/tree.c +++ b/src/tree.c @@ -240,11 +240,11 @@ void tree_split(Con *con, orientation_t orientation) { Con *parent = con->parent; /* if we are in a container whose parent contains only one - * child and has the same orientation like we are trying to - * set, this operation is a no-op to not confuse the user */ - if (con_orientation(parent) == orientation && - TAILQ_NEXT(con, nodes) == TAILQ_END(&(parent->nodes_head))) { - DLOG("Not splitting the same way again\n"); + * child (its split functionality is unused so far), we just change the + * orientation (more intuitive than splitting again) */ + if (con_num_children(parent) == 1) { + parent->orientation = orientation; + DLOG("Just changing orientation of existing container\n"); return; } From 76c07900c279942f3bfae6cc0ecea7bb751c38e9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Nov 2010 23:44:13 +0100 Subject: [PATCH 238/867] take into account x11 border_width settings (fixes uxterm border issue) --- include/data.h | 3 +++ src/manage.c | 2 ++ src/render.c | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/include/data.h b/include/data.h index d34fd733..cc9fca1d 100644 --- a/include/data.h +++ b/include/data.h @@ -297,6 +297,9 @@ struct Con { int base_width; int base_height; + /* the x11 border pixel attribute */ + int border_width; + /* minimum increment size specified for the window (in pixels) */ int width_increment; int height_increment; diff --git a/src/manage.c b/src/manage.c index d0ecf1eb..548e5da7 100644 --- a/src/manage.c +++ b/src/manage.c @@ -185,6 +185,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki nc->window = cwindow; x_reinit(nc); + nc->border_width = geom->border_width; + char *name; asprintf(&name, "[i3 con] container around %p", cwindow); x_set_name(nc, name); diff --git a/src/render.c b/src/render.c index ddb3751a..f8305d54 100644 --- a/src/render.c +++ b/src/render.c @@ -51,6 +51,10 @@ void render_con(Con *con) { *inset = (Rect){0, 0, con->rect.width, con->rect.height}; *inset = rect_add(*inset, con_border_style_rect(con)); + /* Obey x11 border */ + inset->width -= (2 * con->border_width); + inset->height -= (2 * con->border_width); + /* Obey the aspect ratio, if any */ if (con->proportional_height != 0 && con->proportional_width != 0) { From cbd53e8a7f2b3029bb257566bc8f780cb55a7783 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Nov 2010 23:55:53 +0100 Subject: [PATCH 239/867] bugfix: check if the client leader is a managed window (Thanks fernandotcl) --- src/manage.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/manage.c b/src/manage.c index 548e5da7..53dac3d5 100644 --- a/src/manage.c +++ b/src/manage.c @@ -203,7 +203,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki } if (cwindow->transient_for != XCB_NONE || - (cwindow->leader != XCB_NONE && cwindow->leader != cwindow->id)) + (cwindow->leader != XCB_NONE && + cwindow->leader != cwindow->id && + con_by_window_id(cwindow->leader) != NULL)) want_floating = true; if (want_floating) { From 69f29b2b8e1dea450dd960baf498d879760657ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sun, 14 Nov 2010 21:14:49 -0200 Subject: [PATCH 240/867] Use the configured font to draw the decorations. --- src/x.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/x.c b/src/x.c index 5c854d01..1ee23e64 100644 --- a/src/x.c +++ b/src/x.c @@ -299,8 +299,11 @@ void x_draw_decoration(Con *con) { con->deco_rect.y + con->deco_rect.height - 1); /* to_y */ /* 5: draw the title */ - xcb_change_gc_single(conn, parent->gc, XCB_GC_BACKGROUND, color->background); - xcb_change_gc_single(conn, parent->gc, XCB_GC_FOREGROUND, color->text); + i3Font *font = load_font(conn, config.font); + uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; + uint32_t values[] = { color->text, color->background, font->id }; + xcb_change_gc(conn, parent->gc, mask, values); + int text_offset_y = font->height + (con->deco_rect.height - font->height) / 2 - 1; struct Window *win = con->window; if (win == NULL || win->name_x == NULL) { @@ -312,7 +315,7 @@ void x_draw_decoration(Con *con) { parent->frame, parent->gc, con->deco_rect.x + 2, - con->deco_rect.y + 14, /* TODO: hardcoded */ + con->deco_rect.y + text_offset_y, "another container" ); return; @@ -342,7 +345,7 @@ void x_draw_decoration(Con *con) { parent->frame, parent->gc, con->deco_rect.x + 2 + indent_px, - con->deco_rect.y + 14, /* TODO: hardcoded */ + con->deco_rect.y + text_offset_y, (xcb_char2b_t*)win->name_x ); else @@ -352,7 +355,7 @@ void x_draw_decoration(Con *con) { parent->frame, parent->gc, con->deco_rect.x + 2 + indent_px, - con->deco_rect.y + 14, /* TODO: hardcoded */ + con->deco_rect.y + text_offset_y, win->name_x ); } From ea1e9b20be2576e918528670d2271caeef4a3fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sun, 14 Nov 2010 21:25:37 -0200 Subject: [PATCH 241/867] Get rid of the remaining hardcoded height. --- src/floating.c | 6 +++++- src/handlers.c | 5 ++++- src/render.c | 18 +++++++++++------- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/floating.c b/src/floating.c index 2292d89b..b1e23785 100644 --- a/src/floating.c +++ b/src/floating.c @@ -42,11 +42,15 @@ void floating_enable(Con *con, bool automatic) { x_set_name(nc, name); free(name); + /* find the height for the decorations */ + i3Font *font = load_font(conn, config.font); + int deco_height = font->height + 5; + nc->rect = con->rect; /* add pixels for the decoration */ /* TODO: don’t add them when the user automatically puts new windows into * 1pixel/borderless mode */ - nc->rect.height += 17 + 2; + nc->rect.height += deco_height + 4; nc->rect.width += 4; nc->orientation = NO_ORIENTATION; nc->type = CT_FLOATING_CON; diff --git a/src/handlers.c b/src/handlers.c index 7dd73acb..e8c626e1 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -342,11 +342,14 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure DLOG("Configure request!\n"); if (con_is_floating(con) && con_is_leaf(con)) { + /* find the height for the decorations */ + i3Font *font = load_font(conn, config.font); + int deco_height = font->height + 5; /* we actually need to apply the size/position changes to the *parent* * container */ Rect bsr = con_border_style_rect(con); if (con->border_style == BS_NORMAL) - bsr.height -= 17; + bsr.height -= deco_height; con = con->parent; DLOG("Container is a floating leaf node, will do that.\n"); if (event->value_mask & XCB_CONFIG_WINDOW_X) { diff --git a/src/render.c b/src/render.c index f8305d54..fcb2a64e 100644 --- a/src/render.c +++ b/src/render.c @@ -104,6 +104,10 @@ void render_con(Con *con) { return; } + /* find the height for the decorations */ + i3Font *font = load_font(conn, config.font); + int deco_height = font->height + 5; + Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) { @@ -136,11 +140,11 @@ void render_con(Con *con) { child->deco_rect.x = child->rect.x - con->rect.x; child->deco_rect.y = child->rect.y - con->rect.y; - child->rect.y += 17; - child->rect.height -= 17; + child->rect.y += deco_height; + child->rect.height -= deco_height; child->deco_rect.width = child->rect.width; - child->deco_rect.height = 17; + child->deco_rect.height = deco_height; } } @@ -151,13 +155,13 @@ void render_con(Con *con) { child->rect.width = rect.width; child->rect.height = rect.height; - child->rect.y += (17 * children); - child->rect.height -= (17 * children); + child->rect.y += (deco_height * children); + child->rect.height -= (deco_height * children); child->deco_rect.x = x - con->rect.x; - child->deco_rect.y = y - con->rect.y + (i * 17); + child->deco_rect.y = y - con->rect.y + (i * deco_height); child->deco_rect.width = child->rect.width; - child->deco_rect.height = 17; + child->deco_rect.height = deco_height; } printf("child at (%d, %d) with (%d x %d)\n", From 8048cb2e120e33271bdd8823797ad240d009370c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sun, 14 Nov 2010 21:38:53 -0200 Subject: [PATCH 242/867] Render the tabbed mode correctly. --- src/render.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/render.c b/src/render.c index fcb2a64e..e3069e72 100644 --- a/src/render.c +++ b/src/render.c @@ -148,7 +148,8 @@ void render_con(Con *con) { } } - if (con->layout == L_STACKED) { + /* stacked layout */ + else if (con->layout == L_STACKED) { printf("stacked con\n"); child->rect.x = x; child->rect.y = y; @@ -164,6 +165,23 @@ void render_con(Con *con) { child->deco_rect.height = deco_height; } + /* tabbed layout */ + else if (con->layout == L_TABBED) { + printf("tabbed con\n"); + child->rect.x = x; + child->rect.y = y; + child->rect.width = rect.width; + child->rect.height = rect.height; + + child->deco_rect.width = child->rect.width / children; + child->deco_rect.height = deco_height; + child->deco_rect.x = x - con->rect.x + i * child->deco_rect.width; + child->deco_rect.y = y - con->rect.y; + + child->rect.y += deco_height; + child->rect.height -= deco_height; + } + printf("child at (%d, %d) with (%d x %d)\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height); printf("x now %d, y now %d\n", x, y); @@ -172,8 +190,8 @@ void render_con(Con *con) { i++; } - /* in a stacking container, we ensure the focused client is raised */ - if (con->layout == L_STACKED) { + /* in a stacking or tabbed container, we ensure the focused client is raised */ + if (con->layout == L_STACKED || con->layout == L_TABBED) { Con *foc = TAILQ_FIRST(&(con->focus_head)); if (foc != TAILQ_END(&(con->focus_head))) { LOG("con %p is stacking, raising %p\n", con, foc); From c0c7d042648dd4a7e52398742e0e09cba84ebbf2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 15 Nov 2010 13:55:10 +0100 Subject: [PATCH 243/867] When in tabbed mode, nail the orientation to horizontal This is intuitive for the user as he can use the left/right keys to switch. Also, we need to nail the orientation because you can be in either vertical or horizontal mode when you enter the tabbed layout (like with the stacking layout). --- src/con.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/con.c b/src/con.c index 2487dd83..4839526c 100644 --- a/src/con.c +++ b/src/con.c @@ -408,6 +408,9 @@ int con_orientation(Con *con) { if (con->layout == L_STACKED) return VERT; + if (con->layout == L_TABBED) + return HORIZ; + return con->orientation; } From bfa12a581915d6a3de182fa6025fce108cac8eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sun, 14 Nov 2010 22:54:40 -0200 Subject: [PATCH 244/867] Port the path resolution and config loading code from -next. --- common.mk | 1 + include/config.h | 2 +- src/config.c | 77 ++++++++++++++++++++++++++---------------------- src/tree.c | 4 +-- src/util.c | 2 +- 5 files changed, 47 insertions(+), 39 deletions(-) diff --git a/common.mk b/common.mk index 00300cf8..49298049 100644 --- a/common.mk +++ b/common.mk @@ -20,6 +20,7 @@ CFLAGS += -Wunused-value CFLAGS += -Iinclude CFLAGS += -I/usr/local/include CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" +CFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" LDFLAGS += -lm LDFLAGS += -lxcb-event diff --git a/include/config.h b/include/config.h index ab2dac87..c9e8e1a4 100644 --- a/include/config.h +++ b/include/config.h @@ -127,7 +127,7 @@ struct Config { } bar; }; -char *glob_path(const char *path); +char *resolve_tilde(const char *path); bool path_exists(const char *path); /** diff --git a/src/config.c b/src/config.c index b37f013c..93390b4a 100644 --- a/src/config.c +++ b/src/config.c @@ -22,33 +22,38 @@ Config config; struct modes_head modes; + /* * 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 *glob_path(const char *path) { +char *resolve_tilde(const char *path) { static glob_t globbuf; - if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) - die("glob() failed"); - char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path); - globfree(&globbuf); + char *head, *tail, *result; - /* If the file does not exist yet, we still may need to resolve tilde, - * so call wordexp */ - if (strcmp(result, path) == 0) { - wordexp_t we; - wordexp(path, &we, WRDE_NOCMD); - if (we.we_wordc > 0) { - free(result); - result = sstrdup(we.we_wordv[0]); - } - wordfree(&we); + tail = strchr(path, '/'); + head = strndup(path, tail ? 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)); + strncat(result, tail, strlen(tail)); } + globfree(&globbuf); return result; } - /* * Checks if the given path exists by calling stat(). * @@ -205,18 +210,24 @@ void switch_mode(xcb_connection_t *conn, const char *new_mode) { } /* - * Get the path of the first configuration file found. Checks the XDG folders - * first ($XDG_CONFIG_HOME, $XDG_CONFIG_DIRS), then the traditional paths. + * Get the path of the first configuration file found. 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() { - /* 1: check for $XDG_CONFIG_HOME/i3/config */ char *xdg_config_home, *xdg_config_dirs, *config_path; + /* 1: check the traditional path under the home directory */ + config_path = resolve_tilde("~/.i3/config"); + if (path_exists(config_path)) + return 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 = glob_path(xdg_config_home); + xdg_config_home = resolve_tilde(xdg_config_home); if (asprintf(&config_path, "%s/i3/config", xdg_config_home) == -1) die("asprintf() failed"); free(xdg_config_home); @@ -225,14 +236,19 @@ static char *get_config_path() { return config_path; free(config_path); - /* 2: check for $XDG_CONFIG_DIRS/i3/config */ + /* 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 = strdup(xdg_config_dirs); + char *buf = sstrdup(xdg_config_dirs); char *tok = strtok(buf, ":"); while (tok != NULL) { - tok = glob_path(tok); + tok = resolve_tilde(tok); if (asprintf(&config_path, "%s/i3/config", tok) == -1) die("asprintf() failed"); free(tok); @@ -245,18 +261,9 @@ static char *get_config_path() { } free(buf); - /* 3: check traditional paths */ - config_path = glob_path("~/.i3/config"); - if (path_exists(config_path)) - return config_path; - - config_path = strdup("/etc/i3/config"); - if (!path_exists(config_path)) - die("Neither $XDG_CONFIG_HOME/i3/config, nor " - "$XDG_CONFIG_DIRS/i3/config, nor ~/.i3/config nor " - "/etc/i3/config exist."); - - return config_path; + 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)"); } /* diff --git a/src/tree.c b/src/tree.c index 6ba83ea7..7d12cab1 100644 --- a/src/tree.c +++ b/src/tree.c @@ -14,7 +14,7 @@ struct all_cons_head all_cons = TAILQ_HEAD_INITIALIZER(all_cons); * */ bool tree_restore() { - char *globbed = glob_path("~/.i3/_restart.json"); + char *globbed = resolve_tilde("~/.i3/_restart.json"); if (!path_exists(globbed)) { LOG("%s does not exist, not restoring tree\n", globbed); @@ -27,7 +27,7 @@ bool tree_restore() { focused = croot; tree_append_json(globbed); - char *old_restart = glob_path("~/.i3/_restart.json.old"); + char *old_restart = resolve_tilde("~/.i3/_restart.json.old"); unlink(old_restart); rename(globbed, old_restart); free(globbed); diff --git a/src/util.c b/src/util.c index fc3ada5f..df69ecc4 100644 --- a/src/util.c +++ b/src/util.c @@ -485,7 +485,7 @@ void store_restart_layout() { unsigned int length; y(get_buf, &payload, &length); - char *globbed = glob_path("~/.i3/_restart.json"); + char *globbed = resolve_tilde("~/.i3/_restart.json"); int fd = open(globbed, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); free(globbed); if (fd == -1) { From 5ebb3b4832d6370128b87c6b5e2b56f10b096ccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sun, 14 Nov 2010 23:01:04 -0200 Subject: [PATCH 245/867] Port the IPC socket creation code from -next. --- src/ipc.c | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/ipc.c b/src/ipc.c index eed63bd4..b8409a92 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,34 @@ static void set_nonblock(int sockfd) { err(-1, "Could not set O_NONBLOCK"); } +/* + * Emulates mkdir -p (creates any missing folders) + * + */ +static 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 = strdup(path); + /* strip trailing slashes, if any */ + while (copy[strlen(copy)-1] == '/') + copy[strlen(copy)-1] = '\0'; + + char *sep = strrchr(copy, '/'); + if (sep == NULL) + return false; + *sep = '\0'; + bool result = false; + if (mkdirp(copy)) + result = mkdirp(path); + free(copy); + + return result; +} + static void ipc_send_message(int fd, const unsigned char *payload, int message_type, int message_size) { int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + @@ -555,11 +584,20 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) { int ipc_create_socket(const char *filename) { int sockfd; + char *resolved = resolve_tilde(filename); + DLOG("Creating IPC-socket at %s\n", resolved); + char *copy = sstrdup(resolved); + const char *dir = dirname(copy); + if (!path_exists(dir)) + mkdirp(dir); + free(copy); + /* Unlink the unix domain socket before */ unlink(filename); if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { perror("socket()"); + free(resolved); return -1; } @@ -568,12 +606,14 @@ int ipc_create_socket(const char *filename) { struct sockaddr_un addr; memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_LOCAL; - strcpy(addr.sun_path, filename); + strncpy(addr.sun_path, resolved, sizeof(addr.sun_path) - 1); if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) { perror("bind()"); + free(resolved); return -1; } + free(resolved); set_nonblock(sockfd); if (listen(sockfd, 5) < 0) { From 576de246d81856e831391062e63bbfc47fd7e481 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 20 Nov 2010 18:38:24 +0100 Subject: [PATCH 246/867] Bugfix: when no windows to focus, return the workspace instead of NULL --- src/con.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/con.c b/src/con.c index 4839526c..1686d84f 100644 --- a/src/con.c +++ b/src/con.c @@ -439,8 +439,8 @@ Con *con_next_focused(Con *con) { } } if (next == TAILQ_END(&(ws->focus_head))) { - DLOG("Focus list empty, returning NULL\n"); - next = NULL; + DLOG("Focus list empty, returning ws\n"); + next = ws; } } return next; From 131a2f8765892540d24d793df8efa10f9b13426f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 20 Nov 2010 18:40:25 +0100 Subject: [PATCH 247/867] debugging: implement a NOP command which just spits out its argument You can use this in testcases to mark specific sections: $i3->command('nop before trying to crash')->recv; leads to the following output in the i3 logfile: ------------------------------------------------- NOP: before trying to crash ------------------------------------------------- --- src/cmdparse.l | 1 + src/cmdparse.y | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/cmdparse.l b/src/cmdparse.l index 9a236efc..d8320578 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -120,6 +120,7 @@ grow { return TOK_GROW; } px { return TOK_PX; } or { return TOK_OR; } ppt { return TOK_PPT; } +nop { BEGIN(WANT_WS_STRING); return TOK_NOP; } restore { BEGIN(WANT_WS_STRING); return TOK_RESTORE; } mark { BEGIN(WANT_WS_STRING); return TOK_MARK; } diff --git a/src/cmdparse.y b/src/cmdparse.y index 7f9dfd46..c2f0222e 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -144,6 +144,7 @@ char *parse_cmd(const char *new) { %token TOK_PX "px" %token TOK_OR "or" %token TOK_PPT "ppt" +%token TOK_NOP "nop" %token TOK_CLASS "class" %token TOK_ID "id" @@ -306,6 +307,7 @@ operation: | level | mark | resize + | nop ; exec: @@ -609,6 +611,16 @@ mark: } ; +nop: + TOK_NOP WHITESPACE STR + { + printf("-------------------------------------------------\n"); + printf(" NOP: %s\n", $3); + printf("-------------------------------------------------\n"); + free($3); + } + ; + resize: TOK_RESIZE WHITESPACE resize_way WHITESPACE direction resize_px resize_tiling { From db651679c568109b19d2c9bb5270f3359d57fd10 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 20 Nov 2010 19:11:43 +0100 Subject: [PATCH 248/867] Bugfix: Properly ignore UnmapNotify events (especially for floating windows) This fixes the bug which caused floating windows to be visible even when switching to a different workspace. Instead of ignoring a specific sequence, we now set an ignore_unmap counter for each container. (So, should containers be closed too early or stay open even if they should be closed, we probably need to have a closer look at the counter. At the moment, it is increased by one on reparenting and unmapping (for workspace changes) and decremented by one on each UnmapNotify event). This system is better because a sequence does not describe a single unmap or reparent request but a request to X11 on the network layer -- which can contain multiple requests. --- include/data.h | 6 ++++ src/handlers.c | 24 ++++++++----- src/tree.c | 11 ++++++ src/x.c | 9 +++++ testcases/t/36-floating-unmap.t | 64 +++++++++++++++++++++++++++++++++ 5 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 testcases/t/36-floating-unmap.t diff --git a/include/data.h b/include/data.h index cc9fca1d..c7fcb4a5 100644 --- a/include/data.h +++ b/include/data.h @@ -338,6 +338,12 @@ struct Con { FLOATING_USER_ON = 3 } floating; + /** This counter contains the number of UnmapNotify events for this + * container (or, more precisely, for its ->frame) which should be ignored. + * UnmapNotify events need to be ignored when they are caused by i3 itself, + * for example when reparenting or when unmapping the window on a workspace + * change. */ + uint8_t ignore_unmap; TAILQ_ENTRY(Con) nodes; TAILQ_ENTRY(Con) focused; diff --git a/src/handlers.c b/src/handlers.c index e8c626e1..f5966db9 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -444,8 +444,6 @@ int handle_screen_change(void *prophs, xcb_connection_t *conn, */ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event) { - bool ignored = event_is_ignored(event->sequence); - /* FIXME: we cannot ignore this sequence because more UnmapNotifys with the same sequence * numbers but different window IDs may follow */ /* we need to ignore EnterNotify events which will be generated because a @@ -453,14 +451,24 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti //add_ignore_event(event->sequence); DLOG("UnmapNotify for 0x%08x (received from 0x%08x), serial %d\n", event->window, event->event, event->sequence); - if (ignored) { - DLOG("Ignoring UnmapNotify (generated by reparenting)\n"); - unignore_event(event->sequence); - return 1; - } Con *con = con_by_window_id(event->window); if (con == NULL) { - LOG("Not a managed window, ignoring\n"); + /* This could also be an UnmapNotify for the frame. We need to + * decrement the ignore_unmap counter. */ + con = con_by_frame_id(event->window); + if (con == NULL) { + LOG("Not a managed window, ignoring UnmapNotify event\n"); + return 1; + } + if (con->ignore_unmap > 0) + con->ignore_unmap--; + DLOG("ignore_unmap = %d for frame of container %p\n", con->ignore_unmap, con); + return 1; + } + + if (con->ignore_unmap > 0) { + DLOG("ignore_unmap = %d, dec\n", con->ignore_unmap); + con->ignore_unmap--; return 1; } diff --git a/src/tree.c b/src/tree.c index 7d12cab1..dac949ba 100644 --- a/src/tree.c +++ b/src/tree.c @@ -291,10 +291,21 @@ void level_down() { static void mark_unmapped(Con *con) { Con *current; + DLOG("marking container %p unmapped\n", con); con->mapped = false; TAILQ_FOREACH(current, &(con->nodes_head), nodes) mark_unmapped(current); + if (con->type == CT_WORKSPACE) { + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) { + DLOG("Marking unmapped for floating %p\n", current); + current->mapped = false; + Con *child = TAILQ_FIRST(&(current->nodes_head)); + DLOG(" unmapping floating child %p\n", child); + child->mapped = false; + } + } + DLOG("mark_unmapped done\n"); } /* diff --git a/src/x.c b/src/x.c index 1ee23e64..0de6c65a 100644 --- a/src/x.c +++ b/src/x.c @@ -425,6 +425,10 @@ static void x_push_node(Con *con) { state->old_frame = XCB_NONE; state->need_reparent = false; + + con->ignore_unmap++; + DLOG("ignore_unmap for reparenting of con %p (win 0x%08x) is now %d\n", + con, con->window->id, con->ignore_unmap); } /* map/unmap if map state changed, also ensure that the child window @@ -442,6 +446,11 @@ static void x_push_node(Con *con) { cookie = xcb_unmap_window(conn, con->frame); LOG("unmapping container (serial %d)\n", cookie.sequence); + /* we need to increase ignore_unmap for this container (if it contains a window) and for every window "under" this one which contains a window */ + if (con->window != NULL) { + con->ignore_unmap++; + DLOG("ignore_unmap for con %p (frame 0x%08x) now %d\n", con, con->frame, con->ignore_unmap); + } /* Ignore enter_notifies which are generated when unmapping */ add_ignore_event(cookie.sequence); } else { diff --git a/testcases/t/36-floating-unmap.t b/testcases/t/36-floating-unmap.t new file mode 100644 index 00000000..9d122fab --- /dev/null +++ b/testcases/t/36-floating-unmap.t @@ -0,0 +1,64 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# Regression test: Floating windows were not correctly unmapped when switching +# to a different workspace. + +use i3test tests => 5; +use X11::XCB qw(:all); +use Time::HiRes qw(sleep); + +BEGIN { + use_ok('X11::XCB::Window'); +} + +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +############################################################################# +# 1: open a floating window, get it mapped +############################################################################# + +my $x = X11::XCB::Connection->new; + +# FIXME: we open a tiling container as long as t/37* is not done (should swap positions with t/36* then) +my $twindow = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#C0C0C0', +); + +isa_ok($twindow, 'X11::XCB::Window'); + +$twindow->map; + +sleep 0.25; + +# Create a floating window which is smaller than the minimum enforced size of i3 +my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#C0C0C0', + # replace the type with 'utility' as soon as the coercion works again in X11::XCB + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), +); + +isa_ok($window, 'X11::XCB::Window'); + +$window->map; + +sleep 0.25; + +ok($window->mapped, 'Window is mapped'); + +# switch to a different workspace, see if the window is still mapped? + +my $otmp = get_unused_workspace(); +$i3->command("workspace $otmp")->recv; + +sleep 0.25; + +ok(!$window->mapped, 'Window is not mapped after switching ws'); + +$i3->command("nop testcase done")->recv; From 39fa1d724a486e6936e2401cac0434fc78f0c28f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 20 Nov 2010 20:16:15 +0100 Subject: [PATCH 249/867] =?UTF-8?q?bugfix:=20don=E2=80=99t=20treat=20works?= =?UTF-8?q?pace=20as=20empty=20if=20they=20only=20have=20floating=20window?= =?UTF-8?q?s=20(+testcase)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/workspace.c | 2 +- testcases/t/36-floating-ws-empty.t | 54 ++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 testcases/t/36-floating-ws-empty.t diff --git a/src/workspace.c b/src/workspace.c index cdea9827..e13faa60 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -205,7 +205,7 @@ void workspace_show(const char *num) { next = TAILQ_FIRST(&(next->focus_head)); - if (TAILQ_EMPTY(&(old->nodes_head))) { + if (TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) { /* check if this workspace is currently visible */ if (!workspace_is_visible(old)) { LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); diff --git a/testcases/t/36-floating-ws-empty.t b/testcases/t/36-floating-ws-empty.t new file mode 100644 index 00000000..1d795e31 --- /dev/null +++ b/testcases/t/36-floating-ws-empty.t @@ -0,0 +1,54 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# Regression test: when only having a floating window on a workspace, it should not be deleted. + +use i3test tests => 6; +use X11::XCB qw(:all); +use Time::HiRes qw(sleep); + +BEGIN { + use_ok('X11::XCB::Window'); +} + +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +############################################################################# +# 1: open a floating window, get it mapped +############################################################################# + +sub workspace_exists { + my ($name) = @_; + ($name ~~ @{get_workspace_names()}) +} + +ok(workspace_exists($tmp), "workspace $tmp exists"); + +my $x = X11::XCB::Connection->new; + +# Create a floating window which is smaller than the minimum enforced size of i3 +my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#C0C0C0', + # replace the type with 'utility' as soon as the coercion works again in X11::XCB + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), +); + +isa_ok($window, 'X11::XCB::Window'); + +$window->map; + +sleep 0.25; + +ok($window->mapped, 'Window is mapped'); + +# switch to a different workspace, see if the window is still mapped? + +my $otmp = get_unused_workspace(); +$i3->command("workspace $otmp")->recv; + +ok(workspace_exists($otmp), "new workspace $otmp exists"); +ok(workspace_exists($tmp), "old workspace $tmp still exists"); From fcd8518d81132a5212c520929b6e317fbd382f7e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 20 Nov 2010 20:17:25 +0100 Subject: [PATCH 250/867] =?UTF-8?q?floating-unmap.t:=20switch=20order,=20d?= =?UTF-8?q?on=E2=80=99t=20use=20workaround?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{36-floating-unmap.t => 37-floating-unmap.t} | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) rename testcases/t/{36-floating-unmap.t => 37-floating-unmap.t} (79%) diff --git a/testcases/t/36-floating-unmap.t b/testcases/t/37-floating-unmap.t similarity index 79% rename from testcases/t/36-floating-unmap.t rename to testcases/t/37-floating-unmap.t index 9d122fab..8fc7c4c0 100644 --- a/testcases/t/36-floating-unmap.t +++ b/testcases/t/37-floating-unmap.t @@ -3,7 +3,7 @@ # Regression test: Floating windows were not correctly unmapped when switching # to a different workspace. -use i3test tests => 5; +use i3test tests => 4; use X11::XCB qw(:all); use Time::HiRes qw(sleep); @@ -22,19 +22,6 @@ $i3->command("workspace $tmp")->recv; my $x = X11::XCB::Connection->new; -# FIXME: we open a tiling container as long as t/37* is not done (should swap positions with t/36* then) -my $twindow = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', -); - -isa_ok($twindow, 'X11::XCB::Window'); - -$twindow->map; - -sleep 0.25; - # Create a floating window which is smaller than the minimum enforced size of i3 my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, From f53fafe100ad43f4525d5605ad0c6c7c13219f7f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Nov 2010 16:34:45 +0100 Subject: [PATCH 251/867] ipc: s/floating-nodes/floating_nodes for consistency --- src/ipc.c | 2 +- testcases/t/16-nestedcons.t | 2 +- testcases/t/22-split.t | 2 +- testcases/t/lib/i3test.pm | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index b8409a92..fd4f4fd8 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -206,7 +206,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { } y(array_close); - ystr("floating-nodes"); + ystr("floating_nodes"); y(array_open); TAILQ_FOREACH(node, &(con->floating_head), floating_windows) { dump_node(gen, node, inplace_restart); diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t index 1e57994f..3d392c46 100644 --- a/testcases/t/16-nestedcons.t +++ b/testcases/t/16-nestedcons.t @@ -26,7 +26,7 @@ my $expected = { focused => 0, urgent => 0, border => 0, - 'floating-nodes' => ignore(), + 'floating_nodes' => ignore(), }; cmp_deeply($tree, $expected, 'root node OK'); diff --git a/testcases/t/22-split.t b/testcases/t/22-split.t index f3942243..fdf7bb21 100644 --- a/testcases/t/22-split.t +++ b/testcases/t/22-split.t @@ -76,7 +76,7 @@ sub sum_nodes { return 0 if !@{$nodes}; my @children = (map { @{$_->{nodes}} } @{$nodes}, - map { @{$_->{'floating-nodes'}} } @{$nodes}); + map { @{$_->{'floating_nodes'}} } @{$nodes}); return @{$nodes} + sum_nodes(\@children); } diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 2fcfc064..d241cfae 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -112,7 +112,7 @@ sub get_focused { $lf = $focused[0]; last unless defined($con->{focus}); @focused = @{$con->{focus}}; - @cons = grep { $_->{id} == $lf } (@{$con->{nodes}}, @{$con->{'floating-nodes'}}); + @cons = grep { $_->{id} == $lf } (@{$con->{nodes}}, @{$con->{'floating_nodes'}}); $con = $cons[0]; } From 09b5b17830588c4f9c97e485f02b3e6a05ce3b54 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Nov 2010 16:49:59 +0100 Subject: [PATCH 252/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20attach=20tili?= =?UTF-8?q?ng=20containers=20to=20floating=20containers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This bug happened when there were only floating containers on a workspace and a new tiling window was to be opened. --- src/con.c | 22 ++++++++--- src/tree.c | 4 ++ testcases/t/38-floating-attach.t | 63 ++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 testcases/t/38-floating-attach.t diff --git a/src/con.c b/src/con.c index 1686d84f..fb6866d8 100644 --- a/src/con.c +++ b/src/con.c @@ -70,14 +70,24 @@ Con *con_new(Con *parent) { */ void con_attach(Con *con, Con *parent) { con->parent = parent; - Con *current = TAILQ_FIRST(&(parent->focus_head)); + Con *loop; + Con *current = NULL; - if (current == TAILQ_END(&(parent->focus_head))) - TAILQ_INSERT_TAIL(&(parent->nodes_head), con, nodes); - else { - DLOG("inserting after\n"); - TAILQ_INSERT_AFTER(&(parent->nodes_head), current, con, nodes); + /* Get the first tiling container in focus stack */ + TAILQ_FOREACH(loop, &(parent->focus_head), focused) { + if (loop->type == CT_FLOATING_CON) + continue; + current = loop; + break; } + + /* Insert the container after the tiling container, if found */ + if (current) { + DLOG("Inserting con = %p after last focused tiling con %p\n", + con, current); + TAILQ_INSERT_AFTER(&(parent->nodes_head), current, con, nodes); + } else TAILQ_INSERT_TAIL(&(parent->nodes_head), con, nodes); + /* We insert to the TAIL because con_focus() will correct this. * This way, we have the option to insert Cons without having * to focus them. */ diff --git a/src/tree.c b/src/tree.c index dac949ba..e0b1ab7d 100644 --- a/src/tree.c +++ b/src/tree.c @@ -104,6 +104,10 @@ Con *tree_open_con(Con *con) { * the new container needs to be opened as a leaf of the workspace. */ if (con->type == CT_OUTPUT) con = focused; + /* If the currently focused container is a floating container, we + * attach the new container to the workspace */ + if (con->type == CT_FLOATING_CON) + con = con->parent; } assert(con != NULL); diff --git a/testcases/t/38-floating-attach.t b/testcases/t/38-floating-attach.t new file mode 100644 index 00000000..0a85ff6d --- /dev/null +++ b/testcases/t/38-floating-attach.t @@ -0,0 +1,63 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# Regression test: New windows were attached to the container of a floating window +# if only a floating window is present on the workspace. + +use i3test tests => 7; +use X11::XCB qw(:all); +use Time::HiRes qw(sleep); + +BEGIN { + use_ok('X11::XCB::Window'); +} + +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +############################################################################# +# 1: open a floating window, get it mapped +############################################################################# + +my $x = X11::XCB::Connection->new; + +# Create a floating window +my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#C0C0C0', + # replace the type with 'utility' as soon as the coercion works again in X11::XCB + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), +); + +isa_ok($window, 'X11::XCB::Window'); + +$window->map; + +sleep 0.25; + +ok($window->mapped, 'Window is mapped'); + +my $ws = get_ws($tmp); +my ($nodes, $focus) = get_ws_content($tmp); + +is(@{$ws->{floating_nodes}}, 1, 'one floating node'); +is(@{$nodes}, 0, 'no tiling nodes'); + +# Create a tiling window +my $twindow = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#C0C0C0', +); + +isa_ok($twindow, 'X11::XCB::Window'); + +$twindow->map; + +sleep 0.25; + +($nodes, $focus) = get_ws_content($tmp); + +is(@{$nodes}, 1, 'one tiling node'); From 2d280469af8fae2b504ffe5a0c71894dd2f02c8a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Nov 2010 17:00:10 +0100 Subject: [PATCH 253/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20draw=20border?= =?UTF-8?q?s=20for=20fullscreen=20windows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/render.h | 2 +- src/con.c | 6 ++++++ src/render.c | 13 +++++++------ src/tree.c | 2 +- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/include/render.h b/include/render.h index 849f214e..94084489 100644 --- a/include/render.h +++ b/include/render.h @@ -13,6 +13,6 @@ * updated in X11. * */ -void render_con(Con *con); +void render_con(Con *con, bool render_fullscreen); #endif diff --git a/src/con.c b/src/con.c index fb6866d8..a01bdcc1 100644 --- a/src/con.c +++ b/src/con.c @@ -503,6 +503,12 @@ Rect con_border_style_rect(Con *con) { * */ int con_border_style(Con *con) { + Con *fs = con_get_fullscreen_con(con->parent); + if (fs == con) { + DLOG("this one is fullscreen! overriding BS_NONE\n"); + return BS_NONE; + } + if (con->parent->layout == L_STACKED) return BS_NORMAL; diff --git a/src/render.c b/src/render.c index e3069e72..b1b1bd89 100644 --- a/src/render.c +++ b/src/render.c @@ -16,7 +16,7 @@ static bool show_debug_borders = false; * updated in X11. * */ -void render_con(Con *con) { +void render_con(Con *con, bool render_fullscreen) { printf("currently rendering node %p / %s / layout %d\n", con, con->name, con->layout); int children = con_num_children(con); @@ -49,7 +49,8 @@ void render_con(Con *con) { * needs to be smaller */ Rect *inset = &(con->window_rect); *inset = (Rect){0, 0, con->rect.width, con->rect.height}; - *inset = rect_add(*inset, con_border_style_rect(con)); + if (!render_fullscreen) + *inset = rect_add(*inset, con_border_style_rect(con)); /* Obey x11 border */ inset->width -= (2 * con->border_width); @@ -100,7 +101,7 @@ void render_con(Con *con) { LOG("got fs node: %p\n", fullscreen); fullscreen->rect = rect; x_raise_con(fullscreen); - render_con(fullscreen); + render_con(fullscreen, true); return; } @@ -186,7 +187,7 @@ void render_con(Con *con) { child->rect.x, child->rect.y, child->rect.width, child->rect.height); printf("x now %d, y now %d\n", x, y); x_raise_con(child); - render_con(child); + render_con(child, false); i++; } @@ -198,7 +199,7 @@ void render_con(Con *con) { x_raise_con(foc); /* by rendering the stacked container again, we handle the case * that we have a non-leaf-container inside the stack. */ - render_con(foc); + render_con(foc, false); } } @@ -206,7 +207,7 @@ void render_con(Con *con) { LOG("render floating:\n"); LOG("floating child at (%d,%d) with %d x %d\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height); x_raise_con(child); - render_con(child); + render_con(child, false); } printf("-- level up\n"); diff --git a/src/tree.c b/src/tree.c index e0b1ab7d..734406b3 100644 --- a/src/tree.c +++ b/src/tree.c @@ -331,7 +331,7 @@ void tree_render() { Con *output; TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { printf("output %p / %s\n", output, output->name); - render_con(output); + render_con(output, false); } x_push_changes(croot); printf("-- END RENDERING --\n"); From dab83ba413cca0beb06967c0da107097f4480f9b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Nov 2010 17:04:35 +0100 Subject: [PATCH 254/867] add shortcut to default config for tiling/floating toggle --- i3.config | 3 +++ 1 file changed, 3 insertions(+) diff --git a/i3.config b/i3.config index 132918f2..185c2462 100644 --- a/i3.config +++ b/i3.config @@ -34,6 +34,9 @@ bindsym Mod1+w layout tabbed # Default (Mod1+l) bindsym Mod1+l layout default +# toggle tiling / floating +bindsym Mod1+Shift+space mode toggle + bindsym Mod1+u level up #bindsym Mod1+d level down From 5d830e7a27c156889d4a8024a876bda6a01834e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sat, 20 Nov 2010 19:30:53 -0200 Subject: [PATCH 255/867] Ported over some message types from -next. --- include/i3/ipc.h | 23 ++++++- src/ipc.c | 157 ++++++++++++++++++++++++----------------------- 2 files changed, 100 insertions(+), 80 deletions(-) diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 0046b637..e81f9a15 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -27,7 +27,16 @@ #define I3_IPC_MESSAGE_TYPE_COMMAND 0 /** Requests the current workspaces from i3 */ -#define I3_IPC_MESSAGE_TYPE_GET_TREE 1 +#define I3_IPC_MESSAGE_TYPE_GET_WORKSPACES 1 + +/** Subscribe to the specified events */ +#define I3_IPC_MESSAGE_TYPE_SUBSCRIBE 2 + +/** Requests the current outputs from i3 */ +#define I3_IPC_MESSAGE_TYPE_GET_OUTPUTS 3 + +/** Requests the tree layout from i3 */ +#define I3_IPC_MESSAGE_TYPE_GET_TREE 4 /* @@ -39,7 +48,17 @@ #define I3_IPC_REPLY_TYPE_COMMAND 0 /** Workspaces reply type */ -#define I3_IPC_REPLY_TYPE_TREE 1 +#define I3_IPC_REPLY_TYPE_WORKSPACES 1 + +/** Subscription reply type */ +#define I3_IPC_REPLY_TYPE_SUBSCRIBE 2 + +/** Outputs reply type */ +#define I3_IPC_REPLY_TYPE_OUTPUTS 3 + +/** Tree reply type */ +#define I3_IPC_REPLY_TYPE_TREE 4 + /* * Events from i3 to clients. Events have the first bit set high. diff --git a/src/ipc.c b/src/ipc.c index fd4f4fd8..fd4966ce 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -239,7 +239,6 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { } IPC_HANDLER(tree) { - printf("tree\n"); yajl_gen gen = yajl_gen_alloc(NULL, NULL); dump_node(gen, croot, false); @@ -252,68 +251,66 @@ IPC_HANDLER(tree) { } -#if 0 /* * Formats the reply message for a GET_WORKSPACES request and sends it to the * client * */ IPC_HANDLER(get_workspaces) { - Workspace *ws; + yajl_gen gen = yajl_gen_alloc(NULL, NULL); + y(array_open); - Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); - if (last_focused == SLIST_END(&(c_ws->focus_stack))) - last_focused = NULL; + Con *focused_ws = con_get_workspace(focused); - yajl_gen gen = yajl_gen_alloc(NULL, NULL); - y(array_open); + Con *output; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *ws; + TAILQ_FOREACH(ws, &(output->nodes_head), nodes) { + assert(ws->type == CT_WORKSPACE); + y(map_open); - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->output == NULL) - continue; + ystr("num"); + y(integer, con_num_children(ws)); - y(map_open); - ystr("num"); - y(integer, ws->num + 1); + ystr("name"); + ystr(ws->name); - ystr("name"); - ystr(ws->utf8_name); + ystr("visible"); + y(bool, workspace_is_visible(ws)); - ystr("visible"); - y(bool, ws->output->current_workspace == ws); + ystr("focused"); + y(bool, ws == focused_ws); - ystr("focused"); - y(bool, c_ws == ws); + ystr("rect"); + y(map_open); + ystr("x"); + y(integer, ws->rect.x); + ystr("y"); + y(integer, ws->rect.y); + ystr("width"); + y(integer, ws->rect.width); + ystr("height"); + y(integer, ws->rect.height); + y(map_close); - ystr("rect"); - y(map_open); - ystr("x"); - y(integer, ws->rect.x); - ystr("y"); - y(integer, ws->rect.y); - ystr("width"); - y(integer, ws->rect.width); - ystr("height"); - y(integer, ws->rect.height); - y(map_close); + ystr("output"); + ystr(output->name); - ystr("output"); - ystr(ws->output->name); + ystr("urgent"); + y(bool, ws->urgent); - ystr("urgent"); - y(bool, ws->urgent); - - y(map_close); + y(map_close); } + } - y(array_close); + y(array_close); - const unsigned char *payload; - unsigned int length; - y(get_buf, &payload, &length); + const unsigned char *payload; + unsigned int length; + y(get_buf, &payload, &length); - ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length); - y(free); + ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length); + y(free); } /* @@ -322,48 +319,50 @@ IPC_HANDLER(get_workspaces) { * */ IPC_HANDLER(get_outputs) { - Output *output; + yajl_gen gen = yajl_gen_alloc(NULL, NULL); + y(array_open); - yajl_gen gen = yajl_gen_alloc(NULL, NULL); - y(array_open); + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) { + y(map_open); - TAILQ_FOREACH(output, &outputs, outputs) { - y(map_open); + ystr("name"); + ystr(output->name); - ystr("name"); - ystr(output->name); + ystr("active"); + y(bool, output->active); - ystr("active"); - y(bool, output->active); + ystr("rect"); + y(map_open); + ystr("x"); + y(integer, output->rect.x); + ystr("y"); + y(integer, output->rect.y); + ystr("width"); + y(integer, output->rect.width); + ystr("height"); + y(integer, output->rect.height); + y(map_close); - ystr("rect"); - y(map_open); - ystr("x"); - y(integer, output->rect.x); - ystr("y"); - y(integer, output->rect.y); - ystr("width"); - y(integer, output->rect.width); - ystr("height"); - y(integer, output->rect.height); - y(map_close); + /* + * XXX + * No idea how to handle this, where should we get this data from? + * I think we might need to keep a reference to the CT_OUTPUT Con in Output + */ + ystr("current_workspace"); + y(null); - ystr("current_workspace"); - if (output->current_workspace == NULL) - y(null); - else y(integer, output->current_workspace->num + 1); + y(map_close); + } - y(map_close); - } + y(array_close); - y(array_close); + const unsigned char *payload; + unsigned int length; + y(get_buf, &payload, &length); - const unsigned char *payload; - unsigned int length; - y(get_buf, &payload, &length); - - ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_OUTPUTS, length); - y(free); + ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_OUTPUTS, length); + y(free); } /* @@ -441,12 +440,14 @@ IPC_HANDLER(subscribe) { ipc_send_message(fd, (const unsigned char*)reply, I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply)); } -#endif /* The index of each callback function corresponds to the numeric * value of the message type (see include/i3/ipc.h) */ -handler_t handlers[2] = { +handler_t handlers[5] = { handle_command, + handle_get_workspaces, + handle_subscribe, + handle_get_outputs, handle_tree }; From c0cd756b0fdf22f472fe8ba92f2ee7dd42f00580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sat, 20 Nov 2010 19:50:24 -0200 Subject: [PATCH 256/867] Support get_workspaces and get_outputs in i3-msg. --- i3-msg/main.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/i3-msg/main.c b/i3-msg/main.c index 0dc1165a..295fe683 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -130,6 +130,10 @@ int main(int argc, char *argv[]) { } else if (o == 't') { if (strcasecmp(optarg, "command") == 0) message_type = I3_IPC_MESSAGE_TYPE_COMMAND; + else if (strcasecmp(optarg, "get_workspaces") == 0) + message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES; + else if (strcasecmp(optarg, "get_outputs") == 0) + message_type = I3_IPC_MESSAGE_TYPE_GET_OUTPUTS; else if (strcasecmp(optarg, "tree") == 0) message_type = I3_IPC_MESSAGE_TYPE_GET_TREE; else { @@ -140,10 +144,10 @@ int main(int argc, char *argv[]) { } else if (o == 'q') { quiet = true; } else if (o == 'v') { - printf("i3-msg " I3_VERSION); + printf("i3-msg " I3_VERSION "\n"); return 0; } else if (o == 'h') { - printf("i3-msg " I3_VERSION); + printf("i3-msg " I3_VERSION "\n"); printf("i3-msg [-s ] [-t ] \n"); return 0; } From 767dcea7ba55309651d8f32abdf8a24e98e57963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sat, 20 Nov 2010 20:04:01 -0200 Subject: [PATCH 257/867] Send the workspace focus change event. --- src/workspace.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/workspace.c b/src/workspace.c index e13faa60..47a05eb8 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -216,6 +216,8 @@ void workspace_show(const char *num) { con_focus(next); workspace->fullscreen_mode = CF_OUTPUT; LOG("focused now = %p / %s\n", focused, focused->name); + + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); #if 0 /* Check if the workspace has not been used yet */ From e2517bd48c5eb64dda325c519186389e3a75622c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sat, 20 Nov 2010 20:14:28 -0200 Subject: [PATCH 258/867] Unlink the resolved path. --- src/ipc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipc.c b/src/ipc.c index fd4966ce..f0530904 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -594,7 +594,7 @@ int ipc_create_socket(const char *filename) { free(copy); /* Unlink the unix domain socket before */ - unlink(filename); + unlink(resolved); if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { perror("socket()"); From ae22fe065f64ab5b5421f731e3d1b6f39252021b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sat, 20 Nov 2010 20:25:32 -0200 Subject: [PATCH 259/867] Send change:empty too. --- src/workspace.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/workspace.c b/src/workspace.c index 47a05eb8..caa35c88 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -210,6 +210,7 @@ void workspace_show(const char *num) { if (!workspace_is_visible(old)) { LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); tree_close(old, false, false); + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}"); } } From 0f083f6f783688f07e5a826dcda6516392066abf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Nov 2010 21:39:09 +0100 Subject: [PATCH 260/867] i3-msg: s/tree/get_tree for consistency, add new types to error message --- i3-msg/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i3-msg/main.c b/i3-msg/main.c index 295fe683..a945d3b1 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -134,11 +134,11 @@ int main(int argc, char *argv[]) { message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES; else if (strcasecmp(optarg, "get_outputs") == 0) message_type = I3_IPC_MESSAGE_TYPE_GET_OUTPUTS; - else if (strcasecmp(optarg, "tree") == 0) + else if (strcasecmp(optarg, "get_tree") == 0) message_type = I3_IPC_MESSAGE_TYPE_GET_TREE; else { printf("Unknown message type\n"); - printf("Known types: command, get_workspaces\n"); + printf("Known types: command, get_workspaces, get_outputs, get_tree\n"); exit(EXIT_FAILURE); } } else if (o == 'q') { From 4549effe15696753f131bcc5fc62a6e5e49bb317 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Nov 2010 21:42:28 +0100 Subject: [PATCH 261/867] perl: use get_tree instead of get_workspaces --- dump-asy.pl | 2 +- gtk-tree-watch.pl | 2 +- testcases/t/02-fullscreen.t | 2 +- testcases/t/16-nestedcons.t | 2 +- testcases/t/26-regress-close.t | 2 +- testcases/t/27-regress-floating-parent.t | 2 +- testcases/t/34-invalid-command.t | 2 +- testcases/t/lib/i3test.pm | 6 +++--- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dump-asy.pl b/dump-asy.pl index 315dcc6f..dc2cbeab 100755 --- a/dump-asy.pl +++ b/dump-asy.pl @@ -11,7 +11,7 @@ use v5.10; my $i3 = i3("/tmp/nestedcons"); -my $tree = $i3->get_workspaces->recv; +my $tree = $i3->get_tree->recv; my $tmp = File::Temp->new(UNLINK => 0, SUFFIX => '.asy'); diff --git a/gtk-tree-watch.pl b/gtk-tree-watch.pl index b82d25d2..f15d0c18 100755 --- a/gtk-tree-watch.pl +++ b/gtk-tree-watch.pl @@ -116,7 +116,7 @@ my $layout_container = Gtk2::HBox->new(0, 0); $layout_sw->add_with_viewport($layout_container); sub copy_tree { - my $tree = $i3->get_workspaces->recv; + my $tree = $i3->get_tree->recv; # convert the tree back to json so we only rebuild/redraw when the tree is changed my $json = encode_json($tree); diff --git a/testcases/t/02-fullscreen.t b/testcases/t/02-fullscreen.t index aa6861fb..e13bff63 100644 --- a/testcases/t/02-fullscreen.t +++ b/testcases/t/02-fullscreen.t @@ -16,7 +16,7 @@ sub fullscreen_windows { } # get the output of this workspace -my $tree = $i3->get_workspaces->recv; +my $tree = $i3->get_tree->recv; my @outputs = @{$tree->{nodes}}; my $output = first { defined(first { $_->{name} eq $tmp } @{$_->{nodes}}) } @outputs; diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t index 3d392c46..8c66516c 100644 --- a/testcases/t/16-nestedcons.t +++ b/testcases/t/16-nestedcons.t @@ -10,7 +10,7 @@ my $i3 = i3("/tmp/nestedcons"); # Request tree #################### -my $tree = $i3->get_workspaces->recv; +my $tree = $i3->get_tree->recv; my $expected = { fullscreen_mode => 0, diff --git a/testcases/t/26-regress-close.t b/testcases/t/26-regress-close.t index 95bac458..125caf73 100644 --- a/testcases/t/26-regress-close.t +++ b/testcases/t/26-regress-close.t @@ -18,7 +18,7 @@ $i3->command('kill')->recv; $i3->command('kill')->recv; -my $tree = $i3->get_workspaces->recv; +my $tree = $i3->get_tree->recv; my @nodes = @{$tree->{nodes}}; ok(@nodes > 0, 'i3 still lives'); diff --git a/testcases/t/27-regress-floating-parent.t b/testcases/t/27-regress-floating-parent.t index c09858f6..f489eaa6 100644 --- a/testcases/t/27-regress-floating-parent.t +++ b/testcases/t/27-regress-floating-parent.t @@ -32,7 +32,7 @@ is(get_focused($tmp), $floating, 'floating window focused'); sleep 1; $i3->command('mode toggle')->recv; -my $tree = $i3->get_workspaces->recv; +my $tree = $i3->get_tree->recv; my @nodes = @{$tree->{nodes}}; ok(@nodes > 0, 'i3 still lives'); diff --git a/testcases/t/34-invalid-command.t b/testcases/t/34-invalid-command.t index 12380d46..e1cc2c6f 100644 --- a/testcases/t/34-invalid-command.t +++ b/testcases/t/34-invalid-command.t @@ -9,7 +9,7 @@ my $i3 = i3("/tmp/nestedcons"); $i3->command("blargh!")->recv; -my $tree = $i3->get_workspaces->recv; +my $tree = $i3->get_tree->recv; my @nodes = @{$tree->{nodes}}; ok(@nodes > 0, 'i3 still lives'); diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index d241cfae..c0a229dc 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -63,7 +63,7 @@ sub open_empty_con { sub get_workspace_names { my $i3 = i3("/tmp/nestedcons"); # TODO: use correct command as soon as AnyEvent::i3 is updated - my $tree = $i3->get_workspaces->recv; + my $tree = $i3->get_tree->recv; my @workspaces = map { @{$_->{nodes}} } @{$tree->{nodes}}; [ map { $_->{name} } @workspaces ] } @@ -78,7 +78,7 @@ sub get_unused_workspace { sub get_ws { my ($name) = @_; my $i3 = i3("/tmp/nestedcons"); - my $tree = $i3->get_workspaces->recv; + my $tree = $i3->get_tree->recv; my @ws = map { @{$_->{nodes}} } @{$tree->{nodes}}; # as there can only be one workspace with this name, we can safely @@ -100,7 +100,7 @@ sub get_ws_content { sub get_focused { my ($ws) = @_; my $i3 = i3("/tmp/nestedcons"); - my $tree = $i3->get_workspaces->recv; + my $tree = $i3->get_tree->recv; my @ws = map { @{$_->{nodes}} } @{$tree->{nodes}}; my @cons = grep { $_->{name} eq $ws } @ws; From a71d782da98982c166a56891c10bddc7ca911861 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Nov 2010 22:03:55 +0100 Subject: [PATCH 262/867] fix indention of src/ipc.c --- src/ipc.c | 655 +++++++++++++++++++++++++++--------------------------- 1 file changed, 327 insertions(+), 328 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index f0530904..7e76ace5 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * @@ -33,10 +33,10 @@ TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all * */ static void set_nonblock(int sockfd) { - int flags = fcntl(sockfd, F_GETFL, 0); - flags |= O_NONBLOCK; - if (fcntl(sockfd, F_SETFL, flags) < 0) - err(-1, "Could not set O_NONBLOCK"); + int flags = fcntl(sockfd, F_GETFL, 0); + flags |= O_NONBLOCK; + if (fcntl(sockfd, F_SETFL, flags) < 0) + err(-1, "Could not set O_NONBLOCK"); } /* @@ -44,56 +44,56 @@ static void set_nonblock(int sockfd) { * */ static 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 = strdup(path); - /* strip trailing slashes, if any */ - while (copy[strlen(copy)-1] == '/') - copy[strlen(copy)-1] = '\0'; + 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 = strdup(path); + /* strip trailing slashes, if any */ + while (copy[strlen(copy)-1] == '/') + copy[strlen(copy)-1] = '\0'; - char *sep = strrchr(copy, '/'); - if (sep == NULL) - return false; - *sep = '\0'; - bool result = false; - if (mkdirp(copy)) - result = mkdirp(path); - free(copy); + char *sep = strrchr(copy, '/'); + if (sep == NULL) + return false; + *sep = '\0'; + bool result = false; + if (mkdirp(copy)) + result = mkdirp(path); + free(copy); - return result; + return result; } static void ipc_send_message(int fd, const unsigned char *payload, int message_type, int message_size) { - int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + - sizeof(uint32_t) + message_size; - char msg[buffer_size]; - char *walk = msg; + int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + + sizeof(uint32_t) + message_size; + char msg[buffer_size]; + char *walk = msg; - strcpy(walk, "i3-ipc"); - walk += strlen("i3-ipc"); - memcpy(walk, &message_size, sizeof(uint32_t)); - walk += sizeof(uint32_t); - memcpy(walk, &message_type, sizeof(uint32_t)); - walk += sizeof(uint32_t); - memcpy(walk, payload, message_size); + strcpy(walk, "i3-ipc"); + walk += strlen("i3-ipc"); + memcpy(walk, &message_size, sizeof(uint32_t)); + walk += sizeof(uint32_t); + memcpy(walk, &message_type, sizeof(uint32_t)); + walk += sizeof(uint32_t); + memcpy(walk, payload, message_size); - int sent_bytes = 0; - int bytes_to_go = buffer_size; - while (sent_bytes < bytes_to_go) { - int n = write(fd, msg + sent_bytes, bytes_to_go); - if (n == -1) { - DLOG("write() failed: %s\n", strerror(errno)); - return; - } - - sent_bytes += n; - bytes_to_go -= n; + int sent_bytes = 0; + int bytes_to_go = buffer_size; + while (sent_bytes < bytes_to_go) { + int n = write(fd, msg + sent_bytes, bytes_to_go); + if (n == -1) { + DLOG("write() failed: %s\n", strerror(errno)); + return; } + + sent_bytes += n; + bytes_to_go -= n; + } } /* @@ -102,22 +102,22 @@ static void ipc_send_message(int fd, const unsigned char *payload, * */ void ipc_send_event(const char *event, uint32_t message_type, const char *payload) { - ipc_client *current; - TAILQ_FOREACH(current, &all_clients, clients) { - /* see if this client is interested in this event */ - bool interested = false; - for (int i = 0; i < current->num_events; i++) { - if (strcasecmp(current->events[i], event) != 0) - continue; - interested = true; - break; - } - if (!interested) - continue; - - ipc_send_message(current->fd, (const unsigned char*)payload, - message_type, strlen(payload)); + ipc_client *current; + TAILQ_FOREACH(current, &all_clients, clients) { + /* see if this client is interested in this event */ + bool interested = false; + for (int i = 0; i < current->num_events; i++) { + if (strcasecmp(current->events[i], event) != 0) + continue; + interested = true; + break; } + if (!interested) + continue; + + ipc_send_message(current->fd, (const unsigned char*)payload, + message_type, strlen(payload)); + } } /* @@ -126,11 +126,11 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa * */ void ipc_shutdown() { - ipc_client *current; - TAILQ_FOREACH(current, &all_clients, clients) { - shutdown(current->fd, SHUT_RDWR); - close(current->fd); - } + ipc_client *current; + TAILQ_FOREACH(current, &all_clients, clients) { + shutdown(current->fd, SHUT_RDWR); + close(current->fd); + } } /* @@ -139,116 +139,115 @@ void ipc_shutdown() { * */ IPC_HANDLER(command) { - /* To get a properly terminated buffer, we copy - * message_size bytes out of the buffer */ - char *command = scalloc(message_size + 1); - strncpy(command, (const char*)message, message_size); - LOG("IPC: received: *%s*\n", command); - const char *reply = parse_cmd((const char*)command); - tree_render(); - free(command); + /* To get a properly terminated buffer, we copy + * message_size bytes out of the buffer */ + char *command = scalloc(message_size + 1); + strncpy(command, (const char*)message, message_size); + LOG("IPC: received: *%s*\n", command); + const char *reply = parse_cmd((const char*)command); + tree_render(); + free(command); - /* If no reply was provided, we just use the default success message */ - if (reply == NULL) - reply = "{\"success\":true}"; - ipc_send_message(fd, (const unsigned char*)reply, - I3_IPC_REPLY_TYPE_COMMAND, strlen(reply)); + /* If no reply was provided, we just use the default success message */ + if (reply == NULL) + reply = "{\"success\":true}"; + ipc_send_message(fd, (const unsigned char*)reply, + I3_IPC_REPLY_TYPE_COMMAND, strlen(reply)); } void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { - y(map_open); - ystr("id"); - y(integer, (long int)con); + y(map_open); + ystr("id"); + y(integer, (long int)con); - ystr("type"); - y(integer, con->type); + ystr("type"); + y(integer, con->type); - ystr("orientation"); - y(integer, con->orientation); + ystr("orientation"); + y(integer, con->orientation); - ystr("urgent"); - y(integer, con->urgent); + ystr("urgent"); + y(integer, con->urgent); - ystr("focused"); - y(integer, (con == focused)); + ystr("focused"); + y(integer, (con == focused)); - ystr("layout"); - y(integer, con->layout); + ystr("layout"); + y(integer, con->layout); - ystr("border"); - y(integer, con->border_style); + ystr("border"); + y(integer, con->border_style); - ystr("rect"); - y(map_open); - ystr("x"); - y(integer, con->rect.x); - ystr("y"); - y(integer, con->rect.y); - ystr("width"); - y(integer, con->rect.width); - ystr("height"); - y(integer, con->rect.height); - y(map_close); + ystr("rect"); + y(map_open); + ystr("x"); + y(integer, con->rect.x); + ystr("y"); + y(integer, con->rect.y); + ystr("width"); + y(integer, con->rect.width); + ystr("height"); + y(integer, con->rect.height); + y(map_close); - ystr("name"); - ystr(con->name); + ystr("name"); + ystr(con->name); - ystr("window"); - if (con->window) - y(integer, con->window->id); - else y(null); + ystr("window"); + if (con->window) + y(integer, con->window->id); + else y(null); - ystr("nodes"); - y(array_open); - Con *node; - TAILQ_FOREACH(node, &(con->nodes_head), nodes) { - dump_node(gen, node, inplace_restart); + ystr("nodes"); + y(array_open); + Con *node; + TAILQ_FOREACH(node, &(con->nodes_head), nodes) { + dump_node(gen, node, inplace_restart); + } + y(array_close); + + ystr("floating_nodes"); + y(array_open); + TAILQ_FOREACH(node, &(con->floating_head), floating_windows) { + dump_node(gen, node, inplace_restart); + } + y(array_close); + + ystr("focus"); + y(array_open); + TAILQ_FOREACH(node, &(con->focus_head), nodes) { + y(integer, (long int)node); + } + y(array_close); + + ystr("fullscreen_mode"); + y(integer, con->fullscreen_mode); + + if (inplace_restart) { + if (con->window != NULL) { + ystr("swallows"); + y(array_open); + y(map_open); + ystr("id"); + y(integer, con->window->id); + y(map_close); + y(array_close); } - y(array_close); + } - ystr("floating_nodes"); - y(array_open); - TAILQ_FOREACH(node, &(con->floating_head), floating_windows) { - dump_node(gen, node, inplace_restart); - } - y(array_close); - - ystr("focus"); - y(array_open); - TAILQ_FOREACH(node, &(con->focus_head), nodes) { - y(integer, (long int)node); - } - y(array_close); - - ystr("fullscreen_mode"); - y(integer, con->fullscreen_mode); - - if (inplace_restart) { - if (con->window != NULL) { - ystr("swallows"); - y(array_open); - y(map_open); - ystr("id"); - y(integer, con->window->id); - y(map_close); - y(array_close); - } - } - - y(map_close); + y(map_close); } IPC_HANDLER(tree) { - yajl_gen gen = yajl_gen_alloc(NULL, NULL); - dump_node(gen, croot, false); + yajl_gen gen = yajl_gen_alloc(NULL, NULL); + dump_node(gen, croot, false); - const unsigned char *payload; - unsigned int length; - y(get_buf, &payload, &length); - - ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_TREE, length); - y(free); + const unsigned char *payload; + unsigned int length; + y(get_buf, &payload, &length); + ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_TREE, length); + y(free); } /* @@ -371,24 +370,24 @@ IPC_HANDLER(get_outputs) { */ static int add_subscription(void *extra, const unsigned char *s, unsigned int len) { - ipc_client *client = extra; + ipc_client *client = extra; - DLOG("should add subscription to extra %p, sub %.*s\n", client, len, s); - int event = client->num_events; + DLOG("should add subscription to extra %p, sub %.*s\n", client, len, s); + int event = client->num_events; - client->num_events++; - client->events = realloc(client->events, client->num_events * sizeof(char*)); - /* We copy the string because it is not null-terminated and strndup() - * is missing on some BSD systems */ - client->events[event] = scalloc(len+1); - memcpy(client->events[event], s, len); + client->num_events++; + client->events = realloc(client->events, client->num_events * sizeof(char*)); + /* We copy the string because it is not null-terminated and strndup() + * is missing on some BSD systems */ + client->events[event] = scalloc(len+1); + memcpy(client->events[event], s, len); - DLOG("client is now subscribed to:\n"); - for (int i = 0; i < client->num_events; i++) - DLOG("event %s\n", client->events[i]); - DLOG("(done)\n"); + DLOG("client is now subscribed to:\n"); + for (int i = 0; i < client->num_events; i++) + DLOG("event %s\n", client->events[i]); + DLOG("(done)\n"); - return 1; + return 1; } /* @@ -397,58 +396,58 @@ static int add_subscription(void *extra, const unsigned char *s, * */ IPC_HANDLER(subscribe) { - yajl_handle p; - yajl_callbacks callbacks; - yajl_status stat; - ipc_client *current, *client = NULL; + yajl_handle p; + yajl_callbacks callbacks; + yajl_status stat; + ipc_client *current, *client = NULL; - /* Search the ipc_client structure for this connection */ - TAILQ_FOREACH(current, &all_clients, clients) { - if (current->fd != fd) - continue; + /* Search the ipc_client structure for this connection */ + TAILQ_FOREACH(current, &all_clients, clients) { + if (current->fd != fd) + continue; - client = current; - break; - } + client = current; + break; + } - if (client == NULL) { - ELOG("Could not find ipc_client data structure for fd %d\n", fd); - return; - } + if (client == NULL) { + ELOG("Could not find ipc_client data structure for fd %d\n", fd); + return; + } - /* Setup the JSON parser */ - memset(&callbacks, 0, sizeof(yajl_callbacks)); - callbacks.yajl_string = add_subscription; + /* Setup the JSON parser */ + memset(&callbacks, 0, sizeof(yajl_callbacks)); + callbacks.yajl_string = add_subscription; - p = yajl_alloc(&callbacks, NULL, NULL, (void*)client); - stat = yajl_parse(p, (const unsigned char*)message, message_size); - if (stat != yajl_status_ok) { - unsigned char *err; - err = yajl_get_error(p, true, (const unsigned char*)message, - message_size); - ELOG("YAJL parse error: %s\n", err); - yajl_free_error(p, err); + p = yajl_alloc(&callbacks, NULL, NULL, (void*)client); + stat = yajl_parse(p, (const unsigned char*)message, message_size); + if (stat != yajl_status_ok) { + unsigned char *err; + err = yajl_get_error(p, true, (const unsigned char*)message, + message_size); + ELOG("YAJL parse error: %s\n", err); + yajl_free_error(p, err); - const char *reply = "{\"success\":false}"; - ipc_send_message(fd, (const unsigned char*)reply, - I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply)); - yajl_free(p); - return; - } - yajl_free(p); - const char *reply = "{\"success\":true}"; + const char *reply = "{\"success\":false}"; ipc_send_message(fd, (const unsigned char*)reply, I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply)); + yajl_free(p); + return; + } + yajl_free(p); + const char *reply = "{\"success\":true}"; + ipc_send_message(fd, (const unsigned char*)reply, + I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply)); } /* The index of each callback function corresponds to the numeric * value of the message type (see include/i3/ipc.h) */ handler_t handlers[5] = { - handle_command, - handle_get_workspaces, - handle_subscribe, - handle_get_outputs, - handle_tree + handle_command, + handle_get_workspaces, + handle_subscribe, + handle_get_outputs, + handle_tree }; /* @@ -462,87 +461,87 @@ handler_t handlers[5] = { * */ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { - char buf[2048]; - int n = read(w->fd, buf, sizeof(buf)); + char buf[2048]; + int n = read(w->fd, buf, sizeof(buf)); - /* On error or an empty message, we close the connection */ - if (n <= 0) { + /* On error or an empty message, we close the connection */ + if (n <= 0) { #if 0 - /* FIXME: I get these when closing a client socket, - * therefore we just treat them as an error. Is this - * correct? */ - if (errno == EAGAIN || errno == EWOULDBLOCK) - return; + /* FIXME: I get these when closing a client socket, + * therefore we just treat them as an error. Is this + * correct? */ + if (errno == EAGAIN || errno == EWOULDBLOCK) + return; #endif - /* If not, there was some kind of error. We don’t bother - * and close the connection */ - close(w->fd); + /* If not, there was some kind of error. We don’t bother + * and close the connection */ + close(w->fd); - /* Delete the client from the list of clients */ - ipc_client *current; - TAILQ_FOREACH(current, &all_clients, clients) { - if (current->fd != w->fd) - continue; + /* Delete the client from the list of clients */ + ipc_client *current; + TAILQ_FOREACH(current, &all_clients, clients) { + if (current->fd != w->fd) + continue; - for (int i = 0; i < current->num_events; i++) - free(current->events[i]); - /* We can call TAILQ_REMOVE because we break out of the - * TAILQ_FOREACH afterwards */ - TAILQ_REMOVE(&all_clients, current, clients); - break; - } - - ev_io_stop(EV_A_ w); - - DLOG("IPC: client disconnected\n"); - return; + for (int i = 0; i < current->num_events; i++) + free(current->events[i]); + /* We can call TAILQ_REMOVE because we break out of the + * TAILQ_FOREACH afterwards */ + TAILQ_REMOVE(&all_clients, current, clients); + break; } - /* Terminate the message correctly */ - buf[n] = '\0'; + ev_io_stop(EV_A_ w); - /* Check if the message starts with the i3 IPC magic code */ - if (n < strlen(I3_IPC_MAGIC)) { - DLOG("IPC: message too short, ignoring\n"); - return; + DLOG("IPC: client disconnected\n"); + return; + } + + /* Terminate the message correctly */ + buf[n] = '\0'; + + /* Check if the message starts with the i3 IPC magic code */ + if (n < strlen(I3_IPC_MAGIC)) { + DLOG("IPC: message too short, ignoring\n"); + return; + } + + if (strncmp(buf, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) { + DLOG("IPC: message does not start with the IPC magic\n"); + return; + } + + uint8_t *message = (uint8_t*)buf; + while (n > 0) { + DLOG("IPC: n = %d\n", n); + message += strlen(I3_IPC_MAGIC); + n -= strlen(I3_IPC_MAGIC); + + /* The next 32 bit after the magic are the message size */ + uint32_t message_size = *((uint32_t*)message); + message += sizeof(uint32_t); + n -= sizeof(uint32_t); + + if (message_size > n) { + DLOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n"); + return; } - if (strncmp(buf, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) { - DLOG("IPC: message does not start with the IPC magic\n"); - return; - } - - uint8_t *message = (uint8_t*)buf; - while (n > 0) { - DLOG("IPC: n = %d\n", n); - message += strlen(I3_IPC_MAGIC); - n -= strlen(I3_IPC_MAGIC); - - /* The next 32 bit after the magic are the message size */ - uint32_t message_size = *((uint32_t*)message); - message += sizeof(uint32_t); - n -= sizeof(uint32_t); - - if (message_size > n) { - DLOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n"); - return; - } - - /* The last 32 bits of the header are the message type */ - uint32_t message_type = *((uint32_t*)message); - message += sizeof(uint32_t); - n -= sizeof(uint32_t); - - if (message_type >= (sizeof(handlers) / sizeof(handler_t))) - DLOG("Unhandled message type: %d\n", message_type); - else { - handler_t h = handlers[message_type]; - h(w->fd, message, n, message_size, message_type); - } - n -= message_size; - message += message_size; + /* The last 32 bits of the header are the message type */ + uint32_t message_type = *((uint32_t*)message); + message += sizeof(uint32_t); + n -= sizeof(uint32_t); + + if (message_type >= (sizeof(handlers) / sizeof(handler_t))) + DLOG("Unhandled message type: %d\n", message_type); + else { + handler_t h = handlers[message_type]; + h(w->fd, message, n, message_size, message_type); } + n -= message_size; + message += message_size; + } } /* @@ -553,28 +552,28 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { * */ void ipc_new_client(EV_P_ struct ev_io *w, int revents) { - struct sockaddr_un peer; - socklen_t len = sizeof(struct sockaddr_un); - int client; - if ((client = accept(w->fd, (struct sockaddr*)&peer, &len)) < 0) { - if (errno == EINTR) - return; - else perror("accept()"); - return; - } + struct sockaddr_un peer; + socklen_t len = sizeof(struct sockaddr_un); + int client; + if ((client = accept(w->fd, (struct sockaddr*)&peer, &len)) < 0) { + if (errno == EINTR) + return; + else perror("accept()"); + return; + } - set_nonblock(client); + set_nonblock(client); - struct ev_io *package = scalloc(sizeof(struct ev_io)); - ev_io_init(package, ipc_receive_message, client, EV_READ); - ev_io_start(EV_A_ package); + struct ev_io *package = scalloc(sizeof(struct ev_io)); + ev_io_init(package, ipc_receive_message, client, EV_READ); + ev_io_start(EV_A_ package); - DLOG("IPC: new client connected\n"); + DLOG("IPC: new client connected\n"); - ipc_client *new = scalloc(sizeof(ipc_client)); - new->fd = client; + ipc_client *new = scalloc(sizeof(ipc_client)); + new->fd = client; - TAILQ_INSERT_TAIL(&all_clients, new, clients); + TAILQ_INSERT_TAIL(&all_clients, new, clients); } /* @@ -583,44 +582,44 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) { * */ int ipc_create_socket(const char *filename) { - int sockfd; + int sockfd; - char *resolved = resolve_tilde(filename); - DLOG("Creating IPC-socket at %s\n", resolved); - char *copy = sstrdup(resolved); - const char *dir = dirname(copy); - if (!path_exists(dir)) - mkdirp(dir); - free(copy); + char *resolved = resolve_tilde(filename); + DLOG("Creating IPC-socket at %s\n", resolved); + char *copy = sstrdup(resolved); + const char *dir = dirname(copy); + if (!path_exists(dir)) + mkdirp(dir); + free(copy); - /* Unlink the unix domain socket before */ - unlink(resolved); - - if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { - perror("socket()"); - free(resolved); - return -1; - } - - (void)fcntl(sockfd, F_SETFD, FD_CLOEXEC); - - struct sockaddr_un addr; - memset(&addr, 0, sizeof(struct sockaddr_un)); - addr.sun_family = AF_LOCAL; - strncpy(addr.sun_path, resolved, sizeof(addr.sun_path) - 1); - if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) { - perror("bind()"); - free(resolved); - return -1; - } + /* Unlink the unix domain socket before */ + unlink(resolved); + if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { + perror("socket()"); free(resolved); - set_nonblock(sockfd); + return -1; + } - if (listen(sockfd, 5) < 0) { - perror("listen()"); - return -1; - } + (void)fcntl(sockfd, F_SETFD, FD_CLOEXEC); - return sockfd; + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_LOCAL; + strncpy(addr.sun_path, resolved, sizeof(addr.sun_path) - 1); + if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) { + perror("bind()"); + free(resolved); + return -1; + } + + free(resolved); + set_nonblock(sockfd); + + if (listen(sockfd, 5) < 0) { + perror("listen()"); + return -1; + } + + return sockfd; } From fab8b84db753878ee13adf1c1950e83eff19cacf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Nov 2010 22:12:34 +0100 Subject: [PATCH 263/867] ipc: fix current_workspace --- include/data.h | 3 +++ src/ipc.c | 10 ++++------ src/tree.c | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/data.h b/include/data.h index c7fcb4a5..97d47948 100644 --- a/include/data.h +++ b/include/data.h @@ -186,6 +186,9 @@ struct xoutput { /** Name of the output */ char *name; + /** Pointer to the Con which represents this output */ + Con *con; + /** Whether the output is currently active (has a CRTC attached with a * valid mode) */ bool active; diff --git a/src/ipc.c b/src/ipc.c index 7e76ace5..40b6a712 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -343,13 +343,11 @@ IPC_HANDLER(get_outputs) { y(integer, output->rect.height); y(map_close); - /* - * XXX - * No idea how to handle this, where should we get this data from? - * I think we might need to keep a reference to the CT_OUTPUT Con in Output - */ ystr("current_workspace"); - y(null); + Con *ws = NULL; + if (output->con && (ws = con_get_fullscreen_con(output->con))) + ystr(ws->name); + else y(null); y(map_close); } diff --git a/src/tree.c b/src/tree.c index 734406b3..f9f10d59 100644 --- a/src/tree.c +++ b/src/tree.c @@ -69,6 +69,7 @@ void tree_init() { oc->name = strdup(output->name); oc->type = CT_OUTPUT; oc->rect = output->rect; + output->con = oc; char *name; asprintf(&name, "[i3 con] output %s", oc->name); From 1de97a1f1fae52b513b1d1b4b01ccf9fa70425b9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Nov 2010 23:35:49 +0100 Subject: [PATCH 264/867] correctly sort numbered workspaces (+testcase) Numbered workspaces (workspaces with a name containing only digits) will be inserted in the correct order now. Named workspaces are always sorted after numbered workspaces and in the order of creation. --- include/data.h | 4 +++ src/con.c | 34 +++++++++++++++++++-- src/ipc.c | 4 ++- src/tree.c | 4 ++- src/workspace.c | 16 +++++++++- testcases/t/39-ws-numbers.t | 59 +++++++++++++++++++++++++++++++++++++ testcases/t/lib/i3test.pm | 2 +- 7 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 testcases/t/39-ws-numbers.t diff --git a/include/data.h b/include/data.h index 97d47948..06ceef0b 100644 --- a/include/data.h +++ b/include/data.h @@ -281,6 +281,10 @@ struct Con { char *name; + /** the workspace number, if this Con is of type CT_WORKSPACE and the + * workspace is not a named workspace (for named workspaces, num == -1) */ + int num; + /* a sticky-group is an identifier which bundles several containers to a * group. The contents are shared between all of them, that is they are * displayed on whichever of the containers is currently visible */ diff --git a/src/con.c b/src/con.c index a01bdcc1..3d1441f6 100644 --- a/src/con.c +++ b/src/con.c @@ -72,6 +72,35 @@ void con_attach(Con *con, Con *parent) { con->parent = parent; Con *loop; Con *current = NULL; + struct nodes_head *nodes_head = &(parent->nodes_head); + + /* Workspaces are handled differently: they need to be inserted at the + * right position. */ + if (con->type == CT_WORKSPACE) { + DLOG("it's a workspace. num = %d\n", con->num); + if (con->num == -1 || TAILQ_EMPTY(nodes_head)) { + TAILQ_INSERT_TAIL(nodes_head, con, nodes); + } else { + current = TAILQ_FIRST(nodes_head); + if (con->num < current->num) { + /* we need to insert the container at the beginning */ + TAILQ_INSERT_HEAD(nodes_head, con, nodes); + return; + } + while (current->num != -1 && con->num > current->num) { + current = TAILQ_NEXT(current, nodes); + if (current == TAILQ_END(nodes_head)) { + current = NULL; + break; + } + } + /* we need to insert con after current, if current is not NULL */ + if (current) + TAILQ_INSERT_BEFORE(current, con, nodes); + else TAILQ_INSERT_TAIL(nodes_head, con, nodes); + } + goto add_to_focus_head; + } /* Get the first tiling container in focus stack */ TAILQ_FOREACH(loop, &(parent->focus_head), focused) { @@ -85,9 +114,10 @@ void con_attach(Con *con, Con *parent) { if (current) { DLOG("Inserting con = %p after last focused tiling con %p\n", con, current); - TAILQ_INSERT_AFTER(&(parent->nodes_head), current, con, nodes); - } else TAILQ_INSERT_TAIL(&(parent->nodes_head), con, nodes); + TAILQ_INSERT_AFTER(nodes_head, current, con, nodes); + } else TAILQ_INSERT_TAIL(nodes_head, con, nodes); +add_to_focus_head: /* We insert to the TAIL because con_focus() will correct this. * This way, we have the option to insert Cons without having * to focus them. */ diff --git a/src/ipc.c b/src/ipc.c index 40b6a712..1a76950a 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -269,7 +269,9 @@ IPC_HANDLER(get_workspaces) { y(map_open); ystr("num"); - y(integer, con_num_children(ws)); + if (ws->num == -1) + y(null); + else y(integer, ws->num); ystr("name"); ystr(ws->name); diff --git a/src/tree.c b/src/tree.c index f9f10d59..290f1fc2 100644 --- a/src/tree.c +++ b/src/tree.c @@ -77,10 +77,12 @@ void tree_init() { free(name); /* add a workspace to this output */ - ws = con_new(oc); + ws = con_new(NULL); ws->type = CT_WORKSPACE; + ws->num = c; asprintf(&(ws->name), "%d", c); c++; + con_attach(ws, oc); asprintf(&name, "[i3 con] workspace %s", ws->name); x_set_name(ws, name); diff --git a/src/workspace.c b/src/workspace.c index caa35c88..837ac1fe 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -38,14 +38,28 @@ Con *workspace_get(const char *num) { LOG("need to create this one\n"); output = con_get_output(focused); LOG("got output %p\n", output); - workspace = con_new(output); + /* We need to attach this container after setting its type. con_attach + * will handle CT_WORKSPACEs differently */ + workspace = con_new(NULL); char *name; asprintf(&name, "[i3 con] workspace %s", num); x_set_name(workspace, name); free(name); workspace->type = CT_WORKSPACE; workspace->name = strdup(num); + /* We set ->num to the number if this workspace’s name consists only of + * a positive number. Otherwise it’s a named ws and num will be -1. */ + char *end; + long parsed_num = strtol(num, &end, 10); + if (parsed_num == LONG_MIN || + parsed_num == LONG_MAX || + parsed_num < 0 || + (end && *end != '\0')) + workspace->num = -1; + else workspace->num = parsed_num; + LOG("num = %d\n", workspace->num); workspace->orientation = HORIZ; + con_attach(workspace, output); ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); } diff --git a/testcases/t/39-ws-numbers.t b/testcases/t/39-ws-numbers.t new file mode 100644 index 00000000..e3f35722 --- /dev/null +++ b/testcases/t/39-ws-numbers.t @@ -0,0 +1,59 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# Check if numbered workspaces and named workspaces are sorted in the right way +# in get_workspaces IPC output (necessary for i3bar etc.). +use i3test tests => 9; +use X11::XCB qw(:all); +use Time::HiRes qw(sleep); + +BEGIN { + use_ok('X11::XCB::Window'); +} + +my $i3 = i3("/tmp/nestedcons"); +my $x = X11::XCB::Connection->new; + +sub check_order { + my ($msg) = @_; + + my @ws = @{$i3->get_workspaces->recv}; + my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws; + my @sorted = sort @nums; + + cmp_deeply(\@nums, \@sorted, $msg); +} + +check_order('workspace order alright before testing'); + +############################################################################# +# open a window to keep this ws open +############################################################################# + +$i3->command("workspace 93")->recv; + +open_standard_window($x); + +my @ws = @{$i3->get_workspaces->recv}; +my @f = grep { defined($_->{num}) && $_->{num} == 93 } @ws; +is(@f, 1, 'ws 93 found by num'); +check_order('workspace order alright after opening 93'); + +$i3->command("workspace 92")->recv; +open_standard_window($x); +check_order('workspace order alright after opening 92'); + +$i3->command("workspace 94")->recv; +open_standard_window($x); +check_order('workspace order alright after opening 94'); + +$i3->command("workspace 96")->recv; +open_standard_window($x); +check_order('workspace order alright after opening 96'); + +$i3->command("workspace foo")->recv; +open_standard_window($x); +check_order('workspace order alright after opening foo'); + +$i3->command("workspace 91")->recv; +open_standard_window($x); +check_order('workspace order alright after opening 91'); diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index c0a229dc..83b48e11 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -10,7 +10,7 @@ use List::Util qw(first); use v5.10; use Exporter (); -our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content get_ws get_focused open_empty_con); +our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window); BEGIN { my $window_count = 0; From a1c861e6e0d8171f9a8d7f56ad3ce35152c46420 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Nov 2010 11:25:02 +0100 Subject: [PATCH 265/867] Bugfix: Correctly count variables when parsing the configfile (Thanks dbp) --- src/cfgparse.y | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 7818c136..7bbf66bf 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -119,14 +119,21 @@ void parse_file(const char *f) { * how much extra bytes it requires when replaced. */ struct Variable *current, *nearest; int extra_bytes = 0; + /* We need to copy the buffer because we need to invalidate the + * variables (otherwise we will count them twice, which is bad when + * 'extra' is negative) */ + char *bufcopy = sstrdup(buf); SLIST_FOREACH(current, &variables, variables) { int extra = (strlen(current->value) - strlen(current->key)); char *next; for (next = buf; (next = strcasestr(buf + (next - buf), current->key)) != NULL; - next += strlen(current->key)) + next += strlen(current->key)) { + *next = '_'; extra_bytes += extra; + } } + FREE(bufcopy); /* Then, allocate a new buffer and copy the file over to the new one, * but replace occurences of our variables */ From 4d7c24b92cd05763512e3efde070825f0134be08 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Nov 2010 11:25:02 +0100 Subject: [PATCH 266/867] Bugfix: Correctly count variables when parsing the configfile (Thanks dbp) --- src/cfgparse.y | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 5de08bde..260e3013 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -106,14 +106,21 @@ void parse_file(const char *f) { * how much extra bytes it requires when replaced. */ struct Variable *current, *nearest; int extra_bytes = 0; + /* We need to copy the buffer because we need to invalidate the + * variables (otherwise we will count them twice, which is bad when + * 'extra' is negative) */ + char *bufcopy = sstrdup(buf); SLIST_FOREACH(current, &variables, variables) { int extra = (strlen(current->value) - strlen(current->key)); char *next; for (next = buf; (next = strcasestr(buf + (next - buf), current->key)) != NULL; - next += strlen(current->key)) + next += strlen(current->key)) { + *next = '_'; extra_bytes += extra; + } } + FREE(bufcopy); /* Then, allocate a new buffer and copy the file over to the new one, * but replace occurences of our variables */ From e744b48b304c42303a79f181932c19d202d336d2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Nov 2010 18:18:40 +0100 Subject: [PATCH 267/867] Bugfix: When handling an EnterNotify for a child window, access the correct con->layout --- src/handlers.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index f5966db9..7e65af4d 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -191,9 +191,12 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, if (event_is_ignored(event->sequence)) return 1; + bool enter_child = false; /* Get container by frame or by child window */ - if ((con = con_by_frame_id(event->event)) == NULL) + if ((con = con_by_frame_id(event->event)) == NULL) { con = con_by_window_id(event->event); + enter_child = true; + } /* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */ if (con == NULL) { @@ -203,7 +206,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, } /* see if the user entered the window on a certain window decoration */ - int layout = con->layout; + int layout = (enter_child ? con->parent->layout : con->layout); Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) if (rect_contains(child->deco_rect, event->event_x, event->event_y)) { From 962fe075b957bb91574e14e57cc3f79e8149c913 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Nov 2010 18:37:34 +0100 Subject: [PATCH 268/867] Bugfix: Upon ExposEvents, redraw decoration also for the window itself In the meantime, windows can have decorations (borders) on their own frame, too. --- src/handlers.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/handlers.c b/src/handlers.c index 7e65af4d..ce2b4e16 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -609,6 +609,9 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * return 1; } + if (parent->window) + x_draw_decoration(parent); + TAILQ_FOREACH(con, &(parent->nodes_head), nodes) { LOG("expose for con %p / %s\n", con, con->name); if (con->window) From df7788386dc99c186cd00a6c07a71cc261b41318 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Nov 2010 18:48:27 +0100 Subject: [PATCH 269/867] re-indent click function --- src/click.c | 124 +++++++++++++++++++++++++++------------------------- 1 file changed, 64 insertions(+), 60 deletions(-) diff --git a/src/click.c b/src/click.c index dc73cbdc..d33c5241 100644 --- a/src/click.c +++ b/src/click.c @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * @@ -237,74 +237,78 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client, #endif int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) { - Con *con; - LOG("Button %d pressed on window 0x%08x\n", event->state, event->event); + Con *con; + LOG("Button %d pressed on window 0x%08x\n", event->state, event->event); - con = con_by_window_id(event->event); - bool border_click = false; - if (con == NULL) { - con = con_by_frame_id(event->event); - border_click = true; - } - LOG("border_click = %d\n", border_click); + con = con_by_window_id(event->event); + bool border_click = false; + if (con == NULL) { + con = con_by_frame_id(event->event); + border_click = true; + } + LOG("border_click = %d\n", border_click); //if (con && con->type == CT_FLOATING_CON) //con = TAILQ_FIRST(&(con->nodes_head)); - /* See if this was a click with the configured modifier. If so, we need - * to move around the client if it was floating. if not, we just process - * as usual. */ - LOG("state = %d, floating_modifier = %d\n", event->state, config.floating_modifier); - if (border_click || - (config.floating_modifier != 0 && - (event->state & config.floating_modifier) == config.floating_modifier)) { - if (con == NULL) { - LOG("Not handling, floating_modifier was pressed and no client found\n"); - return 1; - } - LOG("handling\n"); + /* See if this was a click with the configured modifier. If so, we need + * to move around the client if it was floating. if not, we just process + * as usual. */ + LOG("state = %d, floating_modifier = %d\n", event->state, config.floating_modifier); + if (border_click || + (config.floating_modifier != 0 && + (event->state & config.floating_modifier) == config.floating_modifier)) { + if (con == NULL) { + LOG("Not handling, floating_modifier was pressed and no client found\n"); + return 1; + } + LOG("handling\n"); #if 0 - if (con->fullscreen) { - LOG("Not handling, client is in fullscreen mode\n"); - return 1; - } -#endif - if ((border_click && con->type == CT_FLOATING_CON) || - (!border_click && con_is_floating(con))) { - /* floating operations are always on the container around - * the "payload container", so make sure we use the right one */ - Con *floatingcon = (border_click ? con : con->parent); - LOG("button %d pressed\n", event->detail); - if (event->detail == 1) { - LOG("left mouse button, dragging\n"); - floating_drag_window(floatingcon, event); - } - else if (event->detail == 3) { - bool proportional = (event->state & BIND_SHIFT); - DLOG("right mouse button\n"); - floating_resize_window(floatingcon, proportional, event); - } - return 1; - } - -#if 0 - if (!floating_mod_on_tiled_client(conn, client, event)) { -#endif - xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); - xcb_flush(conn); -#if 0 - } -#endif - + if (con->fullscreen) { + LOG("Not handling, client is in fullscreen mode\n"); return 1; } +#endif + if ((border_click && con->type == CT_FLOATING_CON) || + (!border_click && con_is_floating(con))) { + /* floating operations are always on the container around + * the "payload container", so make sure we use the right one */ + Con *floatingcon = (border_click ? con : con->parent); + LOG("button %d pressed\n", event->detail); + if (event->detail == 1) { + LOG("left mouse button, dragging\n"); + floating_drag_window(floatingcon, event); + } + else if (event->detail == 3) { + bool proportional = (event->state & BIND_SHIFT); + DLOG("right mouse button\n"); + floating_resize_window(floatingcon, proportional, event); + } + return 1; + } - /* click to focus */ - con_focus(con); - tree_render(); + if (con->layout == L_STACKED) { + DLOG("stacked!\n"); + } - xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); - xcb_flush(conn); - return 0; +#if 0 + if (!floating_mod_on_tiled_client(conn, client, event)) { +#endif + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + xcb_flush(conn); +#if 0 + } +#endif + + return 1; + } + + /* click to focus */ + con_focus(con); + tree_render(); + + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + xcb_flush(conn); + return 0; #if 0 if (client == NULL) { /* The client was neither on a client’s titlebar nor on a client itself, maybe on a stack_window? */ From db3002fce0474eaa4a769248428d8aec929b64c9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Nov 2010 18:57:15 +0100 Subject: [PATCH 270/867] Implement click on stack/tab decoration to focus --- src/click.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/click.c b/src/click.c index d33c5241..0304e105 100644 --- a/src/click.c +++ b/src/click.c @@ -286,8 +286,18 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ return 1; } - if (con->layout == L_STACKED) { - DLOG("stacked!\n"); + if (con->layout == L_STACKED || con->layout == L_TABBED) { + DLOG("stacked! click is on %d, %d\n", event->event_x, event->event_y); + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + if (!rect_contains(child->deco_rect, event->event_x, event->event_y)) + continue; + + con_focus(child); + break; + } + tree_render(); + return 1; } #if 0 From 97bc8f4b8600b5d95495eee48f79c0e1451c7963 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Nov 2010 19:24:14 +0100 Subject: [PATCH 271/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20draw=20decora?= =?UTF-8?q?tions=20of=20CT=5FFLOATING=5FCONs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/x.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/x.c b/src/x.c index 0de6c65a..c489dca7 100644 --- a/src/x.c +++ b/src/x.c @@ -224,11 +224,15 @@ void x_window_kill(xcb_window_t window) { * */ void x_draw_decoration(Con *con) { - /* this code needs to run for: + /* This code needs to run for: * • leaf containers * • non-leaf containers which are in a stacking container + * + * It does not need to run for: + * • floating containers (they don’t have a decoration) */ - if (!con_is_leaf(con) && con->parent->layout != L_STACKED) + if ((!con_is_leaf(con) && con->parent->layout != L_STACKED) || + con->type == CT_FLOATING_CON) return; DLOG("decoration should be rendered for con %p\n", con); From b2db9ac797a9b5adc55c17e57b54ca34eaeb57ab Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Nov 2010 20:15:08 +0100 Subject: [PATCH 272/867] Bugfix: always reset state->initial to false, not only for different stacking order As initial may be set to true again after initializing the window, we need to ensure that it is properly cleared. Otherwise, this leads to ghost windows when unmapping (such as the Xpdf about dialog) due to i3 issuing MapWindow after an Unmap happened but before i3 actually received/handled the UnmapNotify. --- src/x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/x.c b/src/x.c index c489dca7..192e7b89 100644 --- a/src/x.c +++ b/src/x.c @@ -539,7 +539,6 @@ void x_push_changes(Con *con) { if (prev != old_prev) order_changed = true; if ((state->initial || order_changed) && prev != CIRCLEQ_END(&state_head)) { - state->initial = false; LOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id); uint32_t mask = 0; mask |= XCB_CONFIG_WINDOW_SIBLING; @@ -548,6 +547,7 @@ void x_push_changes(Con *con) { xcb_configure_window(conn, prev->id, mask, values); } + state->initial = false; } xcb_window_t to_focus = focused->frame; From 65bd71b213da5ae570b42b0fb4c8acfa77dccaac Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Nov 2010 22:15:09 +0100 Subject: [PATCH 273/867] remove some obsolete code --- src/util.c | 110 ----------------------------------------------------- 1 file changed, 110 deletions(-) diff --git a/src/util.c b/src/util.c index df69ecc4..04ec1508 100644 --- a/src/util.c +++ b/src/util.c @@ -287,116 +287,6 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { redecorate_window(conn, client); } -/* - * Called when the user switches to another mode or when the container is - * destroyed and thus needs to be cleaned up. - * - */ -void leave_stack_mode(xcb_connection_t *conn, Container *container) { - /* When going out of stacking mode, we need to close the window */ - struct Stack_Window *stack_win = &(container->stack_win); - - SLIST_REMOVE(&stack_wins, stack_win, Stack_Window, stack_windows); - - xcb_free_gc(conn, stack_win->pixmap.gc); - xcb_free_pixmap(conn, stack_win->pixmap.id); - xcb_destroy_window(conn, stack_win->window); - - stack_win->rect.width = -1; - stack_win->rect.height = -1; -} - -/* - * Switches the layout of the given container taking care of the necessary house-keeping - * - */ -void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) { - if (mode == MODE_STACK || mode == MODE_TABBED) { - /* When we’re already in stacking mode, nothing has to be done */ - if ((mode == MODE_STACK && container->mode == MODE_STACK) || - (mode == MODE_TABBED && container->mode == MODE_TABBED)) - return; - - if (container->mode == MODE_STACK || container->mode == MODE_TABBED) - goto after_stackwin; - - /* When entering stacking mode, we need to open a window on - * which we can draw the title bars of the clients, it has - * height 1 because we don’t bother here with calculating the - * correct height - it will be adjusted when rendering anyways. - * Also, we need to use max(width, 1) because windows cannot - * be created with either width == 0 or height == 0. */ - Rect rect = {container->x, container->y, max(container->width, 1), 1}; - - uint32_t mask = 0; - uint32_t values[2]; - - /* Don’t generate events for our new window, it should *not* be managed */ - mask |= XCB_CW_OVERRIDE_REDIRECT; - values[0] = 1; - - /* We want to know when… */ - mask |= XCB_CW_EVENT_MASK; - values[1] = XCB_EVENT_MASK_ENTER_WINDOW | /* …mouse is moved into our window */ - XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed */ - XCB_EVENT_MASK_EXPOSURE; /* …our window needs to be redrawn */ - - struct Stack_Window *stack_win = &(container->stack_win); - stack_win->window = create_window(conn, rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values); - - stack_win->rect.height = 0; - - /* Initialize the entry for our cached pixmap. It will be - * created as soon as it’s needed (see cached_pixmap_prepare). */ - memset(&(stack_win->pixmap), 0, sizeof(struct Cached_Pixmap)); - stack_win->pixmap.referred_rect = &stack_win->rect; - stack_win->pixmap.referred_drawable = stack_win->window; - - stack_win->container = container; - - SLIST_INSERT_HEAD(&stack_wins, stack_win, stack_windows); - } else { - if (container->mode == MODE_STACK || container->mode == MODE_TABBED) - leave_stack_mode(conn, container); - } -after_stackwin: - container->mode = mode; - - /* Force reconfiguration of each client */ - Client *client; - - CIRCLEQ_FOREACH(client, &(container->clients), clients) - client->force_reconfigure = true; - - render_layout(conn); - - if (container->currently_focused != NULL) { - /* We need to make sure that this client is above *each* of the - * other clients in this container */ - Client *last_focused = get_last_focused_client(conn, container, container->currently_focused); - - CIRCLEQ_FOREACH(client, &(container->clients), clients) { - if (client == container->currently_focused || client == last_focused) - continue; - - DLOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame); - uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW }; - xcb_configure_window(conn, client->frame, - XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); - } - - if (last_focused != NULL) { - DLOG("Putting last_focused directly underneath the currently focused\n"); - uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW }; - xcb_configure_window(conn, last_focused->frame, - XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); - } - - - set_focus(conn, container->currently_focused, true); - } -} - /* * Gets the first matching client for the given window class/window title. * If the paramater specific is set to a specific client, only this one From c3981e12d3147e62c17abac89f5480fe64579d36 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Nov 2010 22:38:05 +0100 Subject: [PATCH 274/867] Bugfix: use bufcopy instead of buf (Thanks fernando) --- src/cfgparse.y | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 260e3013..c2227f67 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -113,8 +113,8 @@ void parse_file(const char *f) { SLIST_FOREACH(current, &variables, variables) { int extra = (strlen(current->value) - strlen(current->key)); char *next; - for (next = buf; - (next = strcasestr(buf + (next - buf), current->key)) != NULL; + for (next = bufcopy; + (next = strcasestr(bufcopy + (next - bufcopy), current->key)) != NULL; next += strlen(current->key)) { *next = '_'; extra_bytes += extra; From a8c4d255cf44392f9f70a0e368fa46c2f33137ac Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Nov 2010 22:38:05 +0100 Subject: [PATCH 275/867] Bugfix: use bufcopy instead of buf (Thanks fernando) --- src/cfgparse.y | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 7bbf66bf..666445cc 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -126,8 +126,8 @@ void parse_file(const char *f) { SLIST_FOREACH(current, &variables, variables) { int extra = (strlen(current->value) - strlen(current->key)); char *next; - for (next = buf; - (next = strcasestr(buf + (next - buf), current->key)) != NULL; + for (next = bufcopy; + (next = strcasestr(bufcopy + (next - bufcopy), current->key)) != NULL; next += strlen(current->key)) { *next = '_'; extra_bytes += extra; From 77d0d42ed2d7ac8cafe267c92b35a81c1b9491eb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Nov 2010 23:08:12 +0100 Subject: [PATCH 276/867] look and feel: create split container when switching workspace layout Quote from the source: When the container type is CT_WORKSPACE, the user wants to change the whole workspace into stacked/tabbed mode. To do this and still allow intuitive operations (like level-up and then opening a new window), we need to create a new split container. */ --- include/con.h | 8 ++++++++ src/cmdparse.y | 4 ++-- src/con.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/include/con.h b/include/con.h index bdf0b2a2..9194b0e0 100644 --- a/include/con.h +++ b/include/con.h @@ -153,4 +153,12 @@ Rect con_border_style_rect(Con *con); */ int con_border_style(Con *con); +/** + * This function changes the layout of a given container. Use it to handle + * special cases like changing a whole workspace to stacked/tabbed (creates a + * new split container before). + * + */ +void con_set_layout(Con *con, int layout); + #endif diff --git a/src/cmdparse.y b/src/cmdparse.y index c2f0222e..931729d8 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -574,11 +574,11 @@ layout: /* check if the match is empty, not if the result is empty */ if (match_is_empty(¤t_match)) - focused->parent->layout = $3; + con_set_layout(focused->parent, $3); else { TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); - current->con->layout = $3; + con_set_layout(current->con, $3); } } diff --git a/src/con.c b/src/con.c index 3d1441f6..2454684e 100644 --- a/src/con.c +++ b/src/con.c @@ -544,3 +544,47 @@ int con_border_style(Con *con) { return con->border_style; } + +/* + * This function changes the layout of a given container. Use it to handle + * special cases like changing a whole workspace to stacked/tabbed (creates a + * new split container before). + * + */ +void con_set_layout(Con *con, int layout) { + /* When the container type is CT_WORKSPACE, the user wants to change the + * whole workspace into stacked/tabbed mode. To do this and still allow + * intuitive operations (like level-up and then opening a new window), we + * need to create a new split container. */ + if (con->type == CT_WORKSPACE) { + DLOG("Creating new split container\n"); + /* 1: create a new split container */ + Con *new = con_new(NULL); + new->parent = con; + + /* 2: set the requested layout on the split con */ + new->layout = layout; + + /* 3: While the layout is irrelevant in stacked/tabbed mode, it needs + * to be set. Otherwise, this con will not be interpreted as a split + * container. */ + new->orientation = HORIZ; + + /* 4: move the existing cons of this workspace below the new con */ + DLOG("Moving cons\n"); + Con *child; + while (!TAILQ_EMPTY(&(con->nodes_head))) { + child = TAILQ_FIRST(&(con->nodes_head)); + con_detach(child); + con_attach(child, new); + } + + /* 4: attach the new split container to the workspace */ + DLOG("Attaching new split to ws\n"); + con_attach(new, con); + + return; + } + + con->layout = layout; +} From 2faac652372ec4f360cff15104ba5c734fe8d1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Mon, 22 Nov 2010 21:28:21 -0200 Subject: [PATCH 277/867] Always add to the focus list, fixes crash. --- src/con.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/con.c b/src/con.c index 2454684e..4824510f 100644 --- a/src/con.c +++ b/src/con.c @@ -85,19 +85,19 @@ void con_attach(Con *con, Con *parent) { if (con->num < current->num) { /* we need to insert the container at the beginning */ TAILQ_INSERT_HEAD(nodes_head, con, nodes); - return; - } - while (current->num != -1 && con->num > current->num) { - current = TAILQ_NEXT(current, nodes); - if (current == TAILQ_END(nodes_head)) { - current = NULL; - break; + } else { + while (current->num != -1 && con->num > current->num) { + current = TAILQ_NEXT(current, nodes); + if (current == TAILQ_END(nodes_head)) { + current = NULL; + break; + } } + /* we need to insert con after current, if current is not NULL */ + if (current) + TAILQ_INSERT_BEFORE(current, con, nodes); + else TAILQ_INSERT_TAIL(nodes_head, con, nodes); } - /* we need to insert con after current, if current is not NULL */ - if (current) - TAILQ_INSERT_BEFORE(current, con, nodes); - else TAILQ_INSERT_TAIL(nodes_head, con, nodes); } goto add_to_focus_head; } From 1d52bf179b573344a9a7ab697f7cd87ce72b2bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Mon, 22 Nov 2010 21:40:05 -0200 Subject: [PATCH 278/867] Run the autostart commands. --- src/main.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main.c b/src/main.c index 787ff6fb..f0118f98 100644 --- a/src/main.c +++ b/src/main.c @@ -338,5 +338,14 @@ int main(int argc, char *argv[]) { manage_existing_windows(root); + /* Autostarting exec-lines */ + if (autostart) { + struct Autostart *exec; + TAILQ_FOREACH(exec, &autostarts, autostarts) { + LOG("auto-starting %s\n", exec->command); + start_application(exec->command); + } + } + ev_loop(loop, 0); } From 6431d3d1870c0a21435a32933f8447a092ce9343 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Nov 2010 16:44:20 +0100 Subject: [PATCH 279/867] fix indention --- src/handlers.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index ce2b4e16..90f6c1b2 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -324,8 +324,8 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure int c = 0; #define COPY_MASK_MEMBER(mask_member, event_member) do { \ if (event->value_mask & mask_member) { \ - mask |= mask_member; \ - values[c++] = event->event_member; \ + mask |= mask_member; \ + values[c++] = event->event_member; \ } \ } while (0) From 3f3fa08b15956f2e2641d08134b8e486cf0d84d3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Nov 2010 16:44:32 +0100 Subject: [PATCH 280/867] more debug for ClientMessages --- src/handlers.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 90f6c1b2..36b2a2fd 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -665,12 +665,18 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message_event_t *event) { LOG("ClientMessage for window 0x%08x\n", event->window); if (event->type == atoms[_NET_WM_STATE]) { - if (event->format != 32 || event->data.data32[1] != atoms[_NET_WM_STATE_FULLSCREEN]) + if (event->format != 32 || event->data.data32[1] != atoms[_NET_WM_STATE_FULLSCREEN]) { + DLOG("atom in clientmessage is %d, fullscreen is %d\n", + event->data.data32[1], atoms[_NET_WM_STATE_FULLSCREEN]); + DLOG("not about fullscreen atom\n"); return 0; + } Con *con = con_by_window_id(event->window); - if (con == NULL) + if (con == NULL) { + DLOG("Could not get window for client message\n"); return 0; + } /* Check if the fullscreen state should be toggled */ if ((con->fullscreen_mode != CF_NONE && @@ -678,8 +684,10 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message event->data.data32[0] == _NET_WM_STATE_TOGGLE)) || (con->fullscreen_mode == CF_NONE && (event->data.data32[0] == _NET_WM_STATE_ADD || - event->data.data32[0] == _NET_WM_STATE_TOGGLE))) - con_toggle_fullscreen(con); + event->data.data32[0] == _NET_WM_STATE_TOGGLE))) { + DLOG("toggling fullscreen\n"); + con_toggle_fullscreen(con); + } tree_render(); x_push_changes(croot); From d47a1edf22628133dae2e2168895857582254931 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Nov 2010 16:44:45 +0100 Subject: [PATCH 281/867] Bugfix: configure windows before mapping, correctly store window_rect instead of rect --- src/x.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/x.c b/src/x.c index 192e7b89..88463b1a 100644 --- a/src/x.c +++ b/src/x.c @@ -435,6 +435,25 @@ static void x_push_node(Con *con) { con, con->window->id, con->ignore_unmap); } + bool fake_notify = false; + /* set new position if rect changed */ + if (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0) { + LOG("setting rect (%d, %d, %d, %d)\n", rect.x, rect.y, rect.width, rect.height); + xcb_set_window_rect(conn, con->frame, rect); + memcpy(&(state->rect), &rect, sizeof(Rect)); + fake_notify = true; + } + + /* dito, but for child windows */ + if (con->window != NULL && + memcmp(&(state->window_rect), &(con->window_rect), sizeof(Rect)) != 0) { + LOG("setting window rect (%d, %d, %d, %d)\n", + con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height); + xcb_set_window_rect(conn, con->window->id, con->window_rect); + memcpy(&(state->window_rect), &(con->window_rect), sizeof(Rect)); + fake_notify = true; + } + /* map/unmap if map state changed, also ensure that the child window * is changed if we are mapped *and* in initial state (meaning the * container was empty before, but now got a child) */ @@ -483,25 +502,6 @@ static void x_push_node(Con *con) { state->mapped = con->mapped; } - bool fake_notify = false; - /* set new position if rect changed */ - if (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0) { - LOG("setting rect (%d, %d, %d, %d)\n", rect.x, rect.y, rect.width, rect.height); - xcb_set_window_rect(conn, con->frame, rect); - memcpy(&(state->rect), &rect, sizeof(Rect)); - fake_notify = true; - } - - /* dito, but for child windows */ - if (con->window != NULL && - memcmp(&(state->window_rect), &(con->window_rect), sizeof(Rect)) != 0) { - LOG("setting window rect (%d, %d, %d, %d)\n", - con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height); - xcb_set_window_rect(conn, con->window->id, con->window_rect); - memcpy(&(state->rect), &(con->rect), sizeof(Rect)); - fake_notify = true; - } - if (fake_notify) { LOG("Sending fake configure notify\n"); fake_absolute_configure_notify(con); From 2c3e5dbc6569470ace2946589a2138b2b30385dd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Nov 2010 17:45:23 +0100 Subject: [PATCH 282/867] Bugfix: unmap windows in a separate step to avoid focus problems with fullscreen windows This fixes an ugly bug with Adobe Flash in fullscreen mode, for example on YouTube. See comments in the diff for some explanation. --- src/x.c | 114 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 39 deletions(-) diff --git a/src/x.c b/src/x.c index 88463b1a..84659100 100644 --- a/src/x.c +++ b/src/x.c @@ -454,51 +454,33 @@ static void x_push_node(Con *con) { fake_notify = true; } - /* map/unmap if map state changed, also ensure that the child window + /* Map if map state changed, also ensure that the child window * is changed if we are mapped *and* in initial state (meaning the - * container was empty before, but now got a child) */ - if (state->mapped != con->mapped || (con->mapped && state->initial)) { - if (!con->mapped) { - xcb_void_cookie_t cookie; - if (con->window != NULL) { - /* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */ - long data[] = { XCB_WM_STATE_WITHDRAWN, XCB_NONE }; - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, - atoms[WM_STATE], atoms[WM_STATE], 32, 2, data); - } + * container was empty before, but now got a child). Unmaps are handled in + * x_push_node_unmaps(). */ + if ((state->mapped != con->mapped || (con->mapped && state->initial)) && + con->mapped) { + xcb_void_cookie_t cookie; - cookie = xcb_unmap_window(conn, con->frame); - LOG("unmapping container (serial %d)\n", cookie.sequence); - /* we need to increase ignore_unmap for this container (if it contains a window) and for every window "under" this one which contains a window */ - if (con->window != NULL) { - con->ignore_unmap++; - DLOG("ignore_unmap for con %p (frame 0x%08x) now %d\n", con, con->frame, con->ignore_unmap); - } - /* Ignore enter_notifies which are generated when unmapping */ - add_ignore_event(cookie.sequence); - } else { - xcb_void_cookie_t cookie; + if (con->window != NULL) { + /* Set WM_STATE_NORMAL because GTK applications don’t want to + * drag & drop if we don’t. Also, xprop(1) needs it. */ + long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE }; + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, + atoms[WM_STATE], atoms[WM_STATE], 32, 2, data); + } - if (con->window != NULL) { - /* Set WM_STATE_NORMAL because GTK applications don’t want to - * drag & drop if we don’t. Also, xprop(1) needs it. */ - long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE }; - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, - atoms[WM_STATE], atoms[WM_STATE], 32, 2, data); - } - - if (state->initial && con->window != NULL) { - cookie = xcb_map_window(conn, con->window->id); - LOG("mapping child window (serial %d)\n", cookie.sequence); - /* Ignore enter_notifies which are generated when mapping */ - add_ignore_event(cookie.sequence); - } - - cookie = xcb_map_window(conn, con->frame); - LOG("mapping container (serial %d)\n", cookie.sequence); + if (state->initial && con->window != NULL) { + cookie = xcb_map_window(conn, con->window->id); + LOG("mapping child window (serial %d)\n", cookie.sequence); /* Ignore enter_notifies which are generated when mapping */ add_ignore_event(cookie.sequence); } + + cookie = xcb_map_window(conn, con->frame); + LOG("mapping container (serial %d)\n", cookie.sequence); + /* Ignore enter_notifies which are generated when mapping */ + add_ignore_event(cookie.sequence); state->mapped = con->mapped; } @@ -518,6 +500,57 @@ static void x_push_node(Con *con) { x_draw_decoration(con); } +/* + * Same idea as in x_push_node(), but this function only unmaps windows. It is + * necessary to split this up to handle new fullscreen clients properly: The + * new window needs to be mapped and focus needs to be set *before* the + * underlying windows are unmapped. Otherwise, focus will revert to the + * PointerRoot and will then be set to the new window, generating unnecessary + * FocusIn/FocusOut events. + * + */ +static void x_push_node_unmaps(Con *con) { + Con *current; + con_state *state; + + LOG("Pushing changes (with unmaps) for node %p / %s\n", con, con->name); + state = state_for_frame(con->frame); + + /* map/unmap if map state changed, also ensure that the child window + * is changed if we are mapped *and* in initial state (meaning the + * container was empty before, but now got a child) */ + if ((state->mapped != con->mapped || (con->mapped && state->initial)) && + !con->mapped) { + xcb_void_cookie_t cookie; + if (con->window != NULL) { + /* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */ + long data[] = { XCB_WM_STATE_WITHDRAWN, XCB_NONE }; + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, + atoms[WM_STATE], atoms[WM_STATE], 32, 2, data); + } + + cookie = xcb_unmap_window(conn, con->frame); + LOG("unmapping container (serial %d)\n", cookie.sequence); + /* we need to increase ignore_unmap for this container (if it + * contains a window) and for every window "under" this one which + * contains a window */ + if (con->window != NULL) { + con->ignore_unmap++; + DLOG("ignore_unmap for con %p (frame 0x%08x) now %d\n", con, con->frame, con->ignore_unmap); + } + /* Ignore enter_notifies which are generated when unmapping */ + add_ignore_event(cookie.sequence); + state->mapped = con->mapped; + } + + /* handle all children and floating windows of this node */ + TAILQ_FOREACH(current, &(con->nodes_head), nodes) + x_push_node_unmaps(current); + + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) + x_push_node_unmaps(current); +} + /* * Pushes all changes (state of each node, see x_push_node() and the window * stack) to X11. @@ -557,11 +590,14 @@ void x_push_changes(Con *con) { if (focused_id != to_focus) { LOG("Updating focus (focused: %p / %s)\n", focused, focused->name); xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME); + focused_id = to_focus; } xcb_flush(conn); LOG("\n\n ENDING CHANGES\n\n"); + x_push_node_unmaps(con); + /* save the current stack as old stack */ CIRCLEQ_FOREACH(state, &state_head, state) { CIRCLEQ_REMOVE(&old_state_head, state, old_state); From 0cfebcb5b61d0812bd52a69c92271375a37216d3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Nov 2010 18:05:45 +0100 Subject: [PATCH 283/867] remove some debug messages --- src/con.c | 3 --- src/render.c | 1 - src/tree.c | 4 ---- src/x.c | 5 ----- 4 files changed, 13 deletions(-) diff --git a/src/con.c b/src/con.c index 4824510f..8d24c0df 100644 --- a/src/con.c +++ b/src/con.c @@ -232,7 +232,6 @@ struct bfs_entry { Con *con_get_fullscreen_con(Con *con) { Con *current, *child; - LOG("looking for fullscreen node\n"); /* TODO: is breadth-first-search really appropriate? (check as soon as * fullscreen levels and fullscreen for containers is implemented) */ TAILQ_HEAD(bfs_head, bfs_entry) bfs_head = TAILQ_HEAD_INITIALIZER(bfs_head); @@ -243,7 +242,6 @@ Con *con_get_fullscreen_con(Con *con) { while (!TAILQ_EMPTY(&bfs_head)) { entry = TAILQ_FIRST(&bfs_head); current = entry->con; - LOG("checking %p\n", current); if (current != con && current->fullscreen_mode != CF_NONE) { /* empty the queue */ while (!TAILQ_EMPTY(&bfs_head)) { @@ -254,7 +252,6 @@ Con *con_get_fullscreen_con(Con *con) { return current; } - LOG("deleting from queue\n"); TAILQ_REMOVE(&bfs_head, entry, entries); free(entry); diff --git a/src/render.c b/src/render.c index b1b1bd89..7551e0fa 100644 --- a/src/render.c +++ b/src/render.c @@ -40,7 +40,6 @@ void render_con(Con *con, bool render_fullscreen) { int i = 0; - printf("mapped = true\n"); con->mapped = true; /* if this container contains a window, set the coordinates */ diff --git a/src/tree.c b/src/tree.c index 290f1fc2..e37eb5f9 100644 --- a/src/tree.c +++ b/src/tree.c @@ -298,21 +298,17 @@ void level_down() { static void mark_unmapped(Con *con) { Con *current; - DLOG("marking container %p unmapped\n", con); con->mapped = false; TAILQ_FOREACH(current, &(con->nodes_head), nodes) mark_unmapped(current); if (con->type == CT_WORKSPACE) { TAILQ_FOREACH(current, &(con->floating_head), floating_windows) { - DLOG("Marking unmapped for floating %p\n", current); current->mapped = false; Con *child = TAILQ_FIRST(&(current->nodes_head)); - DLOG(" unmapping floating child %p\n", child); child->mapped = false; } } - DLOG("mark_unmapped done\n"); } /* diff --git a/src/x.c b/src/x.c index 84659100..2deb37c1 100644 --- a/src/x.c +++ b/src/x.c @@ -391,16 +391,12 @@ static void x_push_node(Con *con) { * this frame. */ uint32_t max_y = 0, max_height = 0; TAILQ_FOREACH(current, &(con->nodes_head), nodes) { - DLOG("Child's decoration is %d x %d, from (%d, %d)\n", - current->deco_rect.width, current->deco_rect.height, - current->deco_rect.x, current->deco_rect.y); Rect *dr = &(current->deco_rect); if (dr->y >= max_y && dr->height >= max_height) { max_y = dr->y; max_height = dr->height; } } - DLOG("bottom of decorations is %d\n", max_y + max_height); rect.height = max_y + max_height; if (rect.height == 0) { DLOG("Unmapping container because it does not contain anything atm.\n"); @@ -618,7 +614,6 @@ void x_raise_con(Con *con) { LOG("raising in new stack: %p / %s\n", con, con->name); state = state_for_frame(con->frame); - LOG("found state entry, moving to top\n"); CIRCLEQ_REMOVE(&state_head, state, state); CIRCLEQ_INSERT_HEAD(&state_head, state, state); } From 6fe0e58a6421bfcdea797fc3aed208cb4f0bc9da Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 00:52:24 +0100 Subject: [PATCH 284/867] Bugfix: Also render decorations of nearby cons when getting an ExposeEvent (Thanks fernandotcl) --- src/handlers.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/handlers.c b/src/handlers.c index 36b2a2fd..03fd802c 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -617,6 +617,14 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * if (con->window) x_draw_decoration(con); } + + /* We also need to render the decorations of other Cons nearby the Con + * itself to not get overlapping decorations */ + TAILQ_FOREACH(con, &(parent->parent->nodes_head), nodes) { + LOG("expose for con %p / %s\n", con, con->name); + if (con->window) + x_draw_decoration(con); + } xcb_flush(conn); return 1; From 622b51a1ea592d76c9399f4d6e2af16adfaf90e1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 01:13:18 +0100 Subject: [PATCH 285/867] Fix switching containers by moving the mouse over their decorations when in the same container --- include/handlers.h | 2 +- include/xcb.h | 1 + src/handlers.c | 36 ++++++++++++++++++++++++++++-------- src/main.c | 2 ++ 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/include/handlers.h b/include/handlers.h index 71e2bc92..1f2e4b18 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -32,7 +32,6 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_event_t *event); -#if 0 /** * When the user moves the mouse but does not change the active window * (e.g. when having no windows opened but moving mouse on the root screen @@ -42,6 +41,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notify_event_t *event); +#if 0 /** * Called when the keyboard mapping changes (for example by using Xmodmap), * we need to update our key bindings then (re-translate symbols). diff --git a/include/xcb.h b/include/xcb.h index 3f4b735c..3752e9f4 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -37,6 +37,7 @@ /** The XCB_CW_EVENT_MASK for its frame */ #define FRAME_EVENT_MASK (XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed/released */ \ XCB_EVENT_MASK_BUTTON_RELEASE | \ + XCB_EVENT_MASK_POINTER_MOTION | /* …mouse is moved */ \ XCB_EVENT_MASK_EXPOSURE | /* …our window needs to be redrawn */ \ XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* …the frame gets destroyed */ \ XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | /* …the application tries to resize itself */ \ diff --git a/src/handlers.c b/src/handlers.c index 03fd802c..0fc6b8a5 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -243,7 +243,6 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, return 1; } -#if 0 /* * When the user moves the mouse but does not change the active window @@ -252,16 +251,37 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, * */ int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notify_event_t *event) { - /* Skip events where the pointer was over a child window, we are only - * interested in events on the root window. */ - if (event->child != 0) - return 1; - - check_crossing_screen_boundary(event->root_x, event->root_y); - + /* Skip events where the pointer was over a child window, we are only + * interested in events on the root window. */ + if (event->child != 0) return 1; + + Con *con; + if ((con = con_by_frame_id(event->event)) == NULL) { + /* TODO; handle root window: */ + //check_crossing_screen_boundary(event->root_x, event->root_y); + return 1; + } + + /* see over which rect the user is */ + Con *current; + TAILQ_FOREACH(current, &(con->nodes_head), nodes) { + if (!rect_contains(current->deco_rect, event->event_x, event->event_y)) + continue; + + /* We found the rect, let’s see if this window is focused */ + if (TAILQ_FIRST(&(con->focus_head)) == current) + return 1; + + con_focus(current); + x_push_changes(croot); + return 1; + } + + return 1; } +#if 0 /* * Called when the keyboard mapping changes (for example by using Xmodmap), * we need to update our key bindings then (re-translate symbols). diff --git a/src/main.c b/src/main.c index f0118f98..2d52ef8a 100644 --- a/src/main.c +++ b/src/main.c @@ -211,6 +211,8 @@ int main(int argc, char *argv[]) { xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL); + xcb_event_set_motion_notify_handler(&evenths, handle_motion_notify, NULL); + /* Enter window = user moved his mouse over the window */ xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL); From 0a17fe973c2cf655e301217baf618af73c20229a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Fri, 26 Nov 2010 22:27:38 -0200 Subject: [PATCH 286/867] Make the restart path configurable. --- include/config.h | 1 + src/cfgparse.l | 1 + src/cfgparse.y | 9 +++++++++ src/config.c | 2 ++ src/tree.c | 8 ++++++-- src/util.c | 2 +- 6 files changed, 20 insertions(+), 3 deletions(-) diff --git a/include/config.h b/include/config.h index c9e8e1a4..abbee5e7 100644 --- a/include/config.h +++ b/include/config.h @@ -89,6 +89,7 @@ struct Config { const char *font; const char *ipc_socket_path; + const char *restart_state_path; int container_mode; int container_stack_limit; diff --git a/src/cfgparse.l b/src/cfgparse.l index eb9c25c7..93ce916c 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -91,6 +91,7 @@ assign { BEGIN(ASSIGN_COND); return TOKASSIGN; } set[^\n]* { return TOKCOMMENT; } ipc-socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; } ipc_socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; } +restart_state { BEGIN(BIND_AWS_COND); return TOKRESTARTSTATE; } new_container { return TOKNEWCONTAINER; } new_window { return TOKNEWWINDOW; } focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; } diff --git a/src/cfgparse.y b/src/cfgparse.y index c2227f67..eaed7c5d 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -217,6 +217,7 @@ void parse_file(const char *f) { %token TOKASSIGN "assign" %token TOKSET %token TOKIPCSOCKET "ipc_socket" +%token TOKRESTARTSTATE "restart_state" %token TOKEXEC "exec" %token TOKSINGLECOLOR %token TOKCOLOR @@ -248,6 +249,7 @@ line: | workspace | assign | ipcsocket + | restart_state | exec | single_color | color @@ -554,6 +556,13 @@ ipcsocket: } ; +restart_state: + TOKRESTARTSTATE WHITESPACE STR + { + config.restart_state_path = $3; + } + ; + exec: TOKEXEC WHITESPACE STR { diff --git a/src/config.c b/src/config.c index 93390b4a..c02d1c2a 100644 --- a/src/config.c +++ b/src/config.c @@ -364,6 +364,8 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888"); INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff"); + config.restart_state_path = "~/.i3/_restart.json"; + parse_configuration(override_configpath); if (reload) { diff --git a/src/tree.c b/src/tree.c index e37eb5f9..947a8daa 100644 --- a/src/tree.c +++ b/src/tree.c @@ -14,7 +14,7 @@ struct all_cons_head all_cons = TAILQ_HEAD_INITIALIZER(all_cons); * */ bool tree_restore() { - char *globbed = resolve_tilde("~/.i3/_restart.json"); + char *globbed = resolve_tilde(config.restart_state_path); if (!path_exists(globbed)) { LOG("%s does not exist, not restoring tree\n", globbed); @@ -27,7 +27,11 @@ bool tree_restore() { focused = croot; tree_append_json(globbed); - char *old_restart = resolve_tilde("~/.i3/_restart.json.old"); + + size_t path_len = strlen(config.restart_state_path); + char *old_restart = malloc(path_len + 5); + strncpy(old_restart, config.restart_state_path, path_len + 5); + strncat(old_restart, ".old", path_len + 5); unlink(old_restart); rename(globbed, old_restart); free(globbed); diff --git a/src/util.c b/src/util.c index 04ec1508..75203cfb 100644 --- a/src/util.c +++ b/src/util.c @@ -375,7 +375,7 @@ void store_restart_layout() { unsigned int length; y(get_buf, &payload, &length); - char *globbed = resolve_tilde("~/.i3/_restart.json"); + char *globbed = resolve_tilde(config.restart_state_path); int fd = open(globbed, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); free(globbed); if (fd == -1) { From 2c157283ea7b38e6d67d65ca28472c5a6e3e25af Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 01:22:10 +0100 Subject: [PATCH 287/867] fix third argument to strncat(), use smalloc(), use strlen(".old")+1 --- src/tree.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tree.c b/src/tree.c index 947a8daa..75f8c7cb 100644 --- a/src/tree.c +++ b/src/tree.c @@ -29,9 +29,9 @@ bool tree_restore() { tree_append_json(globbed); size_t path_len = strlen(config.restart_state_path); - char *old_restart = malloc(path_len + 5); - strncpy(old_restart, config.restart_state_path, path_len + 5); - strncat(old_restart, ".old", path_len + 5); + char *old_restart = smalloc(path_len + strlen(".old") + 1); + strncpy(old_restart, config.restart_state_path, path_len + strlen(".old") + 1); + strncat(old_restart, ".old", strlen(".old") + 1); unlink(old_restart); rename(globbed, old_restart); free(globbed); From d0de3f403d257ffa9cf7f0a02d2a6106cbf6e12f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 01:39:47 +0100 Subject: [PATCH 288/867] Bugfix: Restore focus after changing layout (Thanks fernandotcl) --- src/con.c | 7 ++++++ testcases/t/40-focus-lost.t | 46 +++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 testcases/t/40-focus-lost.t diff --git a/src/con.c b/src/con.c index 8d24c0df..e86f668c 100644 --- a/src/con.c +++ b/src/con.c @@ -567,6 +567,10 @@ void con_set_layout(Con *con, int layout) { * container. */ new->orientation = HORIZ; + Con *old_focused = TAILQ_FIRST(&(con->focus_head)); + if (old_focused == TAILQ_END(&(con->focus_head))) + old_focused = NULL; + /* 4: move the existing cons of this workspace below the new con */ DLOG("Moving cons\n"); Con *child; @@ -580,6 +584,9 @@ void con_set_layout(Con *con, int layout) { DLOG("Attaching new split to ws\n"); con_attach(new, con); + if (old_focused) + con_focus(old_focused); + return; } diff --git a/testcases/t/40-focus-lost.t b/testcases/t/40-focus-lost.t new file mode 100644 index 00000000..9b2db079 --- /dev/null +++ b/testcases/t/40-focus-lost.t @@ -0,0 +1,46 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# Regression: Check if the focus stays the same when switching the layout +# bug introduced by 77d0d42ed2d7ac8cafe267c92b35a81c1b9491eb +use i3test tests => 4; +use X11::XCB qw(:all); +use Time::HiRes qw(sleep); + +BEGIN { + use_ok('X11::XCB::Window'); +} + +my $i3 = i3("/tmp/nestedcons"); +my $x = X11::XCB::Connection->new; + +sub check_order { + my ($msg) = @_; + + my @ws = @{$i3->get_workspaces->recv}; + my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws; + my @sorted = sort @nums; + + cmp_deeply(\@nums, \@sorted, $msg); +} + +my $tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +my $left = open_standard_window($x); +sleep 0.25; +my $mid = open_standard_window($x); +sleep 0.25; +my $right = open_standard_window($x); +sleep 0.25; + +diag("left = " . $left->id . ", mid = " . $mid->id . ", right = " . $right->id); + +is($x->input_focus, $right->id, 'Right window focused'); + +$i3->command('prev h')->recv; + +is($x->input_focus, $mid->id, 'Mid window focused'); + +$i3->command('layout stacked')->recv; + +is($x->input_focus, $mid->id, 'Mid window focused'); From 3bab222aa7657036ae64e0a1da6cd626bcaceb3d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 01:51:16 +0100 Subject: [PATCH 289/867] Bugfix: Re-attach windows in correct order when switching layout (Thanks fernandotcl) --- include/con.h | 6 +++++- src/con.c | 28 +++++++++++++++++----------- src/tree.c | 4 ++-- src/workspace.c | 2 +- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/include/con.h b/include/con.h index 9194b0e0..736b526c 100644 --- a/include/con.h +++ b/include/con.h @@ -86,8 +86,12 @@ int con_num_children(Con *con); * a container or when inserting a new container at a specific place in the * tree. * + * ignore_focus is to just insert the Con at the end (useful when creating a + * new split container *around* some containers, that is, detaching and + * attaching them in order without wanting to mess with the focus in between). + * */ -void con_attach(Con *con, Con *parent); +void con_attach(Con *con, Con *parent, bool ignore_focus); /** * Detaches the given container from its current parent diff --git a/src/con.c b/src/con.c index e86f668c..80f69a6e 100644 --- a/src/con.c +++ b/src/con.c @@ -57,7 +57,7 @@ Con *con_new(Con *parent) { TAILQ_INIT(&(new->swallow_head)); if (parent != NULL) - con_attach(new, parent); + con_attach(new, parent, false); return new; } @@ -67,8 +67,12 @@ Con *con_new(Con *parent) { * a container or when inserting a new container at a specific place in the * tree. * + * ignore_focus is to just insert the Con at the end (useful when creating a + * new split container *around* some containers, that is, detaching and + * attaching them in order without wanting to mess with the focus in between). + * */ -void con_attach(Con *con, Con *parent) { +void con_attach(Con *con, Con *parent, bool ignore_focus) { con->parent = parent; Con *loop; Con *current = NULL; @@ -102,12 +106,14 @@ void con_attach(Con *con, Con *parent) { goto add_to_focus_head; } - /* Get the first tiling container in focus stack */ - TAILQ_FOREACH(loop, &(parent->focus_head), focused) { - if (loop->type == CT_FLOATING_CON) - continue; - current = loop; - break; + if (!ignore_focus) { + /* Get the first tiling container in focus stack */ + TAILQ_FOREACH(loop, &(parent->focus_head), focused) { + if (loop->type == CT_FLOATING_CON) + continue; + current = loop; + break; + } } /* Insert the container after the tiling container, if found */ @@ -428,7 +434,7 @@ void con_move_to_workspace(Con *con, Con *workspace) { DLOG("Re-attaching container to %p / %s\n", next, next->name); /* 4: re-attach the con to the parent of this focused container */ con_detach(con); - con_attach(con, next); + con_attach(con, next, false); /* 5: keep focus on the current workspace */ con_focus(focus_next); @@ -577,12 +583,12 @@ void con_set_layout(Con *con, int layout) { while (!TAILQ_EMPTY(&(con->nodes_head))) { child = TAILQ_FIRST(&(con->nodes_head)); con_detach(child); - con_attach(child, new); + con_attach(child, new, true); } /* 4: attach the new split container to the workspace */ DLOG("Attaching new split to ws\n"); - con_attach(new, con); + con_attach(new, con, false); if (old_focused) con_focus(old_focused); diff --git a/src/tree.c b/src/tree.c index 75f8c7cb..15799254 100644 --- a/src/tree.c +++ b/src/tree.c @@ -86,7 +86,7 @@ void tree_init() { ws->num = c; asprintf(&(ws->name), "%d", c); c++; - con_attach(ws, oc); + con_attach(ws, oc, false); asprintf(&name, "[i3 con] workspace %s", ws->name); x_set_name(ws, name); @@ -269,7 +269,7 @@ void tree_split(Con *con, orientation_t orientation) { new->orientation = orientation; /* 3: add it as a child to the new Con */ - con_attach(con, new); + con_attach(con, new, false); } /* diff --git a/src/workspace.c b/src/workspace.c index 837ac1fe..a1d3659d 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -59,7 +59,7 @@ Con *workspace_get(const char *num) { else workspace->num = parsed_num; LOG("num = %d\n", workspace->num); workspace->orientation = HORIZ; - con_attach(workspace, output); + con_attach(workspace, output, false); ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); } From 69fc6449dc96045e6fecd31b211b928017fc4f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Fri, 26 Nov 2010 21:26:51 -0200 Subject: [PATCH 290/867] libXcursor support (themed cursors). --- DEPENDS | 1 + Makefile | 2 +- common.mk | 1 + debian/control | 2 +- include/all.h | 1 + include/i3.h | 4 ++-- include/xcb.h | 3 ++- include/xcursor.h | 17 +++++++++++++++ src/main.c | 24 +++++++++++++++++++++ src/x.c | 2 +- src/xcb.c | 55 ++++++++++++++++++++++++++--------------------- src/xcursor.c | 42 ++++++++++++++++++++++++++++++++++++ 12 files changed, 123 insertions(+), 31 deletions(-) create mode 100644 include/xcursor.h create mode 100644 src/xcursor.c diff --git a/DEPENDS b/DEPENDS index b7a6fefb..5ca6d8c4 100644 --- a/DEPENDS +++ b/DEPENDS @@ -11,6 +11,7 @@ mentioned below until a fix is provided. * yajl (the IPC interface uses JSON to serialize data) * asciidoc >= 8.3.0 for docs/hacking-howto * asciidoc, xmlto, docbook-xml for man/i3.man + * libxcursor * Xlib, the one that comes with your X-Server * x11-utils for xmessage (only for displaying the welcome message, so this is mainly interesting for distributors) diff --git a/Makefile b/Makefile index 8aaf20cf..97e0dd80 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c -FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c +FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) diff --git a/common.mk b/common.mk index 49298049..9ac6dcd2 100644 --- a/common.mk +++ b/common.mk @@ -33,6 +33,7 @@ LDFLAGS += -lxcb-xinerama LDFLAGS += -lxcb-randr LDFLAGS += -lxcb LDFLAGS += -lyajl +LDFLAGS += -lXcursor LDFLAGS += -lX11 LDFLAGS += -lev LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib diff --git a/debian/control b/debian/control index 8dde5b80..39446178 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: utils Priority: extra Maintainer: Michael Stapelberg DM-Upload-Allowed: yes -Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev +Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev Standards-Version: 3.8.3 Homepage: http://i3.zekjur.net/ diff --git a/include/all.h b/include/all.h index 2096016d..1625f16c 100644 --- a/include/all.h +++ b/include/all.h @@ -51,5 +51,6 @@ #include "window.h" #include "match.h" #include "cmdparse.h" +#include "xcursor.h" #endif diff --git a/include/i3.h b/include/i3.h index e437943e..ed8cacb6 100644 --- a/include/i3.h +++ b/include/i3.h @@ -26,7 +26,7 @@ extern xcb_connection_t *conn; extern xcb_key_symbols_t *keysyms; extern char **start_argv; -extern Display *xkbdpy; +extern Display *xlibdpy, *xkbdpy; extern int xkb_current_group; extern TAILQ_HEAD(bindings_head, Binding) *bindings; extern TAILQ_HEAD(autostarts_head, Autostart) autostarts; @@ -35,7 +35,7 @@ extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; extern xcb_event_handlers_t evenths; extern xcb_property_handlers_t prophs; extern uint8_t root_depth; -extern bool xkb_supported; +extern bool xcursor_supported, xkb_supported; extern xcb_atom_t atoms[NUM_ATOMS]; extern xcb_window_t root; diff --git a/include/xcb.h b/include/xcb.h index 3752e9f4..4b01d900 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -12,6 +12,7 @@ #define _XCB_H #include "data.h" +#include "xcursor.h" #define _NET_WM_STATE_REMOVE 0 #define _NET_WM_STATE_ADD 1 @@ -94,7 +95,7 @@ uint32_t get_colorpixel(char *hex); * */ xcb_window_t create_window(xcb_connection_t *conn, Rect r, uint16_t window_class, - int cursor, bool map, uint32_t mask, uint32_t *values); + enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values); /** * Changes a single value in the graphic context (so one doesn’t have to diff --git a/include/xcursor.h b/include/xcursor.h new file mode 100644 index 00000000..4b5326d5 --- /dev/null +++ b/include/xcursor.h @@ -0,0 +1,17 @@ +#ifndef _XCURSOR_CURSOR_H +#define _XCURSOR_CURSOR_H + +#include + +enum xcursor_cursor_t { + XCURSOR_CURSOR_POINTER = 0, + XCURSOR_CURSOR_RESIZE_HORIZONTAL, + XCURSOR_CURSOR_RESIZE_VERTICAL, + XCURSOR_CURSOR_MAX +}; + +extern void xcursor_load_cursors(); +extern Cursor xcursor_get_cursor(enum xcursor_cursor_t c); +extern int xcursor_get_xcb_cursor(enum xcursor_cursor_t c); + +#endif diff --git a/src/main.c b/src/main.c index 2d52ef8a..121100b5 100644 --- a/src/main.c +++ b/src/main.c @@ -2,6 +2,7 @@ * vim:ts=4:sw=4:expandtab */ #include +#include #include #include "all.h" @@ -23,6 +24,9 @@ uint8_t root_depth; xcb_key_symbols_t *keysyms; +/* Those are our connections to X11 for use with libXcursor and XKB */ +Display *xlibdpy, *xkbdpy; + /* The list of key bindings */ struct bindings_head *bindings; @@ -32,6 +36,10 @@ struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts); /* The list of assignments */ struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments); +/* We hope that those are supported and set them to true */ +bool xcursor_supported = true; +bool xkb_supported = true; + /* * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb. * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop @@ -195,6 +203,22 @@ int main(int argc, char *argv[]) { REQUEST_ATOM(_NET_ACTIVE_WINDOW); REQUEST_ATOM(_NET_WORKAREA); + /* Initialize the Xlib connection */ + xlibdpy = xkbdpy = XOpenDisplay(NULL); + + /* Try to load the X cursors and initialize the XKB extension */ + if (xlibdpy == NULL) { + ELOG("ERROR: XOpenDisplay() failed, disabling libXcursor/XKB support\n"); + xcursor_supported = false; + xkb_supported = false; + } else if (fcntl(ConnectionNumber(xlibdpy), F_SETFD, FD_CLOEXEC) == -1) { + ELOG("Could not set FD_CLOEXEC on xkbdpy\n"); + return 1; + } else { + xcursor_load_cursors(); + /*init_xkb();*/ + } + memset(&evenths, 0, sizeof(xcb_event_handlers_t)); memset(&prophs, 0, sizeof(xcb_property_handlers_t)); diff --git a/src/x.c b/src/x.c index 2deb37c1..ca292b2c 100644 --- a/src/x.c +++ b/src/x.c @@ -82,7 +82,7 @@ void x_con_init(Con *con) { values[1] = FRAME_EVENT_MASK; Rect dims = { -15, -15, 10, 10 }; - con->frame = create_window(conn, dims, XCB_WINDOW_CLASS_INPUT_OUTPUT, -1, false, mask, values); + con->frame = create_window(conn, dims, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER, false, mask, values); con->gc = xcb_generate_id(conn); xcb_create_gc(conn, con->gc, con->frame, 0, 0); diff --git a/src/xcb.c b/src/xcb.c index 78b7a3b1..765c1894 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -80,40 +80,45 @@ uint32_t get_colorpixel(char *hex) { * for errors. * */ -xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_class, int cursor, - bool map, uint32_t mask, uint32_t *values) { - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; - xcb_window_t result = xcb_generate_id(conn); - xcb_cursor_t cursor_id = xcb_generate_id(conn); +xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_class, + enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values) { + xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; + xcb_window_t result = xcb_generate_id(conn); + xcb_cursor_t cursor_id = xcb_generate_id(conn); - /* If the window class is XCB_WINDOW_CLASS_INPUT_ONLY, depth has to be 0 */ - uint16_t depth = (window_class == XCB_WINDOW_CLASS_INPUT_ONLY ? 0 : XCB_COPY_FROM_PARENT); + /* If the window class is XCB_WINDOW_CLASS_INPUT_ONLY, depth has to be 0 */ + uint16_t depth = (window_class == XCB_WINDOW_CLASS_INPUT_ONLY ? 0 : XCB_COPY_FROM_PARENT); - xcb_create_window(conn, - depth, - result, /* the window id */ - root, /* parent == root */ - dims.x, dims.y, dims.width, dims.height, /* dimensions */ - 0, /* border = 0, we draw our own */ - window_class, - XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ - mask, - values); + xcb_create_window(conn, + depth, + result, /* the window id */ + root, /* parent == root */ + dims.x, dims.y, dims.width, dims.height, /* dimensions */ + 0, /* border = 0, we draw our own */ + window_class, + XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ + mask, + values); - /* Set the cursor */ + /* Set the cursor */ + if (xcursor_supported) { + mask = XCB_CW_CURSOR; + values[0] = xcursor_get_cursor(cursor); + xcb_change_window_attributes(conn, result, mask, values); + } else { i3Font *cursor_font = load_font(conn, "cursor"); + int xcb_cursor = xcursor_get_xcb_cursor(cursor); xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id, - (cursor == -1 ? XCB_CURSOR_LEFT_PTR : cursor), - (cursor == -1 ? XCB_CURSOR_LEFT_PTR : cursor) + 1, - 0, 0, 0, 65535, 65535, 65535); + xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535); xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id); xcb_free_cursor(conn, cursor_id); + } - /* Map the window (= make it visible) */ - if (map) - xcb_map_window(conn, result); + /* Map the window (= make it visible) */ + if (map) + xcb_map_window(conn, result); - return result; + return result; } /* diff --git a/src/xcursor.c b/src/xcursor.c new file mode 100644 index 00000000..2e21aab1 --- /dev/null +++ b/src/xcursor.c @@ -0,0 +1,42 @@ +#include +#include +#include + +#include "i3.h" +#include "xcb.h" +#include "xcursor.h" + +static Cursor cursors[XCURSOR_CURSOR_MAX]; + +static const int xcb_cursors[XCURSOR_CURSOR_MAX] = { + XCB_CURSOR_LEFT_PTR, + XCB_CURSOR_SB_H_DOUBLE_ARROW, + XCB_CURSOR_SB_V_DOUBLE_ARROW +}; + +static Cursor load_cursor(const char *name, int font) +{ + Cursor c = XcursorLibraryLoadCursor(xlibdpy, name); + if (c == None) + c = XCreateFontCursor(xlibdpy, font); + return c; +} + +void xcursor_load_cursors() +{ + cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr", XC_left_ptr); + cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow", XC_sb_h_double_arrow); + cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow", XC_sb_v_double_arrow); +} + +Cursor xcursor_get_cursor(enum xcursor_cursor_t c) +{ + assert(c >= 0 && c < XCURSOR_CURSOR_MAX); + return cursors[c]; +} + +int xcursor_get_xcb_cursor(enum xcursor_cursor_t c) +{ + assert(c >= 0 && c < XCURSOR_CURSOR_MAX); + return xcb_cursors[c]; +} From d60e8c56dc744ad2d963a667961620dca88ea5b4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 13:02:29 +0100 Subject: [PATCH 291/867] little style fixes, add vim modeline --- include/xcursor.h | 3 +++ src/xcursor.c | 15 +++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/include/xcursor.h b/include/xcursor.h index 4b5326d5..1872fa0e 100644 --- a/include/xcursor.h +++ b/include/xcursor.h @@ -1,3 +1,6 @@ +/* + * vim:ts=4:sw=4:expandtab + */ #ifndef _XCURSOR_CURSOR_H #define _XCURSOR_CURSOR_H diff --git a/src/xcursor.c b/src/xcursor.c index 2e21aab1..cd80aa68 100644 --- a/src/xcursor.c +++ b/src/xcursor.c @@ -1,3 +1,6 @@ +/* + * vim:ts=4:sw=4:expandtab + */ #include #include #include @@ -14,29 +17,25 @@ static const int xcb_cursors[XCURSOR_CURSOR_MAX] = { XCB_CURSOR_SB_V_DOUBLE_ARROW }; -static Cursor load_cursor(const char *name, int font) -{ +static Cursor load_cursor(const char *name, int font) { Cursor c = XcursorLibraryLoadCursor(xlibdpy, name); if (c == None) c = XCreateFontCursor(xlibdpy, font); return c; } -void xcursor_load_cursors() -{ +void xcursor_load_cursors() { cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr", XC_left_ptr); cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow", XC_sb_h_double_arrow); cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow", XC_sb_v_double_arrow); } -Cursor xcursor_get_cursor(enum xcursor_cursor_t c) -{ +Cursor xcursor_get_cursor(enum xcursor_cursor_t c) { assert(c >= 0 && c < XCURSOR_CURSOR_MAX); return cursors[c]; } -int xcursor_get_xcb_cursor(enum xcursor_cursor_t c) -{ +int xcursor_get_xcb_cursor(enum xcursor_cursor_t c) { assert(c >= 0 && c < XCURSOR_CURSOR_MAX); return xcb_cursors[c]; } From fd79a3dd5b5e3a541f100b0fb5761ae9e9fac1fc Mon Sep 17 00:00:00 2001 From: Blekos EelVex Kostas Date: Sat, 27 Nov 2010 23:37:29 +0200 Subject: [PATCH 292/867] remove space for flex's -P flag --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 97e0dd80..66f92713 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS} src/cmdparse.yy.o: src/cmdparse.l src/cmdparse.y.o ${HEADERS} echo "LEX $<" - flex -P cmdyy -i -o$(@:.o=.c) $< + flex -Pcmdyy -i -o$(@:.o=.c) $< $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) From 02b786509b1871fb978c7d535a2f7d4197452672 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 13:19:49 +0100 Subject: [PATCH 293/867] Upon ConfigureRequests, send a generated ConfigureNotify (Thanks fernandotcl) This fixes problems with GVim in stacking mode for example. --- src/handlers.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/handlers.c b/src/handlers.c index 0fc6b8a5..1f86de0e 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -394,6 +394,8 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure tree_render(); } + fake_absolute_configure_notify(con); + return 1; #if 0 /* Dock clients can be reconfigured in their height */ From ab8400fff9f15da1707c1c57d575713af1427e9e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 14:03:43 +0100 Subject: [PATCH 294/867] Bugfix: Use setsid() to avoid SIGINT for child processes --- src/util.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util.c b/src/util.c index 75203cfb..673bdb26 100644 --- a/src/util.c +++ b/src/util.c @@ -99,6 +99,7 @@ void start_application(const char *command) { LOG("executing: %s\n", command); if (fork() == 0) { /* Child process */ + setsid(); if (fork() == 0) { /* Stores the path of the shell */ static const char *shell = NULL; From 32cc7134aa35af36054174330c2fd88eb4d6d7c4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 14:11:44 +0100 Subject: [PATCH 295/867] re-enable ipc_shutdown() when restarting --- src/util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.c b/src/util.c index 673bdb26..076d445c 100644 --- a/src/util.c +++ b/src/util.c @@ -415,7 +415,7 @@ void i3_restart() { store_restart_layout(); restore_geometry(); - //ipc_shutdown(); + ipc_shutdown(); LOG("restarting \"%s\"...\n", start_argv[0]); /* make sure -a is in the argument list or append it */ From f7fff5cec1d061a3a3bec17f8ddf9df59814419d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 14:14:34 +0100 Subject: [PATCH 296/867] update indenting of src/util.c --- src/util.c | 246 ++++++++++++++++++++++++++--------------------------- 1 file changed, 123 insertions(+), 123 deletions(-) diff --git a/src/util.c b/src/util.c index 076d445c..cd58bce3 100644 --- a/src/util.c +++ b/src/util.c @@ -1,9 +1,9 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -24,25 +24,25 @@ static iconv_t conversion_descriptor = 0; int min(int a, int b) { - return (a < b ? a : b); + return (a < b ? a : b); } int max(int a, int b) { - return (a > b ? a : b); + return (a > b ? a : b); } bool rect_contains(Rect rect, uint32_t x, uint32_t y) { - return (x >= rect.x && - x <= (rect.x + rect.width) && - y >= rect.y && - y <= (rect.y + rect.height)); + return (x >= rect.x && + x <= (rect.x + rect.width) && + y >= rect.y && + y <= (rect.y + rect.height)); } Rect rect_add(Rect a, Rect b) { - return (Rect){a.x + b.x, - a.y + b.y, - a.width + b.width, - a.height + b.height}; + return (Rect){a.x + b.x, + a.y + b.y, + a.width + b.width, + a.height + b.height}; } /* @@ -51,9 +51,9 @@ Rect rect_add(Rect a, Rect b) { * */ bool update_if_necessary(uint32_t *destination, const uint32_t new_value) { - uint32_t old_value = *destination; + uint32_t old_value = *destination; - return ((*destination = new_value) != old_value); + return ((*destination = new_value) != old_value); } /* @@ -62,27 +62,27 @@ bool update_if_necessary(uint32_t *destination, const uint32_t new_value) { * */ void *smalloc(size_t size) { - void *result = malloc(size); - exit_if_null(result, "Error: out of memory (malloc(%zd))\n", size); - return result; + void *result = malloc(size); + exit_if_null(result, "Error: out of memory (malloc(%zd))\n", size); + return result; } void *scalloc(size_t size) { - void *result = calloc(size, 1); - exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size); - return result; + void *result = calloc(size, 1); + exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size); + return result; } void *srealloc(void *ptr, size_t size) { - void *result = realloc(ptr, size); - exit_if_null(result, "Error: out memory (realloc(%zd))\n", size); - return result; + void *result = realloc(ptr, size); + exit_if_null(result, "Error: out memory (realloc(%zd))\n", size); + return result; } char *sstrdup(const char *str) { - char *result = strdup(str); - exit_if_null(result, "Error: out of memory (strdup())\n"); - return result; + char *result = strdup(str); + exit_if_null(result, "Error: out of memory (strdup())\n"); + return result; } /* @@ -96,25 +96,25 @@ char *sstrdup(const char *str) { * */ void start_application(const char *command) { - LOG("executing: %s\n", command); + LOG("executing: %s\n", command); + if (fork() == 0) { + /* Child process */ + setsid(); if (fork() == 0) { - /* Child process */ - setsid(); - if (fork() == 0) { - /* Stores the path of the shell */ - static const char *shell = NULL; + /* Stores the path of the shell */ + static const char *shell = NULL; - if (shell == NULL) - if ((shell = getenv("SHELL")) == NULL) - shell = "/bin/sh"; + if (shell == NULL) + if ((shell = getenv("SHELL")) == NULL) + shell = "/bin/sh"; - /* This is the child */ - execl(shell, shell, "-c", command, (void*)NULL); - /* not reached */ - } - exit(0); + /* This is the child */ + execl(shell, shell, "-c", command, (void*)NULL); + /* not reached */ } - wait(0); + exit(0); + } + wait(0); } /* @@ -123,12 +123,12 @@ void start_application(const char *command) { * */ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_message) { - xcb_generic_error_t *error = xcb_request_check(conn, cookie); - if (error != NULL) { - fprintf(stderr, "ERROR: %s (X error %d)\n", err_message , error->error_code); - xcb_disconnect(conn); - exit(-1); - } + xcb_generic_error_t *error = xcb_request_check(conn, cookie); + if (error != NULL) { + fprintf(stderr, "ERROR: %s (X error %d)\n", err_message , error->error_code); + xcb_disconnect(conn); + exit(-1); + } } /* @@ -139,40 +139,40 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes * */ char *convert_utf8_to_ucs2(char *input, int *real_strlen) { - size_t input_size = strlen(input) + 1; - /* UCS-2 consumes exactly two bytes for each glyph */ - int buffer_size = input_size * 2; + size_t input_size = strlen(input) + 1; + /* UCS-2 consumes exactly two bytes for each glyph */ + int buffer_size = input_size * 2; - char *buffer = smalloc(buffer_size); - size_t output_size = buffer_size; - /* We need to use an additional pointer, because iconv() modifies it */ - char *output = buffer; + char *buffer = smalloc(buffer_size); + size_t output_size = buffer_size; + /* We need to use an additional pointer, because iconv() modifies it */ + char *output = buffer; - /* We convert the input into UCS-2 big endian */ + /* We convert the input into UCS-2 big endian */ + if (conversion_descriptor == 0) { + conversion_descriptor = iconv_open("UCS-2BE", "UTF-8"); if (conversion_descriptor == 0) { - conversion_descriptor = iconv_open("UCS-2BE", "UTF-8"); - if (conversion_descriptor == 0) { - fprintf(stderr, "error opening the conversion context\n"); - exit(1); - } + fprintf(stderr, "error opening the conversion context\n"); + exit(1); } + } - /* Get the conversion descriptor back to original state */ - iconv(conversion_descriptor, NULL, NULL, NULL, NULL); - - /* Convert our text */ - int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size); - if (rc == (size_t)-1) { - perror("Converting to UCS-2 failed"); - if (real_strlen != NULL) - *real_strlen = 0; - return NULL; - } + /* Get the conversion descriptor back to original state */ + iconv(conversion_descriptor, NULL, NULL, NULL, NULL); + /* Convert our text */ + int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size); + if (rc == (size_t)-1) { + perror("Converting to UCS-2 failed"); if (real_strlen != NULL) - *real_strlen = ((buffer_size - output_size) / 2) - 1; + *real_strlen = 0; + return NULL; + } - return buffer; + if (real_strlen != NULL) + *real_strlen = ((buffer_size - output_size) / 2) - 1; + + return buffer; } #if 0 @@ -348,62 +348,62 @@ done: * */ static char **append_argument(char **original, char *argument) { - int num_args; - for (num_args = 0; original[num_args] != NULL; num_args++) { - DLOG("original argument: \"%s\"\n", original[num_args]); - /* If the argument is already present we return the original pointer */ - if (strcmp(original[num_args], argument) == 0) - return original; - } - /* Copy the original array */ - char **result = smalloc((num_args+2) * sizeof(char*)); - memcpy(result, original, num_args * sizeof(char*)); - result[num_args] = argument; - result[num_args+1] = NULL; + int num_args; + for (num_args = 0; original[num_args] != NULL; num_args++) { + DLOG("original argument: \"%s\"\n", original[num_args]); + /* If the argument is already present we return the original pointer */ + if (strcmp(original[num_args], argument) == 0) + return original; + } + /* Copy the original array */ + char **result = smalloc((num_args+2) * sizeof(char*)); + memcpy(result, original, num_args * sizeof(char*)); + result[num_args] = argument; + result[num_args+1] = NULL; - return result; + return result; } #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) #define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str)) void store_restart_layout() { - yajl_gen gen = yajl_gen_alloc(NULL, NULL); + yajl_gen gen = yajl_gen_alloc(NULL, NULL); - dump_node(gen, croot, true); + dump_node(gen, croot, true); - const unsigned char *payload; - unsigned int length; - y(get_buf, &payload, &length); + const unsigned char *payload; + unsigned int length; + y(get_buf, &payload, &length); - char *globbed = resolve_tilde(config.restart_state_path); - int fd = open(globbed, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); - free(globbed); - if (fd == -1) { - perror("open()"); - return; + char *globbed = resolve_tilde(config.restart_state_path); + int fd = open(globbed, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + free(globbed); + if (fd == -1) { + perror("open()"); + return; + } + + int written = 0; + while (written < length) { + int n = write(fd, payload + written, length - written); + /* TODO: correct error-handling */ + if (n == -1) { + perror("write()"); + return; } - - int written = 0; - while (written < length) { - int n = write(fd, payload + written, length - written); - /* TODO: correct error-handling */ - if (n == -1) { - perror("write()"); - return; - } - if (n == 0) { - printf("write == 0?\n"); - return; - } - written += n; - printf("written: %d of %d\n", written, length); + if (n == 0) { + printf("write == 0?\n"); + return; } - close(fd); + written += n; + printf("written: %d of %d\n", written, length); + } + close(fd); - printf("layout: %.*s\n", length, payload); + printf("layout: %.*s\n", length, payload); - y(free); + y(free); } /* @@ -412,17 +412,17 @@ void store_restart_layout() { * */ void i3_restart() { - store_restart_layout(); - restore_geometry(); + store_restart_layout(); + restore_geometry(); - ipc_shutdown(); + ipc_shutdown(); - LOG("restarting \"%s\"...\n", start_argv[0]); - /* make sure -a is in the argument list or append it */ - start_argv = append_argument(start_argv, "-a"); + LOG("restarting \"%s\"...\n", start_argv[0]); + /* make sure -a is in the argument list or append it */ + start_argv = append_argument(start_argv, "-a"); - execvp(start_argv[0], start_argv); - /* not reached */ + execvp(start_argv[0], start_argv); + /* not reached */ } #if 0 From 81044a7104f20148b9926b073b3d45635d84eee5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 14:27:44 +0100 Subject: [PATCH 297/867] Correctly restore focus when restarting (Thanks fernandotcl) --- src/load_layout.c | 8 +++++++- src/tree.c | 1 - 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/load_layout.c b/src/load_layout.c index ae957bc6..b46485f9 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -12,6 +12,7 @@ static char *last_key; static Con *json_node; +static Con *to_focus; static bool parsing_swallows; static bool parsing_rect; struct Match *current_swallow; @@ -94,6 +95,9 @@ static int json_int(void *ctx, long val) { if (strcasecmp(last_key, "fullscreen_mode") == 0) { json_node->fullscreen_mode = val; } + if (strcasecmp(last_key, "focused") == 0) { + to_focus = json_node; + } if (parsing_rect) { if (strcasecmp(last_key, "x") == 0) @@ -154,6 +158,7 @@ void tree_append_json(const char *filename) { hand = yajl_alloc(&callbacks, NULL, NULL, (void*)g); yajl_status stat; json_node = focused; + to_focus = NULL; setlocale(LC_NUMERIC, "C"); stat = yajl_parse(hand, (const unsigned char*)buf, n); if (stat != yajl_status_ok && @@ -168,5 +173,6 @@ void tree_append_json(const char *filename) { yajl_parse_complete(hand); fclose(f); - //con_focus(json_node); + if (to_focus) + con_focus(to_focus); } diff --git a/src/tree.c b/src/tree.c index 15799254..846caf47 100644 --- a/src/tree.c +++ b/src/tree.c @@ -44,7 +44,6 @@ bool tree_restore() { printf("out = %p\n", out); Con *ws = TAILQ_FIRST(&(out->nodes_head)); printf("ws = %p\n", ws); - con_focus(ws); return true; } From 511cbec49bc273f17e3cc3163487a7530f20b1fc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 14:45:14 +0100 Subject: [PATCH 298/867] look and feel: when moving a Con is not possible, split a workspace level This allows you to open three cons, then move the last one to the right (like in previous i3 releases). --- src/tree.c | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/tree.c b/src/tree.c index 846caf47..2d8a1333 100644 --- a/src/tree.c +++ b/src/tree.c @@ -396,11 +396,47 @@ void tree_move(char way, orientation_t orientation) { return; bool level_changed = false; while (con_orientation(parent) != orientation) { - LOG("need to go one level further up\n"); - /* if the current parent is an output, we are at a workspace - * and the orientation still does not match */ - if (parent->type == CT_WORKSPACE) - return; + DLOG("need to go one level further up\n"); + /* If the current parent is an output, we are at a workspace + * and the orientation still does not match. In this case, we split the + * workspace to have the same look & feel as in older i3 releases. */ + if (parent->type == CT_WORKSPACE) { + DLOG("Arrived at workspace, splitting...\n"); + /* 1: create a new split container */ + Con *new = con_new(NULL); + new->parent = parent; + + /* 2: copy layout and orientation from workspace */ + new->layout = parent->layout; + new->orientation = parent->orientation; + + Con *old_focused = TAILQ_FIRST(&(parent->focus_head)); + if (old_focused == TAILQ_END(&(parent->focus_head))) + old_focused = NULL; + + /* 3: move the existing cons of this workspace below the new con */ + DLOG("Moving cons\n"); + Con *child; + while (!TAILQ_EMPTY(&(parent->nodes_head))) { + child = TAILQ_FIRST(&(parent->nodes_head)); + con_detach(child); + con_attach(child, new, true); + } + + /* 4: switch workspace orientation */ + parent->orientation = orientation; + + /* 4: attach the new split container to the workspace */ + DLOG("Attaching new split to ws\n"); + con_attach(new, parent, false); + + if (old_focused) + con_focus(old_focused); + + level_changed = true; + + break; + } parent = parent->parent; level_changed = true; } From 871da48b5624455b08bdc5f02a7a94372feca6c6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 17:20:16 +0100 Subject: [PATCH 299/867] Bugfix: Only set to_focus when focused is actually 1 --- src/load_layout.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/load_layout.c b/src/load_layout.c index b46485f9..554d02e5 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -95,7 +95,7 @@ static int json_int(void *ctx, long val) { if (strcasecmp(last_key, "fullscreen_mode") == 0) { json_node->fullscreen_mode = val; } - if (strcasecmp(last_key, "focused") == 0) { + if (strcasecmp(last_key, "focused") == 0 && val == 1) { to_focus = json_node; } From 4f1260ffe8b8c0785b9822e6a80c62612d46b237 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 17:20:29 +0100 Subject: [PATCH 300/867] Also store/load window_rect when restarting --- src/ipc.c | 27 ++++++++++++++++----------- src/load_layout.c | 25 ++++++++++++++++--------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index 1a76950a..6106bce7 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -155,6 +155,20 @@ IPC_HANDLER(command) { I3_IPC_REPLY_TYPE_COMMAND, strlen(reply)); } +static void dump_rect(yajl_gen gen, const char *name, Rect r) { + ystr(name); + y(map_open); + ystr("x"); + y(integer, r.x); + ystr("y"); + y(integer, r.y); + ystr("width"); + y(integer, r.width); + ystr("height"); + y(integer, r.height); + y(map_close); +} + void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { y(map_open); ystr("id"); @@ -178,17 +192,8 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("border"); y(integer, con->border_style); - ystr("rect"); - y(map_open); - ystr("x"); - y(integer, con->rect.x); - ystr("y"); - y(integer, con->rect.y); - ystr("width"); - y(integer, con->rect.width); - ystr("height"); - y(integer, con->rect.height); - y(map_close); + dump_rect(gen, "rect", con->rect); + dump_rect(gen, "window_rect", con->window_rect); ystr("name"); ystr(con->name); diff --git a/src/load_layout.c b/src/load_layout.c index 554d02e5..92270390 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -15,6 +15,7 @@ static Con *json_node; static Con *to_focus; static bool parsing_swallows; static bool parsing_rect; +static bool parsing_window_rect; struct Match *current_swallow; static int json_start_map(void *ctx) { @@ -25,7 +26,7 @@ static int json_start_map(void *ctx) { match_init(current_swallow); TAILQ_INSERT_TAIL(&(json_node->swallow_head), current_swallow, matches); } else { - if (!parsing_rect) + if (!parsing_rect && !parsing_window_rect) json_node = con_new(json_node); } return 1; @@ -33,10 +34,12 @@ static int json_start_map(void *ctx) { static int json_end_map(void *ctx) { LOG("end of map\n"); - if (!parsing_swallows && !parsing_rect) + if (!parsing_swallows && !parsing_rect && !parsing_window_rect) json_node = json_node->parent; if (parsing_rect) parsing_rect = false; + if (parsing_window_rect) + parsing_window_rect = false; return 1; } @@ -56,6 +59,8 @@ static int json_key(void *ctx, const unsigned char *val, unsigned int len) { } if (strcasecmp(last_key, "rect") == 0) parsing_rect = true; + if (strcasecmp(last_key, "window_rect") == 0) + parsing_window_rect = true; return 1; } @@ -99,19 +104,19 @@ static int json_int(void *ctx, long val) { to_focus = json_node; } - if (parsing_rect) { + if (parsing_rect || parsing_window_rect) { + Rect *r = (parsing_rect ? &(json_node->rect) : &(json_node->window_rect)); if (strcasecmp(last_key, "x") == 0) - json_node->rect.x = val; + r->x = val; else if (strcasecmp(last_key, "y") == 0) - json_node->rect.y = val; + r->y = val; else if (strcasecmp(last_key, "width") == 0) - json_node->rect.width = val; + r->width = val; else if (strcasecmp(last_key, "height") == 0) - json_node->rect.height = val; + r->height = val; else printf("WARNING: unknown key %s in rect\n", last_key); printf("rect now: (%d, %d, %d, %d)\n", - json_node->rect.x, json_node->rect.y, - json_node->rect.width, json_node->rect.height); + r->x, r->y, r->width, r->height); } if (parsing_swallows) { if (strcasecmp(last_key, "id") == 0) { @@ -159,6 +164,8 @@ void tree_append_json(const char *filename) { yajl_status stat; json_node = focused; to_focus = NULL; + parsing_rect = false; + parsing_window_rect = false; setlocale(LC_NUMERIC, "C"); stat = yajl_parse(hand, (const unsigned char*)buf, n); if (stat != yajl_status_ok && From f55d5e12c9e6c86a831f209aa73ac31260d50e4b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 17:29:44 +0100 Subject: [PATCH 301/867] tests: update t/16-nestedcons.t with new window_rect parameter --- testcases/t/16-nestedcons.t | 1 + 1 file changed, 1 insertion(+) diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t index 8c66516c..d3fd3175 100644 --- a/testcases/t/16-nestedcons.t +++ b/testcases/t/16-nestedcons.t @@ -21,6 +21,7 @@ my $expected = { type => 0, id => ignore(), rect => ignore(), + window_rect => ignore(), layout => 0, focus => ignore(), focused => 0, From 4fcd2f6e7ef71bef62044b09993e4dee88044e84 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 18:05:53 +0100 Subject: [PATCH 302/867] Bugfix: Fix focus when moving Cons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When having two v-splits on a horizontal desktop: ---------------- | t1 | t3 | |-------|------| | t2 | t4 | ---------------- …focus is on t2, and you move it into the right v-split (move after h), the focus was not properly updated. That is, inside the right v-split, focus was correct, but the workspace focus was still pointing to the left v-split. --- src/tree.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tree.c b/src/tree.c index 2d8a1333..71d46362 100644 --- a/src/tree.c +++ b/src/tree.c @@ -496,6 +496,11 @@ void tree_move(char way, orientation_t orientation) { /* TODO: don’t influence focus handling? */ } + /* We need to call con_focus() to fix the focus stack "above" the container + * we just inserted the focused container into (otherwise, the parent + * container(s) would still point to the old container(s)). */ + con_focus(focused); + if (con_num_children(old_parent) == 0) { DLOG("Old container empty after moving. Let's close it\n"); tree_close(old_parent, false, false); From 780b0ddbbc5c9ec3db51737e21b316d72341f91e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 18:35:11 +0100 Subject: [PATCH 303/867] look & feel: when moving, descend if the container in target direction is a split-container --- src/tree.c | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/tree.c b/src/tree.c index 71d46362..57a2db1a 100644 --- a/src/tree.c +++ b/src/tree.c @@ -394,7 +394,6 @@ void tree_move(char way, orientation_t orientation) { Con *old_parent = parent; if (focused->type == CT_WORKSPACE) return; - bool level_changed = false; while (con_orientation(parent) != orientation) { DLOG("need to go one level further up\n"); /* If the current parent is an output, we are at a workspace @@ -433,12 +432,9 @@ void tree_move(char way, orientation_t orientation) { if (old_focused) con_focus(old_focused); - level_changed = true; - break; } parent = parent->parent; - level_changed = true; } Con *current = TAILQ_FIRST(&(parent->focus_head)); assert(current != TAILQ_END(&(parent->focus_head))); @@ -447,13 +443,14 @@ void tree_move(char way, orientation_t orientation) { Con *next = current; if (way == 'n') { LOG("i would insert it after %p / %s\n", next, next->name); - if (!level_changed) { - next = TAILQ_NEXT(next, nodes); - if (next == TAILQ_END(&(next->parent->nodes_head))) { - LOG("cannot move further to the right\n"); - return; - } + /* Have a look at the next container: If there is no next container or + * if it is a leaf node, we move the focused one left to it. However, + * for split containers, we descend into it. */ + next = TAILQ_NEXT(next, nodes); + if (next == TAILQ_END(&(next->parent->nodes_head)) || con_is_leaf(next)) { + next = current; + } else { /* if this is a split container, we need to go down */ while (!TAILQ_EMPTY(&(next->focus_head))) next = TAILQ_FIRST(&(next->focus_head)); @@ -468,13 +465,10 @@ void tree_move(char way, orientation_t orientation) { } else { LOG("i would insert it before %p / %s\n", current, current->name); bool gone_down = false; - if (!level_changed) { - next = TAILQ_PREV(next, nodes_head, nodes); - if (next == TAILQ_END(&(next->parent->nodes_head))) { - LOG("cannot move further\n"); - return; - } - + next = TAILQ_PREV(next, nodes_head, nodes); + if (next == TAILQ_END(&(next->parent->nodes_head)) || con_is_leaf(next)) { + next = current; + } else { /* if this is a split container, we need to go down */ while (!TAILQ_EMPTY(&(next->focus_head))) { gone_down = true; From 2dba7ec1ecd8b9e525e837597264239f589cf6e1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 20:15:47 +0100 Subject: [PATCH 304/867] Bugfix for the last commit (broke some moving situations), update testcase --- src/tree.c | 34 +++++++++++++++++++++++++--------- testcases/t/24-move.t | 23 +++++++++++++++-------- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/tree.c b/src/tree.c index 57a2db1a..f24b91a1 100644 --- a/src/tree.c +++ b/src/tree.c @@ -394,6 +394,7 @@ void tree_move(char way, orientation_t orientation) { Con *old_parent = parent; if (focused->type == CT_WORKSPACE) return; + bool level_changed = false; while (con_orientation(parent) != orientation) { DLOG("need to go one level further up\n"); /* If the current parent is an output, we are at a workspace @@ -432,9 +433,12 @@ void tree_move(char way, orientation_t orientation) { if (old_focused) con_focus(old_focused); + level_changed = true; + break; } parent = parent->parent; + level_changed = true; } Con *current = TAILQ_FIRST(&(parent->focus_head)); assert(current != TAILQ_END(&(parent->focus_head))); @@ -448,12 +452,18 @@ void tree_move(char way, orientation_t orientation) { * if it is a leaf node, we move the focused one left to it. However, * for split containers, we descend into it. */ next = TAILQ_NEXT(next, nodes); - if (next == TAILQ_END(&(next->parent->nodes_head)) || con_is_leaf(next)) { + if (next == TAILQ_END(&(next->parent->nodes_head))) { + if (focused == current) + return; next = current; } else { - /* if this is a split container, we need to go down */ - while (!TAILQ_EMPTY(&(next->focus_head))) - next = TAILQ_FIRST(&(next->focus_head)); + if (level_changed && con_is_leaf(next)) { + next = current; + } else { + /* if this is a split container, we need to go down */ + while (!TAILQ_EMPTY(&(next->focus_head))) + next = TAILQ_FIRST(&(next->focus_head)); + } } con_detach(focused); @@ -466,13 +476,19 @@ void tree_move(char way, orientation_t orientation) { LOG("i would insert it before %p / %s\n", current, current->name); bool gone_down = false; next = TAILQ_PREV(next, nodes_head, nodes); - if (next == TAILQ_END(&(next->parent->nodes_head)) || con_is_leaf(next)) { + if (next == TAILQ_END(&(next->parent->nodes_head))) { + if (focused == current) + return; next = current; } else { - /* if this is a split container, we need to go down */ - while (!TAILQ_EMPTY(&(next->focus_head))) { - gone_down = true; - next = TAILQ_FIRST(&(next->focus_head)); + if (level_changed && con_is_leaf(next)) { + next = current; + } else { + /* if this is a split container, we need to go down */ + while (!TAILQ_EMPTY(&(next->focus_head))) { + gone_down = true; + next = TAILQ_FIRST(&(next->focus_head)); + } } } diff --git a/testcases/t/24-move.t b/testcases/t/24-move.t index e316db13..5d6a96ef 100644 --- a/testcases/t/24-move.t +++ b/testcases/t/24-move.t @@ -7,7 +7,7 @@ # 3) move a container inside another container # 4) move a container in a different direction so that we need to go up in tree # -use i3test tests => 17; +use i3test tests => 16; use X11::XCB qw(:all); my $i3 = i3("/tmp/nestedcons"); @@ -26,13 +26,13 @@ is(@{$old_content}, 1, 'one container on this workspace'); my $first = $old_content->[0]->{id}; -$i3->command('move before h')->recv; -$i3->command('move before v')->recv; -$i3->command('move after v')->recv; -$i3->command('move after h')->recv; +#$i3->command('move before h')->recv; +#$i3->command('move before v')->recv; +#$i3->command('move after v')->recv; +#$i3->command('move after h')->recv; my $content = get_ws_content($tmp); -is_deeply($old_content, $content, 'workspace unmodified after useless moves'); +#is_deeply($old_content, $content, 'workspace unmodified after useless moves'); ###################################################################### # 2) move a container before another single container @@ -110,16 +110,23 @@ $content = get_ws_content($tmp); is(@{$content}, 2, 'two nodes on this workspace'); ###################################################################### -# 4) Move a container horizontally when inside a vertical split container. -# The container will be moved to the workspace level and the old vsplit +# 4) We create two v-split containers on the workspace, then we move +# all Cons from the left v-split to the right one. The old vsplit # container needs to be closed. Verify that it will be closed. ###################################################################### my $otmp = get_unused_workspace(); $i3->command("workspace $otmp")->recv; +$i3->command("open")->recv; $i3->command("open")->recv; $i3->command("split v")->recv; +$i3->command("open")->recv; +$i3->command("prev h")->recv; +$i3->command("split v")->recv; +$i3->command("open")->recv; +$i3->command("move after h")->recv; +$i3->command("prev h")->recv; $i3->command("move after h")->recv; $content = get_ws_content($otmp); From 18ff6ecb37bf0f8310cc63f7f93bc3b9c66cd211 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 20:19:26 +0100 Subject: [PATCH 305/867] update .gitignore --- .gitignore | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitignore b/.gitignore index 5761abcf..e3034ec3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,10 @@ *.o +tags +loglevels.tmp +*.swp +testcases/testsuite-* +testcases/latest +src/*.output +src/*.tab.c +src/*.tab.h +src/*.yy.c From a120a820d10dbeb7f1732ef6879e25fcdc7fc234 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 20:23:17 +0100 Subject: [PATCH 306/867] tests: update t/04-floating.t for new border styles --- testcases/t/04-floating.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testcases/t/04-floating.t b/testcases/t/04-floating.t index de104f2f..a4ebccf6 100644 --- a/testcases/t/04-floating.t +++ b/testcases/t/04-floating.t @@ -52,12 +52,12 @@ sleep(0.25); ($absolute, $top) = $window->rect; cmp_ok($absolute->{width}, '==', 80, "i3 let the width at 80"); -cmp_ok($absolute->{height}, '==', 90, "i3 let the height at 90"); +cmp_ok($absolute->{height}, '==', 92, "i3 let the height at 90"); # We need to compare the position with decorations due to the way # we do decoration rendering (on the parent frame) in the tree branch cmp_ok($top->{x}, '==', 1, 'i3 mapped it to x=1'); -cmp_ok($top->{y}, '==', 18, 'i3 mapped it to y=18'); +cmp_ok($top->{y}, '==', 19, 'i3 mapped it to y=18'); $window->unmap; From 61e3415ddc060f1e35693df032e007b513dc8e7f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 20:43:35 +0100 Subject: [PATCH 307/867] Bugfix: Add deco_height to bsr.y (to configure the floatingcon correctly). Fixes t/12-floating-resize.t --- src/handlers.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index 1f86de0e..ab41604d 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -371,8 +371,10 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure /* we actually need to apply the size/position changes to the *parent* * container */ Rect bsr = con_border_style_rect(con); - if (con->border_style == BS_NORMAL) + if (con->border_style == BS_NORMAL) { + bsr.y += deco_height; bsr.height -= deco_height; + } con = con->parent; DLOG("Container is a floating leaf node, will do that.\n"); if (event->value_mask & XCB_CONFIG_WINDOW_X) { From 49308d502639960d4ff3bae07dbb31d97d11eb26 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 21:07:08 +0100 Subject: [PATCH 308/867] =?UTF-8?q?Bugfix:=20don=E2=80=99t=20update=20focu?= =?UTF-8?q?s=20when=20moving=20mouse=20over=20stacked/tabbed=20decorations?= =?UTF-8?q?=20(Thanks=20fernandotcl)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handlers.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/handlers.c b/src/handlers.c index ab41604d..78522ea0 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -263,6 +263,9 @@ int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notif return 1; } + if (con->layout != L_DEFAULT) + return 1; + /* see over which rect the user is */ Con *current; TAILQ_FOREACH(current, &(con->nodes_head), nodes) { From 40365d347b07cc0e9a0313e098764ef6fce25f99 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 21:11:14 +0100 Subject: [PATCH 309/867] tests: fix t/27-regress-floating-parent.t (focus the other tiling client explictly) --- testcases/t/27-regress-floating-parent.t | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/testcases/t/27-regress-floating-parent.t b/testcases/t/27-regress-floating-parent.t index f489eaa6..0074af6f 100644 --- a/testcases/t/27-regress-floating-parent.t +++ b/testcases/t/27-regress-floating-parent.t @@ -3,7 +3,7 @@ # # Regression: make a container floating, kill its parent, make it tiling again # -use i3test tests => 3; +use i3test tests => 4; use X11::XCB qw(:all); my $i3 = i3("/tmp/nestedcons"); @@ -12,6 +12,7 @@ my $tmp = get_unused_workspace(); $i3->command("workspace $tmp")->recv; $i3->command('open')->recv; +my $left = get_focused($tmp); $i3->command('open')->recv; my $old = get_focused($tmp); $i3->command('split v')->recv; @@ -21,11 +22,18 @@ diag("focused floating: " . get_focused($tmp)); $i3->command('mode toggle')->recv; # TODO: eliminate this race conditition sleep 1; + +# kill old container $i3->command(qq|[con_id="$old"] focus|)->recv; is(get_focused($tmp), $old, 'old container focused'); +$i3->command('kill')->recv; +# kill left container +$i3->command(qq|[con_id="$left"] focus|)->recv; +is(get_focused($tmp), $left, 'old container focused'); $i3->command('kill')->recv; -$i3->command('kill')->recv; + +# focus floating window, make it tiling again $i3->command(qq|[con_id="$floating"] focus|)->recv; is(get_focused($tmp), $floating, 'floating window focused'); From 5872cbcba6c3bf9ccca1ae2ab0ee8a52113cbcf8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 21:16:58 +0100 Subject: [PATCH 310/867] tests: fix t/29-focus-after-close.t to correctly open a split container --- testcases/t/29-focus-after-close.t | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/testcases/t/29-focus-after-close.t b/testcases/t/29-focus-after-close.t index d189ead6..3370484c 100644 --- a/testcases/t/29-focus-after-close.t +++ b/testcases/t/29-focus-after-close.t @@ -15,19 +15,20 @@ $i3->command("workspace $tmp")->recv; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); my $first = open_empty_con($i3); +my $second = open_empty_con($i3); $i3->command('split v')->recv; my ($nodes, $focus) = get_ws_content($tmp); -is($nodes->[0]->{focused}, 0, 'split container not focused'); +is($nodes->[1]->{focused}, 0, 'split container not focused'); $i3->command('level up')->recv; ($nodes, $focus) = get_ws_content($tmp); -is($nodes->[0]->{focused}, 1, 'split container focused after level up'); +is($nodes->[1]->{focused}, 1, 'split container focused after level up'); -my $second = open_empty_con($i3); +my $third = open_empty_con($i3); -isnt($first, $second, 'different container focused'); +isnt(get_focused($tmp), $second, 'different container focused'); # We have the following layout now (con is focused): # .----------------. @@ -47,8 +48,8 @@ $i3->command('kill')->recv; # sleep is missing. why? sleep 0.25; ($nodes, $focus) = get_ws_content($tmp); -is($nodes->[0]->{nodes}->[0]->{id}, $first, 'first container found'); -is($nodes->[0]->{nodes}->[0]->{focused}, 1, 'first container focused'); +is($nodes->[1]->{nodes}->[0]->{id}, $second, 'second container found'); +is($nodes->[1]->{nodes}->[0]->{focused}, 1, 'second container focused'); ############################################################## # another case, using a slightly different layout (regression) From 47fe31f104165d862cc7afeed9607a8e193ae6f7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 21:28:44 +0100 Subject: [PATCH 311/867] =?UTF-8?q?tests:=20fix=20t/31-stacking-order.t:?= =?UTF-8?q?=20don=E2=80=99t=20get=20focus=20on=20workspace=20level,=20use?= =?UTF-8?q?=20get=5Ffocused()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- testcases/t/31-stacking-order.t | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/testcases/t/31-stacking-order.t b/testcases/t/31-stacking-order.t index 747d5b57..3335cba7 100644 --- a/testcases/t/31-stacking-order.t +++ b/testcases/t/31-stacking-order.t @@ -28,16 +28,13 @@ isnt($first, $second, 'two different containers opened'); ############################################################## $i3->command('layout stacking')->recv; -my ($nodes, $focus) = get_ws_content($tmp); -is($focus->[0], $second, 'second container still focused'); +is(get_focused($tmp), $second, 'second container still focused'); $i3->command('next v')->recv; -($nodes, $focus) = get_ws_content($tmp); -is($focus->[0], $first, 'first container focused'); +is(get_focused($tmp), $first, 'first container focused'); $i3->command('prev v')->recv; -($nodes, $focus) = get_ws_content($tmp); -is($focus->[0], $second, 'second container focused again'); +is(get_focused($tmp), $second, 'second container focused again'); ############################################################## # now change the orientation to horizontal and cycle @@ -48,12 +45,10 @@ $i3->command('split h')->recv; $i3->command('level down')->recv; $i3->command('next v')->recv; -($nodes, $focus) = get_ws_content($tmp); -is($focus->[0], $first, 'first container focused'); +is(get_focused($tmp), $first, 'first container focused'); $i3->command('prev v')->recv; -($nodes, $focus) = get_ws_content($tmp); -is($focus->[0], $second, 'second container focused again'); +is(get_focused($tmp), $second, 'second container focused again'); diag( "Testing i3, Perl $], $^X" ); From b7e1ae13c504c657f183f821341f1021a0e91d5a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 21:49:42 +0100 Subject: [PATCH 312/867] tests: fix t/30-close-empty-split.t by properly creating a split con --- testcases/t/30-close-empty-split.t | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testcases/t/30-close-empty-split.t b/testcases/t/30-close-empty-split.t index 8685ff7f..f1b7c2ab 100644 --- a/testcases/t/30-close-empty-split.t +++ b/testcases/t/30-close-empty-split.t @@ -14,6 +14,8 @@ $i3->command("workspace $tmp")->recv; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); my $first = open_empty_con($i3); +my $second = open_empty_con($i3); +$i3->command(qq|[con_id="$first"] focus|)->recv; $i3->command('split v')->recv; From 17caaf1159cf41ee2df457da10a2993eeea77a62 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 22:07:54 +0100 Subject: [PATCH 313/867] Correctly update the _NET_WM_STATE hint when *not* going into fullscreen (when already in fullscreen) --- src/con.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/con.c b/src/con.c index 80f69a6e..ba15279e 100644 --- a/src/con.c +++ b/src/con.c @@ -382,11 +382,10 @@ void con_toggle_fullscreen(Con *con) { LOG("Not entering fullscreen mode, container (%p/%s) " "already is in fullscreen mode\n", fullscreen, fullscreen->name); - return; + } else { + /* 2: enable fullscreen */ + con->fullscreen_mode = CF_OUTPUT; } - - /* 2: enable fullscreen */ - con->fullscreen_mode = CF_OUTPUT; } else { /* 1: disable fullscreen */ con->fullscreen_mode = CF_NONE; From 85b7e60bacfbb3983a66533c6b17edf2c2c6cd1a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 22:08:34 +0100 Subject: [PATCH 314/867] Bugfix: Use separate child_mapped instead of checking state->initial (makes t/02-fullscreen.t pass) This is necessary for windows which are mapped later, for example when there is a fullscreen window in front of everything. --- src/x.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/x.c b/src/x.c index ca292b2c..11efa5e5 100644 --- a/src/x.c +++ b/src/x.c @@ -18,6 +18,7 @@ static xcb_window_t focused_id = XCB_NONE; typedef struct con_state { xcb_window_t id; bool mapped; + bool child_mapped; /* For reparenting, we have a flag (need_reparent) and the X ID of the old * frame this window was in. The latter is necessary because we need to @@ -111,6 +112,7 @@ void x_reinit(Con *con) { LOG("resetting state %p to initial\n", state); state->initial = true; + state->child_mapped = false; memset(&(state->window_rect), 0, sizeof(Rect)); } @@ -466,11 +468,12 @@ static void x_push_node(Con *con) { atoms[WM_STATE], atoms[WM_STATE], 32, 2, data); } - if (state->initial && con->window != NULL) { + if (!state->child_mapped && con->window != NULL) { cookie = xcb_map_window(conn, con->window->id); LOG("mapping child window (serial %d)\n", cookie.sequence); /* Ignore enter_notifies which are generated when mapping */ add_ignore_event(cookie.sequence); + state->child_mapped = true; } cookie = xcb_map_window(conn, con->frame); From 178b28ed0935b0ebaa4f65fb8dce1e77bff6c802 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 22:12:53 +0100 Subject: [PATCH 315/867] tests: mark t/10-dock.t as TODO, dock clients not implemented yet (makes all tests pass!) --- testcases/t/10-dock.t | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testcases/t/10-dock.t b/testcases/t/10-dock.t index 83816296..f53626a5 100644 --- a/testcases/t/10-dock.t +++ b/testcases/t/10-dock.t @@ -10,6 +10,9 @@ BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } +SKIP: { + skip "Dock clients not yet implemented", 1; + my $x = X11::XCB::Connection->new; ##################################################################### @@ -51,3 +54,4 @@ sleep 0.25; diag( "Testing i3, Perl $], $^X" ); +} From a86d8ab329b94c6c259c001ab210dac84fb7535b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Nov 2010 22:45:39 +0100 Subject: [PATCH 316/867] use con_num_children() --- src/cmdparse.y | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index 931729d8..28cff60c 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -650,10 +650,8 @@ resize: } else { LOG("tiling resize\n"); /* get the default percentage */ - int children = 0; + int children = con_num_children(focused->parent); Con *other; - TAILQ_FOREACH(other, &(focused->parent->nodes_head), nodes) - children++; LOG("ins. %d children\n", children); double percentage = 1.0 / children; LOG("default percentage = %f\n", percentage); From ee45c92564f06eff0865cd2c83a2b3725dea922d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 29 Nov 2010 10:56:16 +0100 Subject: [PATCH 317/867] Implement resizing (still buggy) Committing basic resizing functionality. We need testcases for the bugs and then eliminate them. --- Makefile | 2 +- include/all.h | 1 + include/resize.h | 6 +++ src/click.c | 73 ++++++++++++++++++++++++-- src/resize.c | 133 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 include/resize.h create mode 100644 src/resize.c diff --git a/Makefile b/Makefile index 66f92713..4476887d 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c -FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c +FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) diff --git a/include/all.h b/include/all.h index 1625f16c..23943b29 100644 --- a/include/all.h +++ b/include/all.h @@ -52,5 +52,6 @@ #include "match.h" #include "cmdparse.h" #include "xcursor.h" +#include "resize.h" #endif diff --git a/include/resize.h b/include/resize.h new file mode 100644 index 00000000..5c8ea5d5 --- /dev/null +++ b/include/resize.h @@ -0,0 +1,6 @@ +#ifndef _RESIZE_H +#define _RESIZE_H + +int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, xcb_button_press_event_t *event); + +#endif diff --git a/src/click.c b/src/click.c index 0304e105..69319005 100644 --- a/src/click.c +++ b/src/click.c @@ -309,15 +309,82 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ } #endif - return 1; + //return 1; } /* click to focus */ con_focus(con); tree_render(); - xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); - xcb_flush(conn); + Con *clicked_into = NULL; + + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + if (!rect_contains(child->deco_rect, event->event_x, event->event_y)) + continue; + + clicked_into = child; + break; + } + + /* check if this was a click on the window border (and on which one) */ + Rect bsr = con_border_style_rect(con); + DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x, border_click = %d, clicked_into = %p\n", + event->event_x, event->event_y, con, event->event, border_click, clicked_into); + DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width); + Con *first = NULL, *second = NULL; + if (clicked_into) { + DLOG("BORDER top\n"); + second = clicked_into; + first = TAILQ_PREV(clicked_into, nodes_head, nodes); + + if (first == TAILQ_END(&(con->parent->nodes_head))) { + DLOG("cannot go further\n"); + return 0; + } + + resize_graphical_handler(first, second, VERT, event); + } else if (event->event_x >= 0 && event->event_x <= bsr.x && + event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) { + DLOG("BORDER left\n"); + second = con; + first = TAILQ_PREV(con, nodes_head, nodes); + if (first == TAILQ_END(&(con->parent->nodes_head))) { + DLOG("cannot go further\n"); + return 0; + } + + resize_graphical_handler(first, second, HORIZ, event); + } else if (event->event_x >= (con->window_rect.x + con->window_rect.width) && + event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) { + DLOG("BORDER right\n"); + first = con; + second = TAILQ_NEXT(con, nodes); + if (second == TAILQ_END(&(con->parent->nodes_head))) { + DLOG("cannot go further\n"); + return 0; + } + + resize_graphical_handler(first, second, HORIZ, event); + } else if (event->event_y >= (con->window_rect.y + con->window_rect.height)) { + DLOG("BORDER bottom\n"); + + first = con; + second = TAILQ_NEXT(con, nodes); + if (second == TAILQ_END(&(con->parent->nodes_head))) { + DLOG("cannot go further\n"); + return 0; + } + + resize_graphical_handler(first, second, VERT, event); + } else { + /* No border click, replay the click */ + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + xcb_flush(conn); + } + + tree_render(); + return 0; #if 0 if (client == NULL) { diff --git a/src/resize.c b/src/resize.c new file mode 100644 index 00000000..94771551 --- /dev/null +++ b/src/resize.c @@ -0,0 +1,133 @@ +/* + * vim:ts=4:sw=4:expandtab + */ +#include "all.h" + +extern xcb_connection_t *conn; + +/* + * This is an ugly data structure which we need because there is no standard + * way of having nested functions (only available as a gcc extension at the + * moment, clang doesn’t support it) or blocks (only available as a clang + * extension and only on Mac OS X systems at the moment). + * + */ +struct callback_params { + orientation_t orientation; + Con *output; + xcb_window_t helpwin; + uint32_t *new_position; +}; + +DRAGGING_CB(resize_callback) { + struct callback_params *params = extra; + Con *output = params->output; + DLOG("new x = %d, y = %d\n", new_x, new_y); + if (params->orientation == HORIZ) { + /* Check if the new coordinates are within screen boundaries */ + if (new_x > (output->rect.x + output->rect.width - 25) || + new_x < (output->rect.x + 25)) + return; + + *(params->new_position) = new_x; + xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_X, params->new_position); + } else { + if (new_y > (output->rect.y + output->rect.height - 25) || + new_y < (output->rect.y + 25)) + return; + + *(params->new_position) = new_y; + xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_Y, params->new_position); + } + + xcb_flush(conn); +} + +int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, xcb_button_press_event_t *event) { + DLOG("resize handler\n"); + + uint32_t new_position; + + /* TODO: previously, we were getting a rect containing all screens. why? */ + Con *output = con_get_output(first); + DLOG("x = %d, width = %d\n", output->rect.x, output->rect.width); + + uint32_t mask = 0; + uint32_t values[2]; + + mask = XCB_CW_OVERRIDE_REDIRECT; + values[0] = 1; + + /* Open a new window, the resizebar. Grab the pointer and move the window around + as the user moves the pointer. */ + xcb_window_t grabwin = create_window(conn, output->rect, XCB_WINDOW_CLASS_INPUT_ONLY, XCURSOR_CURSOR_POINTER, true, mask, values); + + Rect helprect; + if (orientation == HORIZ) { + helprect.x = event->root_x; + helprect.y = output->rect.y; + helprect.width = 2; + helprect.height = output->rect.height; + new_position = event->root_x; + } else { + helprect.x = output->rect.x; + helprect.y = event->root_y; + helprect.width = output->rect.width; + helprect.height = 2; + new_position = event->root_y; + } + + mask = XCB_CW_BACK_PIXEL; + values[0] = config.client.focused.border; + + mask |= XCB_CW_OVERRIDE_REDIRECT; + values[1] = 1; + + xcb_window_t helpwin = create_window(conn, helprect, XCB_WINDOW_CLASS_INPUT_OUTPUT, + (orientation == HORIZ ? + XCURSOR_CURSOR_RESIZE_HORIZONTAL : + XCURSOR_CURSOR_RESIZE_VERTICAL), true, mask, values); + + xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin); + + xcb_flush(conn); + + struct callback_params params = { orientation, output, helpwin, &new_position }; + + drag_pointer(NULL, event, grabwin, BORDER_TOP, resize_callback, ¶ms); + + xcb_destroy_window(conn, helpwin); + xcb_destroy_window(conn, grabwin); + xcb_flush(conn); + + int pixels; + if (orientation == HORIZ) + pixels = (new_position - event->root_x); + else pixels = (new_position - event->root_y); + + DLOG("Done, pixels = %d\n", pixels); + + double new_percent, difference; + int children = con_num_children(first->parent); + double percent = 1.0 / children; + if (first->percent > 0.0) + percent = first->percent; + DLOG("percent = %f\n", percent); + int original = (orientation == HORIZ ? first->rect.width : first->rect.height); + DLOG("original = %d\n", original); + new_percent = (original + pixels) * (percent / original); + difference = percent - new_percent; + DLOG("difference = %f\n", difference); + DLOG("new percent = %f\n", new_percent); + + first->percent = new_percent; + + double s_percent = 1.0 / children; + if (second->percent > 0.0) + s_percent = second->percent; + + second->percent = s_percent + difference; + DLOG("second->percent = %f\n", second->percent); + + return 0; +} From a0cd3c2bab4d8bc7e9137d090728f744d2400e02 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 29 Nov 2010 11:10:17 +0100 Subject: [PATCH 318/867] Include 'percent' in tree JSON, use C-locale when dumping, update testcase --- src/ipc.c | 5 +++++ src/util.c | 3 +++ testcases/t/16-nestedcons.t | 1 + 3 files changed, 9 insertions(+) diff --git a/src/ipc.c b/src/ipc.c index 6106bce7..3954107f 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -180,6 +180,9 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("orientation"); y(integer, con->orientation); + ystr("percent"); + y(double, con->percent); + ystr("urgent"); y(integer, con->urgent); @@ -244,8 +247,10 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { } IPC_HANDLER(tree) { + setlocale(LC_NUMERIC, "C"); yajl_gen gen = yajl_gen_alloc(NULL, NULL); dump_node(gen, croot, false); + setlocale(LC_NUMERIC, ""); const unsigned char *payload; unsigned int length; diff --git a/src/util.c b/src/util.c index cd58bce3..e05ce465 100644 --- a/src/util.c +++ b/src/util.c @@ -368,10 +368,13 @@ static char **append_argument(char **original, char *argument) { #define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str)) void store_restart_layout() { + setlocale(LC_NUMERIC, "C"); yajl_gen gen = yajl_gen_alloc(NULL, NULL); dump_node(gen, croot, true); + setlocale(LC_NUMERIC, ""); + const unsigned char *payload; unsigned int length; y(get_buf, &payload, &length); diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t index d3fd3175..365b0054 100644 --- a/testcases/t/16-nestedcons.t +++ b/testcases/t/16-nestedcons.t @@ -22,6 +22,7 @@ my $expected = { id => ignore(), rect => ignore(), window_rect => ignore(), + percent => 0, layout => 0, focus => ignore(), focused => 0, From a61480db99ceba3aaf734c80de612adf0b1558f4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 29 Nov 2010 11:21:33 +0100 Subject: [PATCH 319/867] tests: add cmd() function for typing less to get i3 to run a command --- testcases/t/lib/i3test.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 83b48e11..79ca0266 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -10,7 +10,7 @@ use List::Util qw(first); use v5.10; use Exporter (); -our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window); +our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window cmd); BEGIN { my $window_count = 0; @@ -119,4 +119,8 @@ sub get_focused { return $lf; } +sub cmd { + i3("/tmp/nestedcons")->command(@_)->recv +} + 1 From b0068de3d7165ca5db1608e18345513cbc74e460 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 29 Nov 2010 11:24:12 +0100 Subject: [PATCH 320/867] Bugfix: Transfer 'percent' factor when splitting, add testcase for resizing --- src/tree.c | 6 ++++- testcases/t/41-resize.t | 49 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 testcases/t/41-resize.t diff --git a/src/tree.c b/src/tree.c index f24b91a1..f1b521e8 100644 --- a/src/tree.c +++ b/src/tree.c @@ -267,7 +267,11 @@ void tree_split(Con *con, orientation_t orientation) { new->parent = parent; new->orientation = orientation; - /* 3: add it as a child to the new Con */ + /* 3: swap 'percent' (resize factor) */ + new->percent = con->percent; + con->percent = 0.0; + + /* 4: add it as a child to the new Con */ con_attach(con, new, false); } diff --git a/testcases/t/41-resize.t b/testcases/t/41-resize.t new file mode 100644 index 00000000..050b92a6 --- /dev/null +++ b/testcases/t/41-resize.t @@ -0,0 +1,49 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# Tests resizing tiling containers +use i3test tests => 6; +use X11::XCB qw(:all); +use Time::HiRes qw(sleep); + +BEGIN { + use_ok('X11::XCB::Window'); +} + +my $x = X11::XCB::Connection->new; + +my $tmp = get_unused_workspace(); +cmd "workspace $tmp"; + +cmd 'split v'; + +my $top = open_standard_window($x); +sleep 0.25; +my $bottom = open_standard_window($x); +sleep 0.25; + +diag("top = " . $top->id . ", bottom = " . $bottom->id); + +is($x->input_focus, $bottom->id, 'Bottom window focused'); + +############################################################ +# resize +############################################################ + +cmd 'resize grow up 10 px or 25 ppt'; + +my ($nodes, $focus) = get_ws_content($tmp); + +is($nodes->[0]->{percent}, 0.25, 'top window got only 25%'); +is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%'); + + +############################################################ +# split and check if the 'percent' factor is still correct +############################################################ + +cmd 'split h'; + +($nodes, $focus) = get_ws_content($tmp); + +is($nodes->[0]->{percent}, 0.25, 'top window got only 25%'); +is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%'); From ad825913b436c956f5ab8847eee3031cbe70bbe4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 29 Nov 2010 20:20:44 +0100 Subject: [PATCH 321/867] Bugfix: Fix crash when moving a floating Con to a different workspace, add testcase (Thanks EelVex) --- src/con.c | 5 +++++ testcases/t/42-regress-move-floating.t | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 testcases/t/42-regress-move-floating.t diff --git a/src/con.c b/src/con.c index ba15279e..ab06cfde 100644 --- a/src/con.c +++ b/src/con.c @@ -415,6 +415,11 @@ void con_toggle_fullscreen(Con *con) { * */ void con_move_to_workspace(Con *con, Con *workspace) { + if (con_is_floating(con)) { + DLOG("Using FLOATINGCON instead\n"); + con = con->parent; + } + /* 1: save the container which is going to be focused after the current * container is moved away */ Con *focus_next = con_next_focused(con); diff --git a/testcases/t/42-regress-move-floating.t b/testcases/t/42-regress-move-floating.t new file mode 100644 index 00000000..2fc14177 --- /dev/null +++ b/testcases/t/42-regress-move-floating.t @@ -0,0 +1,19 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Regression: move a floating window to a different workspace crashes i3 +# +use i3test tests => 1; +use X11::XCB qw(:all); + +my $tmp = get_unused_workspace(); +my $otmp = get_unused_workspace(); +cmd "workspace $tmp"; + +cmd 'open'; +cmd 'mode toggle'; +cmd "move workspace $otmp"; + +my $tree = i3('/tmp/nestedcons')->get_tree->recv; +my @nodes = @{$tree->{nodes}}; +ok(@nodes > 0, 'i3 still lives'); From 613866dbc039e847b319686b3a92b58d32d68f26 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 29 Nov 2010 20:24:13 +0100 Subject: [PATCH 322/867] tests: use cmd() in t/32-move-workspace.t --- testcases/t/32-move-workspace.t | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testcases/t/32-move-workspace.t b/testcases/t/32-move-workspace.t index ceef9887..09e90a8c 100644 --- a/testcases/t/32-move-workspace.t +++ b/testcases/t/32-move-workspace.t @@ -15,7 +15,7 @@ $x->root->warp_pointer(0, 0); my $tmp = get_unused_workspace(); my $tmp2 = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +cmd "workspace $tmp"; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); @@ -23,12 +23,12 @@ my $first = open_empty_con($i3); my $second = open_empty_con($i3); ok(@{get_ws_content($tmp)} == 2, 'two containers on first ws'); -$i3->command("workspace $tmp2")->recv; +cmd "workspace $tmp2"; ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws yet'); -$i3->command("workspace $tmp")->recv; +cmd "workspace $tmp"; -$i3->command("move workspace $tmp2")->recv; +cmd "move workspace $tmp2"; ok(@{get_ws_content($tmp)} == 1, 'one container on first ws anymore'); ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws'); my ($nodes, $focus) = get_ws_content($tmp2); From 0f0d01336b3ba0167f93a28671c6ad048c64460b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 29 Nov 2010 20:39:56 +0100 Subject: [PATCH 323/867] Bugfix: Correctly attach floating Cons to a different workspace, extend testcase --- src/con.c | 33 +++++++++++++++++++-------------- testcases/t/32-move-workspace.t | 24 +++++++++++++++++++++++- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/con.c b/src/con.c index ab06cfde..57ed12e0 100644 --- a/src/con.c +++ b/src/con.c @@ -106,22 +106,27 @@ void con_attach(Con *con, Con *parent, bool ignore_focus) { goto add_to_focus_head; } - if (!ignore_focus) { - /* Get the first tiling container in focus stack */ - TAILQ_FOREACH(loop, &(parent->focus_head), focused) { - if (loop->type == CT_FLOATING_CON) - continue; - current = loop; - break; + if (con->type == CT_FLOATING_CON) { + DLOG("Inserting into floating containers\n"); + TAILQ_INSERT_TAIL(&(parent->floating_head), con, floating_windows); + } else { + if (!ignore_focus) { + /* Get the first tiling container in focus stack */ + TAILQ_FOREACH(loop, &(parent->focus_head), focused) { + if (loop->type == CT_FLOATING_CON) + continue; + current = loop; + break; + } } - } - /* Insert the container after the tiling container, if found */ - if (current) { - DLOG("Inserting con = %p after last focused tiling con %p\n", - con, current); - TAILQ_INSERT_AFTER(nodes_head, current, con, nodes); - } else TAILQ_INSERT_TAIL(nodes_head, con, nodes); + /* Insert the container after the tiling container, if found */ + if (current) { + DLOG("Inserting con = %p after last focused tiling con %p\n", + con, current); + TAILQ_INSERT_AFTER(nodes_head, current, con, nodes); + } else TAILQ_INSERT_TAIL(nodes_head, con, nodes); + } add_to_focus_head: /* We insert to the TAIL because con_focus() will correct this. diff --git a/testcases/t/32-move-workspace.t b/testcases/t/32-move-workspace.t index 09e90a8c..871be6da 100644 --- a/testcases/t/32-move-workspace.t +++ b/testcases/t/32-move-workspace.t @@ -3,7 +3,7 @@ # # Checks if the 'move workspace' command works correctly # -use i3test tests => 7; +use i3test tests => 11; use Time::HiRes qw(sleep); my $i3 = i3("/tmp/nestedcons"); @@ -38,4 +38,26 @@ is($focus->[0], $second, 'same container on different ws'); ($nodes, $focus) = get_ws_content($tmp); is($nodes->[0]->{focused}, 1, 'first container focused on first ws'); +################################################################### +# check if floating cons are moved to new workspaces properly +# (that is, if they are floating on the target ws, too) +################################################################### + +$tmp = get_unused_workspace(); +$tmp2 = get_unused_workspace(); +cmd "workspace $tmp"; + +cmd "open"; +cmd "mode toggle"; + +my $ws = get_ws($tmp); +is(@{$ws->{nodes}}, 0, 'no nodes on workspace'); +is(@{$ws->{floating_nodes}}, 1, 'one floating node on workspace'); + +cmd "move workspace $tmp2"; + +$ws = get_ws($tmp2); +is(@{$ws->{nodes}}, 0, 'no nodes on workspace'); +is(@{$ws->{floating_nodes}}, 1, 'one floating node on workspace'); + diag( "Testing i3, Perl $], $^X" ); From b0e41cb0a2d56f2fb41f6e6ab6faeb922882ee3e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 29 Nov 2010 21:46:00 +0100 Subject: [PATCH 324/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20attach=20floa?= =?UTF-8?q?ting=20clients=20to=20'nodes'=20when=20restoring=20(Thanks=20Ee?= =?UTF-8?q?lVex)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/load_layout.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/load_layout.c b/src/load_layout.c index 92270390..60a63313 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -19,15 +19,24 @@ static bool parsing_window_rect; struct Match *current_swallow; static int json_start_map(void *ctx) { - LOG("start of map\n"); + LOG("start of map, last_key = %s\n", last_key); if (parsing_swallows) { LOG("TODO: create new swallow\n"); current_swallow = smalloc(sizeof(Match)); match_init(current_swallow); TAILQ_INSERT_TAIL(&(json_node->swallow_head), current_swallow, matches); } else { - if (!parsing_rect && !parsing_window_rect) - json_node = con_new(json_node); + if (!parsing_rect && !parsing_window_rect) { + if (last_key && strcasecmp(last_key, "floating_nodes") == 0) { + Con *ws = con_get_workspace(json_node); + json_node = con_new(NULL); + json_node->parent = ws; + TAILQ_INSERT_TAIL(&(ws->floating_head), json_node, floating_windows); + TAILQ_INSERT_TAIL(&(ws->focus_head), json_node, focused); + } else { + json_node = con_new(json_node); + } + } } return 1; } From 371ec037b82b86df341fd4420e64e0a8368316ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Mon, 29 Nov 2010 00:11:30 -0200 Subject: [PATCH 325/867] Fix click to focus. --- src/click.c | 30 +++++++++--------------------- src/handlers.c | 3 +++ 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/click.c b/src/click.c index 69319005..5b7f0827 100644 --- a/src/click.c +++ b/src/click.c @@ -286,30 +286,18 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ return 1; } - if (con->layout == L_STACKED || con->layout == L_TABBED) { - DLOG("stacked! click is on %d, %d\n", event->event_x, event->event_y); - Con *child; - TAILQ_FOREACH(child, &(con->nodes_head), nodes) { - if (!rect_contains(child->deco_rect, event->event_x, event->event_y)) - continue; + DLOG("border click on non-floating container at %d, %d\n", event->event_x, event->event_y); + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + if (!rect_contains(child->deco_rect, event->event_x, event->event_y)) + continue; - con_focus(child); - break; - } - tree_render(); - return 1; + con_focus(child); + break; } -#if 0 - if (!floating_mod_on_tiled_client(conn, client, event)) { -#endif - xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); - xcb_flush(conn); -#if 0 - } -#endif - - //return 1; + tree_render(); + return 1; } /* click to focus */ diff --git a/src/handlers.c b/src/handlers.c index 78522ea0..d8e859b1 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -263,6 +263,9 @@ int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notif return 1; } + if (config.disable_focus_follows_mouse) + return 1; + if (con->layout != L_DEFAULT) return 1; From 529bdf833f0e393528c1ba391fc34fe52cd4fbcf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 29 Nov 2010 22:23:49 +0100 Subject: [PATCH 326/867] floating.c: remove obsolete code, fix indenting --- src/floating.c | 403 +++++++++++-------------------------------------- 1 file changed, 90 insertions(+), 313 deletions(-) diff --git a/src/floating.c b/src/floating.c index b1e23785..6506941a 100644 --- a/src/floating.c +++ b/src/floating.c @@ -115,7 +115,6 @@ void floating_disable(Con *con, bool automatic) { con_focus(con); } - /* * Toggles floating mode for the given container. * @@ -124,249 +123,27 @@ void floating_disable(Con *con, bool automatic) { * */ void toggle_floating_mode(Con *con, bool automatic) { - //i3Font *font = load_font(conn, config.font); + /* see if the client is already floating */ + if (con_is_floating(con)) { + LOG("already floating, re-setting to tiling\n"); - /* see if the client is already floating */ - if (con_is_floating(con)) { - LOG("already floating, re-setting to tiling\n"); + floating_disable(con, automatic); + return; + } - floating_disable(con, automatic); - return; - } - - floating_enable(con, automatic); - - -#if 0 - if (client->dock) { - DLOG("Not putting dock client into floating mode\n"); - return; - } - - if (con == NULL) { - DLOG("This client is already in floating (container == NULL), re-inserting\n"); - Client *next_tiling; - Workspace *ws = client->workspace; - SLIST_FOREACH(next_tiling, &(ws->focus_stack), focus_clients) - if (!client_is_floating(next_tiling)) - break; - /* If there are no tiling clients on this workspace, there can only be one - * container: the first one */ - if (next_tiling == TAILQ_END(&(ws->focus_stack))) - con = ws->table[0][0]; - else con = next_tiling->container; - - /* Remove the client from the list of floating clients */ - TAILQ_REMOVE(&(ws->floating_clients), client, floating_clients); - - DLOG("destination container = %p\n", con); - Client *old_focused = con->currently_focused; - /* Preserve position/size */ - memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect)); - - client->floating = FLOATING_USER_OFF; - client->container = con; - - if (old_focused != NULL && !old_focused->dock) - CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients); - else CIRCLEQ_INSERT_TAIL(&(con->clients), client, clients); - - DLOG("Re-inserted the window.\n"); - con->currently_focused = client; - - client_set_below_floating(conn, client); - - render_container(conn, con); - xcb_flush(conn); - - return; - } - - DLOG("Entering floating for client %08x\n", client->child); - - /* Remove the client of its container */ - client_remove_from_container(conn, client, con, false); - client->container = NULL; - - /* Add the client to the list of floating clients for its workspace */ - TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients); - - if (con->currently_focused == client) { - DLOG("Need to re-adjust currently_focused\n"); - /* Get the next client in the focus stack for this particular container */ - con->currently_focused = get_last_focused_client(conn, con, NULL); - } - - if (automatic) - client->floating = FLOATING_AUTO_ON; - else client->floating = FLOATING_USER_ON; - - /* Initialize the floating position from the position in tiling mode, if this - * client never was floating (x == -1) */ - if (client->floating_rect.x == -1) { - /* Copy over the position */ - client->floating_rect.x = client->rect.x; - client->floating_rect.y = client->rect.y; - - /* Copy size the other direction */ - client->child_rect.width = client->floating_rect.width; - client->child_rect.height = client->floating_rect.height; - - client->rect.width = client->child_rect.width + 2 + 2; - client->rect.height = client->child_rect.height + (font->height + 2 + 2) + 2; - - DLOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, - client->floating_rect.width, client->floating_rect.height); - } else { - /* If the client was already in floating before we restore the old position / size */ - DLOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, - client->floating_rect.width, client->floating_rect.height); - memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect)); - } - - /* Raise the client */ - xcb_raise_window(conn, client->frame); - reposition_client(conn, client); - resize_client(conn, client); - /* redecorate_window flushes */ - redecorate_window(conn, client); - - /* Re-render the tiling layout of this container */ - render_container(conn, con); - xcb_flush(conn); -#endif + floating_enable(con, automatic); } -#if 0 -/* - * Removes the floating client from its workspace and attaches it to the new workspace. - * This is centralized here because it may happen if you move it via keyboard and - * if you move it using your mouse. - * - */ -void floating_assign_to_workspace(Client *client, Workspace *new_workspace) { - /* Remove from focus stack and list of floating clients */ - SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); - TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients); - - if (client->workspace->fullscreen_client == client) - client->workspace->fullscreen_client = NULL; - - /* Insert into destination focus stack and list of floating clients */ - client->workspace = new_workspace; - SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients); - TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients); - if (client->fullscreen) - client->workspace->fullscreen_client = client; -} - -/* - * This is an ugly data structure which we need because there is no standard - * way of having nested functions (only available as a gcc extension at the - * moment, clang doesn’t support it) or blocks (only available as a clang - * extension and only on Mac OS X systems at the moment). - * - */ -struct resize_callback_params { - border_t border; - xcb_button_press_event_t *event; -}; - -DRAGGING_CB(resize_callback) { - struct resize_callback_params *params = extra; - xcb_button_press_event_t *event = params->event; - switch (params->border) { - case BORDER_RIGHT: { - int new_width = old_rect->width + (new_x - event->root_x); - if ((new_width < 0) || - (new_width < client_min_width(client) && client->rect.width >= new_width)) - return; - client->rect.width = new_width; - break; - } - - case BORDER_BOTTOM: { - int new_height = old_rect->height + (new_y - event->root_y); - if ((new_height < 0) || - (new_height < client_min_height(client) && client->rect.height >= new_height)) - return; - client->rect.height = old_rect->height + (new_y - event->root_y); - break; - } - - case BORDER_TOP: { - int new_height = old_rect->height + (event->root_y - new_y); - if ((new_height < 0) || - (new_height < client_min_height(client) && client->rect.height >= new_height)) - return; - - client->rect.y = old_rect->y + (new_y - event->root_y); - client->rect.height = new_height; - break; - } - - case BORDER_LEFT: { - int new_width = old_rect->width + (event->root_x - new_x); - if ((new_width < 0) || - (new_width < client_min_width(client) && client->rect.width >= new_width)) - return; - client->rect.x = old_rect->x + (new_x - event->root_x); - client->rect.width = new_width; - break; - } - } - - /* Push the new position/size to X11 */ - reposition_client(conn, client); - resize_client(conn, client); - xcb_flush(conn); -} - - -/* - * Called whenever the user clicks on a border (not the titlebar!) of a floating window. - * Determines on which border the user clicked and launches the drag_pointer function - * with the resize_callback. - * - */ -int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { - DLOG("floating border click\n"); - - border_t border; - - if (event->event_y < 2) - border = BORDER_TOP; - else if (event->event_y >= (client->rect.height - 2)) - border = BORDER_BOTTOM; - else if (event->event_x <= 2) - border = BORDER_LEFT; - else if (event->event_x >= (client->rect.width - 2)) - border = BORDER_RIGHT; - else { - DLOG("Not on any border, not doing anything.\n"); - return 1; - } - - DLOG("border = %d\n", border); - - struct resize_callback_params params = { border, event }; - - drag_pointer(conn, client, event, XCB_NONE, border, resize_callback, ¶ms); - - return 1; -} - -#endif DRAGGING_CB(drag_window_callback) { - struct xcb_button_press_event_t *event = extra; + struct xcb_button_press_event_t *event = extra; - /* Reposition the client correctly while moving */ - con->rect.x = old_rect->x + (new_x - event->root_x); - con->rect.y = old_rect->y + (new_y - event->root_y); - /* TODO: don’t re-render the whole tree just because we change - * coordinates of a floating window */ - tree_render(); - x_push_changes(croot); + /* Reposition the client correctly while moving */ + con->rect.x = old_rect->x + (new_x - event->root_x); + con->rect.y = old_rect->y + (new_y - event->root_y); + /* TODO: don’t re-render the whole tree just because we change + * coordinates of a floating window */ + tree_render(); + x_push_changes(croot); } /* @@ -375,10 +152,10 @@ DRAGGING_CB(drag_window_callback) { * */ void floating_drag_window(Con *con, xcb_button_press_event_t *event) { - DLOG("floating_drag_window\n"); + DLOG("floating_drag_window\n"); - drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event); - tree_render(); + drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event); + tree_render(); } /* @@ -409,11 +186,11 @@ DRAGGING_CB(resize_window_callback) { /* First guess: We resize by exactly the amount the mouse moved, * taking into account in which corner the client was grabbed */ if (corner & BORDER_LEFT) - dest_width = old_rect->width - (new_x - event->root_x); + dest_width = old_rect->width - (new_x - event->root_x); else dest_width = old_rect->width + (new_x - event->root_x); if (corner & BORDER_TOP) - dest_height = old_rect->height - (new_y - event->root_y); + dest_height = old_rect->height - (new_y - event->root_y); else dest_height = old_rect->height + (new_y - event->root_y); /* TODO: minimum window size */ @@ -425,17 +202,17 @@ DRAGGING_CB(resize_window_callback) { /* User wants to keep proportions, so we may have to adjust our values */ if (params->proportional) { - dest_width = max(dest_width, (int) (dest_height * ratio)); - dest_height = max(dest_height, (int) (dest_width / ratio)); + dest_width = max(dest_width, (int) (dest_height * ratio)); + dest_height = max(dest_height, (int) (dest_width / ratio)); } /* If not the lower right corner is grabbed, we must also reposition * the client by exactly the amount we resized it */ if (corner & BORDER_LEFT) - dest_x = old_rect->x + (old_rect->width - dest_width); + dest_x = old_rect->x + (old_rect->width - dest_width); if (corner & BORDER_TOP) - dest_y = old_rect->y + (old_rect->height - dest_height); + dest_y = old_rect->y + (old_rect->height - dest_height); con->rect = (Rect) { dest_x, dest_y, dest_width, dest_height }; @@ -460,11 +237,11 @@ void floating_resize_window(Con *con, bool proportional, border_t corner = 0; if (event->event_x <= (con->rect.width / 2)) - corner |= BORDER_LEFT; + corner |= BORDER_LEFT; else corner |= BORDER_RIGHT; if (event->event_y <= (con->rect.height / 2)) - corner |= BORDER_TOP; + corner |= BORDER_TOP; else corner |= BORDER_RIGHT; struct resize_window_callback_params params = { corner, proportional, event }; @@ -483,81 +260,81 @@ void floating_resize_window(Con *con, bool proportional, void drag_pointer(Con *con, xcb_button_press_event_t *event, xcb_window_t confine_to, border_t border, callback_t callback, void *extra) { - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; - uint32_t new_x, new_y; - Rect old_rect; - if (con != NULL) - memcpy(&old_rect, &(con->rect), sizeof(Rect)); + xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; + uint32_t new_x, new_y; + Rect old_rect; + if (con != NULL) + memcpy(&old_rect, &(con->rect), sizeof(Rect)); - /* Grab the pointer */ - /* TODO: returncode */ - xcb_grab_pointer(conn, - false, /* get all pointer events specified by the following mask */ - root, /* grab the root window */ - XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */ - XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */ - XCB_GRAB_MODE_ASYNC, /* keyboard mode */ - confine_to, /* confine_to = in which window should the cursor stay */ - XCB_NONE, /* don’t display a special cursor */ - XCB_CURRENT_TIME); + /* Grab the pointer */ + /* TODO: returncode */ + xcb_grab_pointer(conn, + false, /* get all pointer events specified by the following mask */ + root, /* grab the root window */ + XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */ + XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */ + XCB_GRAB_MODE_ASYNC, /* keyboard mode */ + confine_to, /* confine_to = in which window should the cursor stay */ + XCB_NONE, /* don’t display a special cursor */ + XCB_CURRENT_TIME); - /* Go into our own event loop */ - xcb_flush(conn); + /* Go into our own event loop */ + xcb_flush(conn); - xcb_generic_event_t *inside_event, *last_motion_notify = NULL; - /* I’ve always wanted to have my own eventhandler… */ - while ((inside_event = xcb_wait_for_event(conn))) { - /* We now handle all events we can get using xcb_poll_for_event */ - do { - /* Same as get_event_handler in xcb */ - int nr = inside_event->response_type; - if (nr == 0) { - /* An error occured */ - //handle_event(NULL, conn, inside_event); - free(inside_event); - continue; - } - assert(nr < 256); - nr &= XCB_EVENT_RESPONSE_TYPE_MASK; - assert(nr >= 2); + xcb_generic_event_t *inside_event, *last_motion_notify = NULL; + /* I’ve always wanted to have my own eventhandler… */ + while ((inside_event = xcb_wait_for_event(conn))) { + /* We now handle all events we can get using xcb_poll_for_event */ + do { + /* Same as get_event_handler in xcb */ + int nr = inside_event->response_type; + if (nr == 0) { + /* An error occured */ + //handle_event(NULL, conn, inside_event); + free(inside_event); + continue; + } + assert(nr < 256); + nr &= XCB_EVENT_RESPONSE_TYPE_MASK; + assert(nr >= 2); - switch (nr) { - case XCB_BUTTON_RELEASE: - goto done; + switch (nr) { + case XCB_BUTTON_RELEASE: + goto done; - case XCB_MOTION_NOTIFY: - /* motion_notify events are saved for later */ - FREE(last_motion_notify); - last_motion_notify = inside_event; - break; + case XCB_MOTION_NOTIFY: + /* motion_notify events are saved for later */ + FREE(last_motion_notify); + last_motion_notify = inside_event; + break; - case XCB_UNMAP_NOTIFY: - DLOG("Unmap-notify, aborting\n"); - xcb_event_handle(&evenths, inside_event); - goto done; + case XCB_UNMAP_NOTIFY: + DLOG("Unmap-notify, aborting\n"); + xcb_event_handle(&evenths, inside_event); + goto done; - default: - DLOG("Passing to original handler\n"); - /* Use original handler */ - xcb_event_handle(&evenths, inside_event); - break; - } - if (last_motion_notify != inside_event) - free(inside_event); - } while ((inside_event = xcb_poll_for_event(conn)) != NULL); + default: + DLOG("Passing to original handler\n"); + /* Use original handler */ + xcb_event_handle(&evenths, inside_event); + break; + } + if (last_motion_notify != inside_event) + free(inside_event); + } while ((inside_event = xcb_poll_for_event(conn)) != NULL); - if (last_motion_notify == NULL) - continue; + if (last_motion_notify == NULL) + continue; - new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x; - new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y; + new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x; + new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y; - callback(con, &old_rect, new_x, new_y, extra); - FREE(last_motion_notify); - } + callback(con, &old_rect, new_x, new_y, extra); + FREE(last_motion_notify); + } done: - xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); - xcb_flush(conn); + xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); + xcb_flush(conn); } #if 0 From 38b231b848d349341e6aabf8d63d40d22ac0924c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 29 Nov 2010 22:25:55 +0100 Subject: [PATCH 327/867] handlers.c: remove obsolete code --- src/handlers.c | 60 -------------------------------------------------- 1 file changed, 60 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index d8e859b1..ed59f528 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -28,30 +28,6 @@ void add_ignore_event(const int sequence) { SLIST_INSERT_HEAD(&ignore_events, event, ignore_events); } -/* - * Unignores the given sequence. Called when unmap events (generated by - * reparenting) should be ignored and the unmap event actually happens, in - * order to not ignore too many unmap events (leading to ghost window - * decorations). - * - */ -static void unignore_event(const int sequence) { - struct Ignore_Event *event; - for (event = SLIST_FIRST(&ignore_events); - event != SLIST_END(&ignore_events); - event = SLIST_NEXT(event, ignore_events)) { - if (event->sequence != sequence) - continue; - - DLOG("Unignoring sequence number %d\n", sequence); - struct Ignore_Event *save = event; - event = SLIST_NEXT(event, ignore_events); - SLIST_REMOVE(&ignore_events, save, Ignore_Event, ignore_events); - free(save); - break; - } -} - /* * Checks if the given sequence is ignored and returns true if so. * @@ -658,42 +634,6 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * xcb_flush(conn); return 1; - -#if 0 - else { - uint32_t background_color; - if (client->urgent) - background_color = config.client.urgent.background; - /* Distinguish if the window is currently focused… */ - else if (CUR_CELL != NULL && CUR_CELL->currently_focused == client) - background_color = config.client.focused.background; - /* …or if it is the focused window in a not focused container */ - else background_color = config.client.focused_inactive.background; - - /* Set foreground color to current focused color, line width to 2 */ - uint32_t values[] = {background_color, 2}; - xcb_change_gc(conn, client->titlegc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); - - /* Draw the border, the ±1 is for line width = 2 */ - xcb_point_t points[] = {{1, 0}, /* left upper edge */ - {1, client->rect.height-1}, /* left bottom edge */ - {client->rect.width-1, client->rect.height-1}, /* right bottom edge */ - {client->rect.width-1, 0}}; /* right upper edge */ - xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, client->frame, client->titlegc, 4, points); - - /* Draw a black background */ - xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); - if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) { - xcb_rectangle_t crect = {1, 0, client->rect.width - (1 + 1), client->rect.height - 1}; - xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); - } else { - xcb_rectangle_t crect = {2, 0, client->rect.width - (2 + 2), client->rect.height - 2}; - xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); - } - } - xcb_flush(conn); - return 1; -#endif } /* From 5625a2f17f14e0a789334e4bb2b1bb8b2714336f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 29 Nov 2010 22:28:23 +0100 Subject: [PATCH 328/867] log.c: fix indenting --- src/log.c | 92 +++++++++++++++++++++++++++---------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/src/log.c b/src/log.c index a899efcb..0371e9be 100644 --- a/src/log.c +++ b/src/log.c @@ -1,9 +1,9 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -24,37 +24,37 @@ static uint64_t loglevel = 0; static bool verbose = true; -/** +/* * Set verbosity of i3. If verbose is set to true, informative messages will * be printed to stdout. If verbose is set to false, only errors will be * printed. * */ void set_verbosity(bool _verbose) { - verbose = _verbose; + verbose = _verbose; } -/** +/* * Enables the given loglevel. * */ void add_loglevel(const char *level) { - /* Handle the special loglevel "all" */ - if (strcasecmp(level, "all") == 0) { - loglevel = UINT64_MAX; - return; - } + /* Handle the special loglevel "all" */ + if (strcasecmp(level, "all") == 0) { + loglevel = UINT64_MAX; + return; + } - for (int i = 0; i < sizeof(loglevels) / sizeof(char*); i++) { - if (strcasecmp(loglevels[i], level) != 0) - continue; + for (int i = 0; i < sizeof(loglevels) / sizeof(char*); i++) { + if (strcasecmp(loglevels[i], level) != 0) + continue; - /* The position in the array (plus one) is the amount of times - * which we need to shift 1 to the left to get our bitmask for - * the specific loglevel. */ - loglevel |= (1 << (i+1)); - break; - } + /* The position in the array (plus one) is the amount of times + * which we need to shift 1 to the left to get our bitmask for + * the specific loglevel. */ + loglevel |= (1 << (i+1)); + break; + } } /* @@ -63,44 +63,44 @@ void add_loglevel(const char *level) { * */ void vlog(char *fmt, va_list args) { - char timebuf[64]; + char timebuf[64]; - /* Get current time */ - time_t t = time(NULL); - /* Convert time to local time (determined by the locale) */ - struct tm *tmp = localtime(&t); - /* Generate time prefix */ - strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp); - printf("%s", timebuf); - vprintf(fmt, args); + /* Get current time */ + time_t t = time(NULL); + /* Convert time to local time (determined by the locale) */ + struct tm *tmp = localtime(&t); + /* Generate time prefix */ + strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp); + printf("%s", timebuf); + vprintf(fmt, args); } -/** +/* * Logs the given message to stdout while prefixing the current time to it, * but only if verbose mode is activated. * */ void verboselog(char *fmt, ...) { - va_list args; + va_list args; - if (!verbose) - return; + if (!verbose) + return; - va_start(args, fmt); - vlog(fmt, args); - va_end(args); + va_start(args, fmt); + vlog(fmt, args); + va_end(args); } -/** +/* * Logs the given message to stdout while prefixing the current time to it. * */ void errorlog(char *fmt, ...) { - va_list args; + va_list args; - va_start(args, fmt); - vlog(fmt, args); - va_end(args); + va_start(args, fmt); + vlog(fmt, args); + va_end(args); } /* @@ -110,12 +110,12 @@ void errorlog(char *fmt, ...) { * */ void debuglog(uint64_t lev, char *fmt, ...) { - va_list args; + va_list args; - if ((loglevel & lev) == 0) - return; + if ((loglevel & lev) == 0) + return; - va_start(args, fmt); - vlog(fmt, args); - va_end(args); + va_start(args, fmt); + vlog(fmt, args); + va_end(args); } From 1a40641462ae29299f5f09536febe4f4a9304933 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 29 Nov 2010 22:32:19 +0100 Subject: [PATCH 329/867] workspace.{c,h}: remove obsolete code --- include/workspace.h | 14 ------ src/workspace.c | 119 -------------------------------------------- 2 files changed, 133 deletions(-) diff --git a/include/workspace.h b/include/workspace.h index a2c1836e..f65f1494 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -99,18 +99,4 @@ void workspace_map_clients(xcb_connection_t *conn, Workspace *ws); */ void workspace_update_urgent_flag(Con *ws); -#if 0 -/* - * Returns the width of the workspace. - * - */ -int workspace_width(Workspace *ws); - -/* - * Returns the effective height of the workspace (without the internal bar and - * without dock clients). - * - */ -int workspace_height(Workspace *ws); -#endif #endif diff --git a/src/workspace.c b/src/workspace.c index a1d3659d..9ce28e52 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -450,93 +450,6 @@ Workspace *get_first_workspace_for_output(Output *output) { return result; } -/* - * Maps all clients (and stack windows) of the given workspace. - * - */ -void workspace_map_clients(xcb_connection_t *conn, Workspace *ws) { - Client *client; - - ignore_enter_notify_forall(conn, ws, true); - - /* Map all clients on the new workspace */ - FOR_TABLE(ws) - CIRCLEQ_FOREACH(client, &(ws->table[cols][rows]->clients), clients) - client_map(conn, client); - - /* Map all floating clients */ - if (!ws->floating_hidden) - TAILQ_FOREACH(client, &(ws->floating_clients), floating_clients) - client_map(conn, client); - - /* Map all stack windows, if any */ - struct Stack_Window *stack_win; - SLIST_FOREACH(stack_win, &stack_wins, stack_windows) - if (stack_win->container->workspace == ws && stack_win->rect.height > 0) - xcb_map_window(conn, stack_win->window); - - ignore_enter_notify_forall(conn, ws, false); -} - -/* - * Unmaps all clients (and stack windows) of the given workspace. - * - * This needs to be called separately when temporarily rendering - * a workspace which is not the active workspace to force - * reconfiguration of all clients, like in src/xinerama.c when - * re-assigning a workspace to another screen. - * - */ -void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) { - Client *client; - struct Stack_Window *stack_win; - - /* Ignore notify events because they would cause focus to be changed */ - ignore_enter_notify_forall(conn, u_ws, true); - - /* Unmap all clients of the given workspace */ - int unmapped_clients = 0; - FOR_TABLE(u_ws) - CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) { - DLOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child); - client_unmap(conn, client); - unmapped_clients++; - } - - /* To find floating clients, we traverse the focus stack */ - SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) { - if (!client_is_floating(client)) - continue; - - DLOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child); - - client_unmap(conn, client); - unmapped_clients++; - } - - /* If we did not unmap any clients, the workspace is empty and we can destroy it, at least - * if it is not the current workspace. */ - if (unmapped_clients == 0 && u_ws != c_ws) { - /* Re-assign the workspace of all dock clients which use this workspace */ - Client *dock; - DLOG("workspace %p is empty\n", u_ws); - SLIST_FOREACH(dock, &(u_ws->output->dock_clients), dock_clients) { - if (dock->workspace != u_ws) - continue; - - DLOG("Re-assigning dock client to c_ws (%p)\n", c_ws); - dock->workspace = c_ws; - } - u_ws->output = NULL; - } - - /* Unmap the stack windows on the given workspace, if any */ - SLIST_FOREACH(stack_win, &stack_wins, stack_windows) - if (stack_win->container->workspace == u_ws) - xcb_unmap_window(conn, stack_win->window); - - ignore_enter_notify_forall(conn, u_ws, false); -} #endif static bool get_urgency_flag(Con *con) { @@ -565,35 +478,3 @@ void workspace_update_urgent_flag(Con *ws) { if (old_flag != ws->urgent) ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}"); } - -#if 0 - -/* - * Returns the width of the workspace. - * - */ -int workspace_width(Workspace *ws) { - return ws->rect.width; -} - -/* - * Returns the effective height of the workspace (without the internal bar and - * without dock clients). - * - */ -int workspace_height(Workspace *ws) { - int height = ws->rect.height; - i3Font *font = load_font(global_conn, config.font); - - /* Reserve space for dock clients */ - Client *client; - SLIST_FOREACH(client, &(ws->output->dock_clients), dock_clients) - height -= client->desired_height; - - /* Space for the internal bar */ - if (!config.disable_workspace_bar) - height -= (font->height + 6); - - return height; -} -#endif From a05b1857016b9b80ed10fd3858de34b9a36db917 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 29 Nov 2010 22:35:07 +0100 Subject: [PATCH 330/867] xcb.c: fix indenting --- src/xcb.c | 284 +++++++++++++++++++++++++++--------------------------- 1 file changed, 142 insertions(+), 142 deletions(-) diff --git a/src/xcb.c b/src/xcb.c index 765c1894..4b400ae5 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * @@ -22,37 +22,37 @@ unsigned int xcb_numlock_mask; * */ i3Font *load_font(xcb_connection_t *conn, const char *pattern) { - /* Check if we got the font cached */ - i3Font *font; - TAILQ_FOREACH(font, &cached_fonts, fonts) - if (strcmp(font->pattern, pattern) == 0) - return font; + /* Check if we got the font cached */ + i3Font *font; + TAILQ_FOREACH(font, &cached_fonts, fonts) + if (strcmp(font->pattern, pattern) == 0) + return font; - i3Font *new = smalloc(sizeof(i3Font)); - xcb_void_cookie_t font_cookie; - xcb_list_fonts_with_info_cookie_t info_cookie; + i3Font *new = smalloc(sizeof(i3Font)); + xcb_void_cookie_t font_cookie; + xcb_list_fonts_with_info_cookie_t info_cookie; - /* Send all our requests first */ - new->id = xcb_generate_id(conn); - font_cookie = xcb_open_font_checked(conn, new->id, strlen(pattern), pattern); - info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern); + /* Send all our requests first */ + new->id = xcb_generate_id(conn); + font_cookie = xcb_open_font_checked(conn, new->id, strlen(pattern), pattern); + info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern); - check_error(conn, font_cookie, "Could not open font"); + check_error(conn, font_cookie, "Could not open font"); - /* Get information (height/name) for this font */ - xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL); - exit_if_null(reply, "Could not load font \"%s\"\n", pattern); + /* Get information (height/name) for this font */ + xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL); + exit_if_null(reply, "Could not load font \"%s\"\n", pattern); - if (asprintf(&(new->name), "%.*s", xcb_list_fonts_with_info_name_length(reply), - xcb_list_fonts_with_info_name(reply)) == -1) - die("asprintf() failed\n"); - new->pattern = sstrdup(pattern); - new->height = reply->font_ascent + reply->font_descent; + if (asprintf(&(new->name), "%.*s", xcb_list_fonts_with_info_name_length(reply), + xcb_list_fonts_with_info_name(reply)) == -1) + die("asprintf() failed\n"); + new->pattern = sstrdup(pattern); + new->height = reply->font_ascent + reply->font_descent; - /* Insert into cache */ - TAILQ_INSERT_TAIL(&cached_fonts, new, fonts); + /* Insert into cache */ + TAILQ_INSERT_TAIL(&cached_fonts, new, fonts); - return new; + return new; } /* @@ -65,14 +65,14 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern) { * */ uint32_t get_colorpixel(char *hex) { - char strgroups[3][3] = {{hex[1], hex[2], '\0'}, - {hex[3], hex[4], '\0'}, - {hex[5], hex[6], '\0'}}; - uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)), - (strtol(strgroups[1], NULL, 16)), - (strtol(strgroups[2], NULL, 16))}; + char strgroups[3][3] = {{hex[1], hex[2], '\0'}, + {hex[3], hex[4], '\0'}, + {hex[5], hex[6], '\0'}}; + uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)), + (strtol(strgroups[1], NULL, 16)), + (strtol(strgroups[2], NULL, 16))}; - return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2]; + return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2]; } /* @@ -126,7 +126,7 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl * */ void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) { - xcb_change_gc(conn, gc, mask, &value); + xcb_change_gc(conn, gc, mask, &value); } /* @@ -135,9 +135,9 @@ void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t ma */ void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t to_x, uint32_t to_y) { - xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, colorpixel); - xcb_point_t points[] = {{x, y}, {to_x, to_y}}; - xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, drawable, gc, 2, points); + xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, colorpixel); + xcb_point_t points[] = {{x, y}, {to_x, to_y}}; + xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, drawable, gc, 2, points); } /* @@ -146,9 +146,9 @@ void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext */ void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { - xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, colorpixel); - xcb_rectangle_t rect = {x, y, width, height}; - xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); + xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, colorpixel); + xcb_rectangle_t rect = {x, y, width, height}; + xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); } /* @@ -158,23 +158,23 @@ void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext * */ void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window) { - xcb_configure_notify_event_t generated_event; + xcb_configure_notify_event_t generated_event; - generated_event.event = window; - generated_event.window = window; - generated_event.response_type = XCB_CONFIGURE_NOTIFY; + generated_event.event = window; + generated_event.window = window; + generated_event.response_type = XCB_CONFIGURE_NOTIFY; - generated_event.x = r.x; - generated_event.y = r.y; - generated_event.width = r.width; - generated_event.height = r.height; + generated_event.x = r.x; + generated_event.y = r.y; + generated_event.width = r.width; + generated_event.height = r.height; - generated_event.border_width = 0; - generated_event.above_sibling = XCB_NONE; - generated_event.override_redirect = false; + generated_event.border_width = 0; + generated_event.above_sibling = XCB_NONE; + generated_event.override_redirect = false; - xcb_send_event(conn, false, window, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&generated_event); - xcb_flush(conn); + xcb_send_event(conn, false, window, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&generated_event); + xcb_flush(conn); } /* @@ -183,16 +183,16 @@ void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window) * */ void fake_absolute_configure_notify(Con *con) { - Rect absolute; - if (con->window == NULL) - return; + Rect absolute; + if (con->window == NULL) + return; - absolute.x = con->rect.x + con->window_rect.x; - absolute.y = con->rect.y + con->window_rect.y; - absolute.width = con->window_rect.width; - absolute.height = con->window_rect.height; + absolute.x = con->rect.x + con->window_rect.x; + absolute.y = con->rect.y + con->window_rect.y; + absolute.width = con->window_rect.width; + absolute.height = con->window_rect.height; - fake_configure_notify(conn, absolute, con->window->id); + fake_configure_notify(conn, absolute, con->window->id); } /* @@ -200,53 +200,53 @@ void fake_absolute_configure_notify(Con *con) { * */ void xcb_get_numlock_mask(xcb_connection_t *conn) { - xcb_key_symbols_t *keysyms; - xcb_get_modifier_mapping_cookie_t cookie; - xcb_get_modifier_mapping_reply_t *reply; - xcb_keycode_t *modmap; - int mask, i; - const int masks[8] = { XCB_MOD_MASK_SHIFT, - XCB_MOD_MASK_LOCK, - XCB_MOD_MASK_CONTROL, - XCB_MOD_MASK_1, - XCB_MOD_MASK_2, - XCB_MOD_MASK_3, - XCB_MOD_MASK_4, - XCB_MOD_MASK_5 }; + xcb_key_symbols_t *keysyms; + xcb_get_modifier_mapping_cookie_t cookie; + xcb_get_modifier_mapping_reply_t *reply; + xcb_keycode_t *modmap; + int mask, i; + const int masks[8] = { XCB_MOD_MASK_SHIFT, + XCB_MOD_MASK_LOCK, + XCB_MOD_MASK_CONTROL, + XCB_MOD_MASK_1, + XCB_MOD_MASK_2, + XCB_MOD_MASK_3, + XCB_MOD_MASK_4, + XCB_MOD_MASK_5 }; - /* Request the modifier map */ - cookie = xcb_get_modifier_mapping_unchecked(conn); + /* Request the modifier map */ + cookie = xcb_get_modifier_mapping_unchecked(conn); - /* Get the keysymbols */ - keysyms = xcb_key_symbols_alloc(conn); + /* Get the keysymbols */ + keysyms = xcb_key_symbols_alloc(conn); - if ((reply = xcb_get_modifier_mapping_reply(conn, cookie, NULL)) == NULL) { - xcb_key_symbols_free(keysyms); - return; - } + if ((reply = xcb_get_modifier_mapping_reply(conn, cookie, NULL)) == NULL) { + xcb_key_symbols_free(keysyms); + return; + } - modmap = xcb_get_modifier_mapping_keycodes(reply); + modmap = xcb_get_modifier_mapping_keycodes(reply); - /* Get the keycode for numlock */ + /* Get the keycode for numlock */ #ifdef OLD_XCB_KEYSYMS_API - xcb_keycode_t numlock = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK); + xcb_keycode_t numlock = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK); #else - /* For now, we only use the first keysymbol. */ - xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK); - if (numlock_syms == NULL) - return; - xcb_keycode_t numlock = *numlock_syms; - free(numlock_syms); + /* For now, we only use the first keysymbol. */ + xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK); + if (numlock_syms == NULL) + return; + xcb_keycode_t numlock = *numlock_syms; + free(numlock_syms); #endif - /* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */ - for (mask = 0; mask < 8; mask++) - for (i = 0; i < reply->keycodes_per_modifier; i++) - if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock) - xcb_numlock_mask = masks[mask]; + /* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */ + for (mask = 0; mask < 8; mask++) + for (i = 0; i < reply->keycodes_per_modifier; i++) + if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock) + xcb_numlock_mask = masks[mask]; - xcb_key_symbols_free(keysyms); - free(reply); + xcb_key_symbols_free(keysyms); + free(reply); } /* @@ -254,8 +254,8 @@ void xcb_get_numlock_mask(xcb_connection_t *conn) { * */ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) { - uint32_t values[] = { XCB_STACK_MODE_ABOVE }; - xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values); + uint32_t values[] = { XCB_STACK_MODE_ABOVE }; + xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values); } /* @@ -266,28 +266,28 @@ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) { * */ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) { - DLOG("preparing pixmap\n"); + DLOG("preparing pixmap\n"); - /* If the Rect did not change, the pixmap does not need to be recreated */ - if (memcmp(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)) == 0) - return; + /* If the Rect did not change, the pixmap does not need to be recreated */ + if (memcmp(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)) == 0) + return; - memcpy(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)); + memcpy(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)); - if (pixmap->id == 0 || pixmap->gc == 0) { - DLOG("Creating new pixmap...\n"); - pixmap->id = xcb_generate_id(conn); - pixmap->gc = xcb_generate_id(conn); - } else { - DLOG("Re-creating this pixmap...\n"); - xcb_free_gc(conn, pixmap->gc); - xcb_free_pixmap(conn, pixmap->id); - } + if (pixmap->id == 0 || pixmap->gc == 0) { + DLOG("Creating new pixmap...\n"); + pixmap->id = xcb_generate_id(conn); + pixmap->gc = xcb_generate_id(conn); + } else { + DLOG("Re-creating this pixmap...\n"); + xcb_free_gc(conn, pixmap->gc); + xcb_free_pixmap(conn, pixmap->id); + } - xcb_create_pixmap(conn, root_depth, pixmap->id, - pixmap->referred_drawable, pixmap->rect.width, pixmap->rect.height); + xcb_create_pixmap(conn, root_depth, pixmap->id, + pixmap->referred_drawable, pixmap->rect.width, pixmap->rect.height); - xcb_create_gc(conn, pixmap->gc, pixmap->id, 0, 0); + xcb_create_gc(conn, pixmap->gc, pixmap->id, 0, 0); } /* @@ -296,26 +296,26 @@ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) * */ int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *text, int length) { - i3Font *font = load_font(conn, font_pattern); + i3Font *font = load_font(conn, font_pattern); - xcb_query_text_extents_cookie_t cookie; - xcb_query_text_extents_reply_t *reply; - xcb_generic_error_t *error; - int width; + xcb_query_text_extents_cookie_t cookie; + xcb_query_text_extents_reply_t *reply; + xcb_generic_error_t *error; + int width; - cookie = xcb_query_text_extents(conn, font->id, length, (xcb_char2b_t*)text); - if ((reply = xcb_query_text_extents_reply(conn, cookie, &error)) == NULL) { - ELOG("Could not get text extents (X error code %d)\n", - error->error_code); - /* We return the rather safe guess of 7 pixels, because a - * rendering error is better than a crash. Plus, the user will - * see the error in his log. */ - return 7; - } + cookie = xcb_query_text_extents(conn, font->id, length, (xcb_char2b_t*)text); + if ((reply = xcb_query_text_extents_reply(conn, cookie, &error)) == NULL) { + ELOG("Could not get text extents (X error code %d)\n", + error->error_code); + /* We return the rather safe guess of 7 pixels, because a + * rendering error is better than a crash. Plus, the user will + * see the error in his log. */ + return 7; + } - width = reply->overall_width; - free(reply); - return width; + width = reply->overall_width; + free(reply); + return width; } /* @@ -323,15 +323,15 @@ int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *t * */ void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r) { - xcb_void_cookie_t cookie; - cookie = xcb_configure_window(conn, window, - XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT, - &(r.x)); - /* ignore events which are generated because we configured a window */ - add_ignore_event(cookie.sequence); + xcb_void_cookie_t cookie; + cookie = xcb_configure_window(conn, window, + XCB_CONFIG_WINDOW_X | + XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT, + &(r.x)); + /* ignore events which are generated because we configured a window */ + add_ignore_event(cookie.sequence); } /* From 3412e12602d09982c36138591918c2dd3fb9741a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 29 Nov 2010 22:35:46 +0100 Subject: [PATCH 331/867] xinerama.c: fix indention --- src/xinerama.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/xinerama.c b/src/xinerama.c index bf92a636..369e8ab7 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -26,8 +26,8 @@ static int num_screens; static Output *get_screen_at(int x, int y) { Output *output; TAILQ_FOREACH(output, &outputs, outputs) - if (output->rect.x == x && output->rect.y == y) - return output; + if (output->rect.x == x && output->rect.y == y) + return output; return NULL; } @@ -100,10 +100,10 @@ void xinerama_init() { reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL); if (reply == NULL || !reply->state) { - DLOG("Xinerama is not active (in your X-Server), disabling.\n"); - disable_randr(conn); + DLOG("Xinerama is not active (in your X-Server), disabling.\n"); + disable_randr(conn); } else - query_screens(conn); + query_screens(conn); FREE(reply); } From d046fa446dbc641216b0481e9daa93d46d585e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Mon, 29 Nov 2010 20:11:18 -0200 Subject: [PATCH 332/867] Fix possible rounding errors. --- src/render.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/render.c b/src/render.c index 7551e0fa..f4d1e830 100644 --- a/src/render.c +++ b/src/render.c @@ -108,28 +108,40 @@ void render_con(Con *con, bool render_fullscreen) { i3Font *font = load_font(conn, config.font); int deco_height = font->height + 5; + /* precalculate the sizes to be able to correct rounding errors */ + int sizes[children]; + if (con->layout == L_DEFAULT && children > 0) { + Con *child; + int i = 0, assigned = 0; + int total = con->orientation == HORIZ ? rect.width : rect.height; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + double percentage = child->percent > 0.0 ? child->percent : 1.0 / children; + assigned += sizes[i++] = percentage * total; + } + while (assigned < total) { + for (i = 0; i < children && assigned < total; ++i) { + ++sizes[i]; + ++assigned; + } + } + } + Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) { /* default layout */ if (con->layout == L_DEFAULT) { - double percentage = 1.0 / children; - if (child->percent > 0.0) - percentage = child->percent; - printf("child %p / %s requests percentage %f\n", - child, child->name, percentage); - if (con->orientation == HORIZ) { child->rect.x = x; child->rect.y = y; - child->rect.width = percentage * rect.width; + child->rect.width = sizes[i]; child->rect.height = rect.height; x += child->rect.width; } else { child->rect.x = x; child->rect.y = y; child->rect.width = rect.width; - child->rect.height = percentage * rect.height; + child->rect.height = sizes[i]; y += child->rect.height; } From edf4aa433f975118be2050f44c5029f35ccb18d1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 6 Dec 2010 13:14:14 +0100 Subject: [PATCH 333/867] need more escaping for the version string when replacing in makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4476887d..85b01dd1 100644 --- a/Makefile +++ b/Makefile @@ -88,7 +88,7 @@ dist: distclean # Only copy source code from i3-input mkdir i3-${VERSION}/i3-input find i3-input -maxdepth 1 -type f \( -name "*.c" -or -name "*.h" -or -name "Makefile" \) -exec cp '{}' i3-${VERSION}/i3-input \; - sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION=${GIT_VERSION}/g;s/^VERSION:=\(.*\)/VERSION=${VERSION}/g' common.mk > i3-${VERSION}/common.mk + sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION:=$(shell echo '${GIT_VERSION}' | sed 's/\\/\\\\/g')/g;s/^VERSION:=\(.*\)/VERSION:=${VERSION}/g' common.mk > i3-${VERSION}/common.mk # Pre-generate a manpage to allow distributors to skip this step and save some dependencies make -C man cp man/*.1 i3-${VERSION}/man/ From 87cffac03a50a798a63ff84801910b3240468a0e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 6 Dec 2010 13:20:37 +0100 Subject: [PATCH 334/867] remove i3-wsbar from 'make install', not supported at the moment --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index 85b01dd1..4f2ab77f 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,6 @@ install: all $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3 $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/ - $(INSTALL) -m 0755 i3-wsbar $(DESTDIR)$(PREFIX)/bin/ test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config $(INSTALL) -m 0644 i3.welcome $(DESTDIR)$(SYSCONFDIR)/i3/welcome $(INSTALL) -m 0644 i3.desktop $(DESTDIR)$(PREFIX)/share/xsessions/ From 8d5421e6a40874a98e954f3643ca01969efe3c81 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 11 Dec 2010 17:03:53 +0100 Subject: [PATCH 335/867] Bugfix: Invalidate focused_id to correctly focus new windows with the same ID --- src/x.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/x.c b/src/x.c index 11efa5e5..979b8734 100644 --- a/src/x.c +++ b/src/x.c @@ -168,6 +168,9 @@ void x_con_kill(Con *con) { state = state_for_frame(con->frame); CIRCLEQ_REMOVE(&state_head, state, state); CIRCLEQ_REMOVE(&old_state_head, state, old_state); + + /* Invalidate focused_id to correctly focus new windows with the same ID */ + focused_id = XCB_NONE; } /* From 41eb810531567083633e5d897692bcae5cbe9577 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 11 Dec 2010 17:07:20 +0100 Subject: [PATCH 336/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20update=20focu?= =?UTF-8?q?s=20when=20container=20is=20not=20mapped?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/x.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/x.c b/src/x.c index 979b8734..fa003020 100644 --- a/src/x.c +++ b/src/x.c @@ -589,10 +589,15 @@ void x_push_changes(Con *con) { if (focused->window != NULL) to_focus = focused->window->id; + DLOG("focused_id = 0x%08x, to_focus = 0x%08x\n", focused_id, to_focus); if (focused_id != to_focus) { - LOG("Updating focus (focused: %p / %s)\n", focused, focused->name); - xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME); - focused_id = to_focus; + if (!focused->mapped) { + DLOG("Not updating focus (to %p / %s), focused window is not mapped.\n", focused, focused->name); + } else { + LOG("Updating focus (focused: %p / %s)\n", focused, focused->name); + xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME); + focused_id = to_focus; + } } xcb_flush(conn); From 2959dcb24c26a18aedba3111890b34eba93f216b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 25 Dec 2010 15:42:53 +0100 Subject: [PATCH 337/867] s/separate/seperate/g (Thanks Donald) --- docs/debugging | 2 +- docs/hacking-howto | 4 ++-- docs/ipc | 4 ++-- docs/userguide | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/debugging b/docs/debugging index d52edea9..b4bda670 100644 --- a/docs/debugging +++ b/docs/debugging @@ -14,7 +14,7 @@ debugging and/or need further help, do not hesitate to contact us! i3 spits out much information onto stdout, if told so. To have a clearly defined place where log files will be saved, you should redirect stdout and -stderr in xsession. While you’re at it, putting each run of i3 in a separate +stderr in xsession. While you’re at it, putting each run of i3 in a seperate log file with date/time in it is a good idea to not get confused about the different log files later on. diff --git a/docs/hacking-howto b/docs/hacking-howto index dff074cb..8a778546 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -216,7 +216,7 @@ screen you are currently on. A workspace is identified by its number. Basically, you could think of workspaces as different desks in your office, if you like the desktop methaphor. They just contain different sets of windows and are completely -separate of each other. Other window managers also call this ``Virtual +seperate of each other. Other window managers also call this ``Virtual desktops''. === The layout table @@ -264,7 +264,7 @@ should be chosen for those: == Startup (src/mainx.c, main()) * Establish the xcb connection - * Check for XKB extension on the separate X connection + * Check for XKB extension on the seperate X connection * Check for Xinerama screens * Grab the keycodes for which bindings exist * Manage all existing windows diff --git a/docs/ipc b/docs/ipc index 5fcaf62e..c35e9016 100644 --- a/docs/ipc +++ b/docs/ipc @@ -3,7 +3,7 @@ IPC interface (interprocess communication) Michael Stapelberg March 2010 -This document describes how to interface with i3 from a separate process. This +This document describes how to interface with i3 from a seperate process. This is useful for example to remote-control i3 (to write test cases for example) or to get various information like the current workspaces to implement an external workspace bar. @@ -241,7 +241,7 @@ situation can happen: You send a GET_WORKSPACES request but you receive a "workspace" event before receiving the reply to GET_WORKSPACES. If your program does not want to cope which such kinds of race conditions (an event based library may not have a problem here), I suggest you create a -separate connection to receive events. +seperate connection to receive events. === Subscribing to events diff --git a/docs/userguide b/docs/userguide index f3d71fcd..7402f64b 100644 --- a/docs/userguide +++ b/docs/userguide @@ -556,7 +556,7 @@ simple -- it does not provide a way to display custom text and it does not offer advanced customization features. This is intended because we do not want to duplicate functionality of tools like +dzen2+, +xmobar+ and so on (they render bars, we manage windows). Instead, there is an option which will -turn off the internal bar completely, so that you can use a separate program to +turn off the internal bar completely, so that you can use a seperate program to display it (see +i3-wsbar+, a sample implementation of such a program): *Syntax*: From a1dd74da5a6074da934fba53ecf70b8da23aae26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Tue, 7 Dec 2010 21:32:04 -0200 Subject: [PATCH 338/867] Implement default border styles (thanks litemotiv). --- include/config.h | 3 ++- include/data.h | 3 ++- src/cfgparse.l | 3 +++ src/cfgparse.y | 15 ++++++++++++--- src/con.c | 4 ++++ src/config.c | 1 + 6 files changed, 24 insertions(+), 5 deletions(-) diff --git a/include/config.h b/include/config.h index abbee5e7..eaa14f60 100644 --- a/include/config.h +++ b/include/config.h @@ -107,7 +107,8 @@ struct Config { * comes with i3. Thus, you can turn it off entirely. */ bool disable_workspace_bar; - const char *default_border; + /** The default border style for new windows. */ + border_style_t default_border; /** The modifier which needs to be pressed in combination with your mouse * buttons to do things with floating windows (move, resize) */ diff --git a/include/data.h b/include/data.h index 06ceef0b..6f4a4738 100644 --- a/include/data.h +++ b/include/data.h @@ -42,6 +42,7 @@ typedef struct Window i3Window; *****************************************************************************/ typedef enum { D_LEFT, D_RIGHT, D_UP, D_DOWN } direction_t; typedef enum { NO_ORIENTATION = 0, HORIZ, VERT } orientation_t; +typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_1PIXEL = 3 } border_style_t; enum { BIND_NONE = 0, @@ -331,7 +332,7 @@ struct Con { enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode; enum { L_DEFAULT = 0, L_STACKED = 1, L_TABBED = 2 } layout; - enum { BS_NORMAL = 0, BS_NONE = 1, BS_1PIXEL = 3 } border_style; + border_style_t border_style; /** floating? (= not in tiling layout) This cannot be simply a bool * because we want to keep track of whether the status was set by the * application (by setting _NET_WM_WINDOW_TYPE appropriately) or by the diff --git a/src/cfgparse.l b/src/cfgparse.l index 93ce916c..66afb14b 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -94,6 +94,9 @@ ipc_socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; } restart_state { BEGIN(BIND_AWS_COND); return TOKRESTARTSTATE; } new_container { return TOKNEWCONTAINER; } new_window { return TOKNEWWINDOW; } +normal { return TOK_NORMAL; } +none { return TOK_NONE; } +1pixel { return TOK_1PIXEL; } focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; } workspace_bar { return TOKWORKSPACEBAR; } default { /* yylval.number = MODE_DEFAULT; */return TOKCONTAINERMODE; } diff --git a/src/cfgparse.y b/src/cfgparse.y index eaed7c5d..c93c33e6 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -225,6 +225,9 @@ void parse_file(const char *f) { %token TOKMODE "mode" %token TOKNEWCONTAINER "new_container" %token TOKNEWWINDOW "new_window" +%token TOK_NORMAL "normal" +%token TOK_NONE "none" +%token TOK_1PIXEL "1pixel" %token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse" %token TOKWORKSPACEBAR "workspace_bar" %token TOKCONTAINERMODE "default/stacking/tabbed" @@ -411,13 +414,19 @@ new_container: ; new_window: - TOKNEWWINDOW WHITESPACE WORD + TOKNEWWINDOW WHITESPACE border_style { - DLOG("new windows should start in mode %s\n", $3); - config.default_border = sstrdup($3); + DLOG("new windows should start with border style %d\n", $3); + config.default_border = $3; } ; +border_style: + TOK_NORMAL { $$ = BS_NORMAL; } + | TOK_NONE { $$ = BS_NONE; } + | TOK_1PIXEL { $$ = BS_1PIXEL; } + ; + bool: NUMBER { diff --git a/src/con.c b/src/con.c index 57ed12e0..8e94c326 100644 --- a/src/con.c +++ b/src/con.c @@ -35,6 +35,7 @@ Con *con_new(Con *parent) { TAILQ_INSERT_TAIL(&all_cons, new, all_cons); new->type = CT_CON; new->name = strdup(""); + new->border_style = config.default_border; static int cnt = 0; LOG("opening window %d\n", cnt); @@ -554,6 +555,9 @@ int con_border_style(Con *con) { if (con->parent->layout == L_STACKED) return BS_NORMAL; + if (con->parent->layout == L_TABBED && con->border_style != BS_NORMAL) + return con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL; + return con->border_style; } diff --git a/src/config.c b/src/config.c index c02d1c2a..e60fd9b0 100644 --- a/src/config.c +++ b/src/config.c @@ -365,6 +365,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff"); config.restart_state_path = "~/.i3/_restart.json"; + config.default_border = BS_NORMAL; parse_configuration(override_configpath); From 3669bcbd5f447b185c16dd7cb761fdf9e390d3d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Tue, 30 Nov 2010 22:43:15 -0200 Subject: [PATCH 339/867] Remove some commented out code. --- include/util.h | 48 -------------- src/util.c | 166 ------------------------------------------------- 2 files changed, 214 deletions(-) diff --git a/include/util.h b/include/util.h index 8a399d45..fab7e399 100644 --- a/include/util.h +++ b/include/util.h @@ -103,49 +103,6 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, */ char *convert_utf8_to_ucs2(char *input, int *real_strlen); -#if 0 -/** - * Returns the client which comes next in focus stack (= was selected before) for - * the given container, optionally excluding the given client. - * - */ -Client *get_last_focused_client(xcb_connection_t *conn, Container *container, - Client *exclude); -#endif - -#if 0 -/** - * Sets the given client as focused by updating the data structures correctly, - * updating the X input focus and finally re-decorating both windows (to - * signalize the user the new focus situation) - * - */ -void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways); - -/** - * Called when the user switches to another mode or when the container is - * destroyed and thus needs to be cleaned up. - * - */ -void leave_stack_mode(xcb_connection_t *conn, Container *container); - -/** - * Switches the layout of the given container taking care of the necessary - * house-keeping - * - */ -void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode); - -/** - * Gets the first matching client for the given window class/window title. - * If the paramater specific is set to a specific client, only this one - * will be checked. - * - */ -Client *get_matching_client(xcb_connection_t *conn, - const char *window_classtitle, Client *specific); -#endif - /* * Restart i3 in-place * appends -a to argument list to disable autostart @@ -153,9 +110,4 @@ Client *get_matching_client(xcb_connection_t *conn, */ void i3_restart(); -#if defined(__OpenBSD__) -/* OpenBSD does not provide memmem(), so we provide FreeBSD’s implementation */ -void *memmem(const void *l, size_t l_len, const void *s, size_t s_len); -#endif - #endif diff --git a/src/util.c b/src/util.c index e05ce465..0c36cf57 100644 --- a/src/util.c +++ b/src/util.c @@ -174,172 +174,6 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) { return buffer; } -#if 0 - -/* - * Returns the client which comes next in focus stack (= was selected before) for - * the given container, optionally excluding the given client. - * - */ -Client *get_last_focused_client(xcb_connection_t *conn, Container *container, Client *exclude) { - Client *current; - SLIST_FOREACH(current, &(container->workspace->focus_stack), focus_clients) - if ((current->container == container) && ((exclude == NULL) || (current != exclude))) - return current; - return NULL; -} - - -/* - * Sets the given client as focused by updating the data structures correctly, - * updating the X input focus and finally re-decorating both windows (to signalize - * the user the new focus situation) - * - */ -void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { - /* The dock window cannot be focused, but enter notifies are still handled correctly */ - if (client->dock) - return; - - /* Store the old client */ - Client *old_client = SLIST_FIRST(&(c_ws->focus_stack)); - - /* Check if the focus needs to be changed at all */ - if (!set_anyways && (old_client == client)) - return; - - /* Store current_row/current_col */ - c_ws->current_row = current_row; - c_ws->current_col = current_col; - c_ws = client->workspace; - ewmh_update_current_desktop(); - /* Load current_col/current_row if we switch to a client without a container */ - current_col = c_ws->current_col; - current_row = c_ws->current_row; - - /* Update container */ - if (client->container != NULL) { - client->container->currently_focused = client; - - current_col = client->container->col; - current_row = client->container->row; - } - - CLIENT_LOG(client); - /* Set focus to the entered window, and flush xcb buffer immediately */ - xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME); - ewmh_update_active_window(client->child); - //xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10); - - if (client->container != NULL) { - /* Get the client which was last focused in this particular container, it may be a different - one than old_client */ - Client *last_focused = get_last_focused_client(conn, client->container, NULL); - - /* In stacking containers, raise the client in respect to the one which was focused before */ - if ((client->container->mode == MODE_STACK || client->container->mode == MODE_TABBED) && - client->container->workspace->fullscreen_client == NULL) { - /* We need to get the client again, this time excluding the current client, because - * we might have just gone into stacking mode and need to raise */ - Client *last_focused = get_last_focused_client(conn, client->container, client); - - if (last_focused != NULL) { - DLOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child); - uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE }; - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); - } - } - - /* If it is the same one as old_client, we save us the unnecessary redecorate */ - if ((last_focused != NULL) && (last_focused != old_client)) - redecorate_window(conn, last_focused); - } - - /* If the last client was a floating client, we need to go to the next - * tiling client in stack and re-decorate it. */ - if (old_client != NULL && client_is_floating(old_client)) { - DLOG("Coming from floating client, searching next tiling...\n"); - Client *current; - SLIST_FOREACH(current, &(client->workspace->focus_stack), focus_clients) { - if (client_is_floating(current)) - continue; - - DLOG("Found window: %p / child %p\n", current->frame, current->child); - redecorate_window(conn, current); - break; - } - } - - SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); - SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients); - - /* Clear the urgency flag if set (necessary when i3 sets the flag, for - * example when automatically putting windows on the workspace of their - * leader) */ - client->urgent = false; - workspace_update_urgent_flag(client->workspace); - - /* If we’re in stacking mode, this renders the container to update changes in the title - bars and to raise the focused client */ - if ((old_client != NULL) && (old_client != client) && !old_client->dock) - redecorate_window(conn, old_client); - - /* redecorate_window flushes, so we don’t need to */ - redecorate_window(conn, client); -} - -/* - * Gets the first matching client for the given window class/window title. - * If the paramater specific is set to a specific client, only this one - * will be checked. - * - */ -Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle, - Client *specific) { - char *to_class, *to_title, *to_title_ucs = NULL; - int to_title_ucs_len = 0; - Client *matching = NULL; - - to_class = sstrdup(window_classtitle); - - /* If a title was specified, split both strings at the slash */ - if ((to_title = strstr(to_class, "/")) != NULL) { - *(to_title++) = '\0'; - /* Convert to UCS-2 */ - to_title_ucs = convert_utf8_to_ucs2(to_title, &to_title_ucs_len); - } - - /* If we were given a specific client we only check if that one matches */ - if (specific != NULL) { - if (client_matches_class_name(specific, to_class, to_title, to_title_ucs, to_title_ucs_len)) - matching = specific; - goto done; - } - - DLOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title); - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->output == NULL) - continue; - - Client *client; - SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) { - DLOG("Checking client with class=%s / %s, name=%s\n", client->window_class_instance, - client->window_class_class, client->name); - if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) - continue; - - matching = client; - goto done; - } - } - -done: - free(to_class); - FREE(to_title_ucs); - return matching; -} -#endif /* * Goes through the list of arguments (for exec()) and checks if the given argument From 68f906f278aba60c669e2cbcb7185ae970f1aa18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Tue, 30 Nov 2010 22:47:16 -0200 Subject: [PATCH 340/867] util.c is the proper place for those functions. --- include/config.h | 3 --- include/util.h | 14 ++++++++++++++ src/config.c | 41 ----------------------------------------- src/util.c | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 44 deletions(-) diff --git a/include/config.h b/include/config.h index eaa14f60..ccff0fc8 100644 --- a/include/config.h +++ b/include/config.h @@ -129,9 +129,6 @@ struct Config { } bar; }; -char *resolve_tilde(const char *path); -bool path_exists(const char *path); - /** * Reads the configuration from ~/.i3/config or /etc/i3/config if not found. * diff --git a/include/util.h b/include/util.h index fab7e399..46a15655 100644 --- a/include/util.h +++ b/include/util.h @@ -103,6 +103,20 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, */ char *convert_utf8_to_ucs2(char *input, int *real_strlen); +/* + * 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(). + * + */ +bool path_exists(const char *path); + /* * Restart i3 in-place * appends -a to argument list to disable autostart diff --git a/src/config.c b/src/config.c index e60fd9b0..683310c8 100644 --- a/src/config.c +++ b/src/config.c @@ -22,47 +22,6 @@ Config config; struct modes_head modes; - -/* - * 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 ? 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)); - strncat(result, tail, strlen(tail)); - } - globfree(&globbuf); - - return result; -} - -/* - * Checks if the given path exists by calling stat(). - * - */ -bool path_exists(const char *path) { - struct stat buf; - return (stat(path, &buf) == 0); -} - /** * Ungrabs all keys, to be called before re-grabbing the keys because of a * mapping_notify event or a configuration file reload diff --git a/src/util.c b/src/util.c index 0c36cf57..89f55960 100644 --- a/src/util.c +++ b/src/util.c @@ -175,6 +175,46 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) { return buffer; } +/* + * 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 ? 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)); + strncat(result, tail, strlen(tail)); + } + globfree(&globbuf); + + return result; +} + +/* + * Checks if the given path exists by calling stat(). + * + */ +bool path_exists(const char *path) { + struct stat buf; + return (stat(path, &buf) == 0); +} + /* * Goes through the list of arguments (for exec()) and checks if the given argument * is present. If not, it copies the arguments (because we cannot realloc it) and From c88c3e3ab2c16da734f0897701c51faf3df84768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Wed, 1 Dec 2010 00:14:08 -0200 Subject: [PATCH 341/867] Default to a file in /tmp for the restart state. The file is now created in /tmp using the process PID and the username of the user running i3. The restart state file is only loaded when restarting (the --restart option is appended to the command line prior to the restart). That means that renaming the old state file with the ".old" extension is no longer needed. This "--restart" switch is supposed to be only used by i3. The "-L" switch can be used to load a layout (and not delete it afterwards). We unlink the state file after we load it so that we don't keep cruft in /tmp or try to restart from an old config file if restart_state is set. --- include/tree.h | 2 +- src/config.c | 1 - src/main.c | 25 ++++++++++++++-- src/tree.c | 13 ++------- src/util.c | 78 ++++++++++++++++++++++++++++++++++++++++++++------ 5 files changed, 95 insertions(+), 24 deletions(-) diff --git a/include/tree.h b/include/tree.h index f04e9e62..6376ed92 100644 --- a/include/tree.h +++ b/include/tree.h @@ -82,6 +82,6 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent); * Loads tree from ~/.i3/_restart.json (used for in-place restarts). * */ -bool tree_restore(); +bool tree_restore(const char *path); #endif diff --git a/src/config.c b/src/config.c index 683310c8..8a394c1f 100644 --- a/src/config.c +++ b/src/config.c @@ -323,7 +323,6 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888"); INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff"); - config.restart_state_path = "~/.i3/_restart.json"; config.default_border = BS_NORMAL; parse_configuration(override_configpath); diff --git a/src/main.c b/src/main.c index 121100b5..b6923d6a 100644 --- a/src/main.c +++ b/src/main.c @@ -76,6 +76,8 @@ int main(int argc, char *argv[]) { int screens; char *override_configpath = NULL; bool autostart = true; + char *layout_path = NULL; + bool delete_layout_path; bool only_check_config = false; bool force_xinerama = false; xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS]; @@ -84,6 +86,8 @@ int main(int argc, char *argv[]) { {"config", required_argument, 0, 'c'}, {"version", no_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, + {"layout", required_argument, 0, 'L'}, + {"restart", required_argument, 0, 0}, {"force-xinerama", no_argument, 0, 0}, {0, 0, 0, 0} }; @@ -97,12 +101,16 @@ int main(int argc, char *argv[]) { start_argv = argv; - while ((opt = getopt_long(argc, argv, "c:Cvahld:V", long_options, &option_index)) != -1) { + while ((opt = getopt_long(argc, argv, "c:CvaL:hld:V", long_options, &option_index)) != -1) { switch (opt) { case 'a': LOG("Autostart disabled using -a\n"); autostart = false; break; + case 'L': + layout_path = sstrdup(optarg); + delete_layout_path = false; + break; case 'c': override_configpath = sstrdup(optarg); break; @@ -132,12 +140,17 @@ int main(int argc, char *argv[]) { "Please check if your driver really does not support RandR " "and disable this option as soon as you can.\n"); break; + } else if (strcmp(long_options[option_index].name, "restart") == 0) { + layout_path = sstrdup(optarg); + delete_layout_path = true; + break; } /* fall-through */ default: fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]); fprintf(stderr, "\n"); fprintf(stderr, "-a: disable autostart\n"); + fprintf(stderr, "-L : load the layout from \n"); fprintf(stderr, "-v: display version and exit\n"); fprintf(stderr, "-V: enable verbose mode\n"); fprintf(stderr, "-d : enable debug loglevel \n"); @@ -327,7 +340,15 @@ int main(int argc, char *argv[]) { #endif } - if (!tree_restore()) + bool needs_tree_init = true; + if (layout_path) { + LOG("Trying to restore the layout from %s...", layout_path); + needs_tree_init = !tree_restore(layout_path); + if (delete_layout_path) + unlink(layout_path); + free(layout_path); + } + if (needs_tree_init) tree_init(); tree_render(); diff --git a/src/tree.c b/src/tree.c index f1b521e8..1edbf735 100644 --- a/src/tree.c +++ b/src/tree.c @@ -13,8 +13,8 @@ struct all_cons_head all_cons = TAILQ_HEAD_INITIALIZER(all_cons); * Loads tree from ~/.i3/_restart.json (used for in-place restarts). * */ -bool tree_restore() { - char *globbed = resolve_tilde(config.restart_state_path); +bool tree_restore(const char *path) { + char *globbed = resolve_tilde(path); if (!path_exists(globbed)) { LOG("%s does not exist, not restoring tree\n", globbed); @@ -28,15 +28,6 @@ bool tree_restore() { tree_append_json(globbed); - size_t path_len = strlen(config.restart_state_path); - char *old_restart = smalloc(path_len + strlen(".old") + 1); - strncpy(old_restart, config.restart_state_path, path_len + strlen(".old") + 1); - strncat(old_restart, ".old", strlen(".old") + 1); - unlink(old_restart); - rename(globbed, old_restart); - free(globbed); - free(old_restart); - printf("appended tree, using new root\n"); croot = TAILQ_FIRST(&(croot->nodes_head)); printf("new root = %p\n", croot); diff --git a/src/util.c b/src/util.c index 89f55960..b8c314c7 100644 --- a/src/util.c +++ b/src/util.c @@ -16,8 +16,8 @@ #if defined(__OpenBSD__) #include #endif - #include +#include #include "all.h" @@ -238,10 +238,29 @@ static char **append_argument(char **original, char *argument) { return result; } +/* + * Returns the name of a temporary file with the specified prefix. + * + */ +char *get_process_filename(const char *prefix) +{ + struct passwd *pw = getpwuid(getuid()); + const char *username = pw ? pw->pw_name : "unknown"; + char *filename; + int res = asprintf(&filename, "/tmp/%s-%s.%d", prefix, username, getpid()); + if (res == -1) { + perror("asprintf()"); + return NULL; + } + else { + return filename; + } +} + #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) #define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str)) -void store_restart_layout() { +char *store_restart_layout() { setlocale(LC_NUMERIC, "C"); yajl_gen gen = yajl_gen_alloc(NULL, NULL); @@ -253,12 +272,22 @@ void store_restart_layout() { unsigned int length; y(get_buf, &payload, &length); - char *globbed = resolve_tilde(config.restart_state_path); - int fd = open(globbed, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); - free(globbed); + /* create a temporary file if one hasn't been specified, or just + * resolve the tildes in the specified path */ + char *filename; + if (config.restart_state_path == NULL) { + filename = get_process_filename("i3-restart-state"); + if (!filename) + return NULL; + } else { + filename = resolve_tilde(config.restart_state_path); + } + + int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (fd == -1) { perror("open()"); - return; + free(filename); + return NULL; } int written = 0; @@ -267,11 +296,13 @@ void store_restart_layout() { /* TODO: correct error-handling */ if (n == -1) { perror("write()"); - return; + free(filename); + return NULL; } if (n == 0) { printf("write == 0?\n"); - return; + free(filename); + return NULL; } written += n; printf("written: %d of %d\n", written, length); @@ -281,6 +312,8 @@ void store_restart_layout() { printf("layout: %.*s\n", length, payload); y(free); + + return filename; } /* @@ -289,7 +322,7 @@ void store_restart_layout() { * */ void i3_restart() { - store_restart_layout(); + char *restart_filename = store_restart_layout(); restore_geometry(); ipc_shutdown(); @@ -298,6 +331,33 @@ void i3_restart() { /* make sure -a is in the argument list or append it */ start_argv = append_argument(start_argv, "-a"); + /* replace -r so that the layout is restored */ + if (restart_filename != NULL) { + /* create the new argv */ + int num_args; + for (num_args = 0; start_argv[num_args] != NULL; num_args++); + char **new_argv = scalloc((num_args + 3) * sizeof(char*)); + + /* copy the arguments, but skip the ones we'll replace */ + int write_index = 0; + bool skip_next = false; + for (int i = 0; i < num_args; ++i) { + if (skip_next) + skip_next = false; + else if (!strcmp(start_argv[i], "-r")) + skip_next = true; + else + new_argv[write_index++] = start_argv[i]; + } + + /* add the arguments we'll replace */ + new_argv[write_index++] = "--restart"; + new_argv[write_index++] = restart_filename; + + /* swap the argvs */ + start_argv = new_argv; + } + execvp(start_argv[0], start_argv); /* not reached */ } From dc3c633ee42e30f2f9ea29afa6394333cc7fb595 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 27 Dec 2010 22:28:59 +0100 Subject: [PATCH 342/867] Bugfix: store and properly load workspace order when restarting --- src/ipc.c | 5 +++++ src/load_layout.c | 12 ++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index 3954107f..f953f9ae 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -201,6 +201,11 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("name"); ystr(con->name); + if (con->type == CT_WORKSPACE) { + ystr("num"); + y(integer, con->num); + } + ystr("window"); if (con->window) y(integer, con->window->id); diff --git a/src/load_layout.c b/src/load_layout.c index 60a63313..8d532da4 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -34,7 +34,9 @@ static int json_start_map(void *ctx) { TAILQ_INSERT_TAIL(&(ws->floating_head), json_node, floating_windows); TAILQ_INSERT_TAIL(&(ws->focus_head), json_node, focused); } else { - json_node = con_new(json_node); + Con *parent = json_node; + json_node = con_new(NULL); + json_node->parent = parent; } } } @@ -43,8 +45,11 @@ static int json_start_map(void *ctx) { static int json_end_map(void *ctx) { LOG("end of map\n"); - if (!parsing_swallows && !parsing_rect && !parsing_window_rect) + if (!parsing_swallows && !parsing_rect && !parsing_window_rect) { + LOG("attaching\n"); + con_attach(json_node, json_node->parent, false); json_node = json_node->parent; + } if (parsing_rect) parsing_rect = false; if (parsing_window_rect) @@ -113,6 +118,9 @@ static int json_int(void *ctx, long val) { to_focus = json_node; } + if (strcasecmp(last_key, "num") == 0) + json_node->num = val; + if (parsing_rect || parsing_window_rect) { Rect *r = (parsing_rect ? &(json_node->rect) : &(json_node->window_rect)); if (strcasecmp(last_key, "x") == 0) From d1845879594215450a2b8c898e6a3418ab27cdce Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 28 Dec 2010 02:27:11 +0100 Subject: [PATCH 343/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20focus=20next?= =?UTF-8?q?=20window=20if=20the=20window=20was=20not=20mapped=20at=20the?= =?UTF-8?q?=20moment=20(on=20a=20different=20ws)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tree.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/tree.c b/src/tree.c index 1edbf735..f86ae312 100644 --- a/src/tree.c +++ b/src/tree.c @@ -145,6 +145,7 @@ static void fix_floating_parent(Con *con, Con *vanishing) { * */ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { + bool was_mapped = con->mapped; Con *parent = con->parent; /* check floating clients and adjust old_parent if necessary */ @@ -196,9 +197,13 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { if (!next) return; - DLOG("focusing %p / %s\n", next, next->name); - /* TODO: check if the container (or one of its children) was focused */ - con_focus(next); + if (was_mapped) { + DLOG("focusing %p / %s\n", next, next->name); + /* TODO: check if the container (or one of its children) was focused */ + con_focus(next); + } else { + DLOG("not focusing, was not mapped\n"); + } /* check if the parent container is empty now and close it */ if (!dont_kill_parent && From 028f7d2ca78395cdd024ace597719ddbba2dfb94 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 28 Dec 2010 16:25:34 +0100 Subject: [PATCH 344/867] Fix resize handling on click on borders (did not correctly use orientation) --- include/con.h | 7 +++++ src/click.c | 71 +++++++++++++++++---------------------------------- src/con.c | 37 +++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 47 deletions(-) diff --git a/include/con.h b/include/con.h index 736b526c..820d847d 100644 --- a/include/con.h +++ b/include/con.h @@ -139,6 +139,13 @@ int con_orientation(Con *con); */ Con *con_next_focused(Con *con); +/** + * Get the next/previous container in the specified orientation. This may + * travel up until it finds a container with suitable orientation. + * + */ +Con *con_get_next(Con *con, char way, orientation_t orientation); + /** * Returns a "relative" Rect which contains the amount of pixels that need to * be added to the original Rect to get the final position (obviously the diff --git a/src/click.c b/src/click.c index 5b7f0827..a4d9181a 100644 --- a/src/click.c +++ b/src/click.c @@ -238,7 +238,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client, int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) { Con *con; - LOG("Button %d pressed on window 0x%08x\n", event->state, event->event); + DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event); con = con_by_window_id(event->event); bool border_click = false; @@ -246,14 +246,14 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ con = con_by_frame_id(event->event); border_click = true; } - LOG("border_click = %d\n", border_click); + DLOG("border_click = %d\n", border_click); //if (con && con->type == CT_FLOATING_CON) //con = TAILQ_FIRST(&(con->nodes_head)); /* See if this was a click with the configured modifier. If so, we need * to move around the client if it was floating. if not, we just process * as usual. */ - LOG("state = %d, floating_modifier = %d\n", event->state, config.floating_modifier); + DLOG("state = %d, floating_modifier = %d\n", event->state, config.floating_modifier); if (border_click || (config.floating_modifier != 0 && (event->state & config.floating_modifier) == config.floating_modifier)) { @@ -261,7 +261,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ LOG("Not handling, floating_modifier was pressed and no client found\n"); return 1; } - LOG("handling\n"); + DLOG("handling\n"); #if 0 if (con->fullscreen) { LOG("Not handling, client is in fullscreen mode\n"); @@ -285,24 +285,10 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ } return 1; } - - DLOG("border click on non-floating container at %d, %d\n", event->event_x, event->event_y); - Con *child; - TAILQ_FOREACH(child, &(con->nodes_head), nodes) { - if (!rect_contains(child->deco_rect, event->event_x, event->event_y)) - continue; - - con_focus(child); - break; - } - - tree_render(); - return 1; } /* click to focus */ con_focus(con); - tree_render(); Con *clicked_into = NULL; @@ -312,65 +298,56 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ continue; clicked_into = child; + con_focus(child); break; } + tree_render(); + /* check if this was a click on the window border (and on which one) */ Rect bsr = con_border_style_rect(con); DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x, border_click = %d, clicked_into = %p\n", event->event_x, event->event_y, con, event->event, border_click, clicked_into); DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width); + /* TODO: das problem ist, dass TAILQ_PREV etc. nicht die orientation beachtet. */ Con *first = NULL, *second = NULL; if (clicked_into) { DLOG("BORDER top\n"); second = clicked_into; - first = TAILQ_PREV(clicked_into, nodes_head, nodes); - - if (first == TAILQ_END(&(con->parent->nodes_head))) { - DLOG("cannot go further\n"); - return 0; - } - - resize_graphical_handler(first, second, VERT, event); + if ((first = con_get_next(clicked_into, 'p', VERT)) != NULL) + resize_graphical_handler(first, second, VERT, event); } else if (event->event_x >= 0 && event->event_x <= bsr.x && event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) { DLOG("BORDER left\n"); second = con; - first = TAILQ_PREV(con, nodes_head, nodes); - if (first == TAILQ_END(&(con->parent->nodes_head))) { - DLOG("cannot go further\n"); - return 0; - } - - resize_graphical_handler(first, second, HORIZ, event); + if ((first = con_get_next(con, 'p', HORIZ)) != NULL) + resize_graphical_handler(first, second, HORIZ, event); } else if (event->event_x >= (con->window_rect.x + con->window_rect.width) && event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) { DLOG("BORDER right\n"); first = con; - second = TAILQ_NEXT(con, nodes); - if (second == TAILQ_END(&(con->parent->nodes_head))) { - DLOG("cannot go further\n"); - return 0; - } - - resize_graphical_handler(first, second, HORIZ, event); + if ((second = con_get_next(con, 'n', HORIZ)) != NULL) + resize_graphical_handler(first, second, HORIZ, event); } else if (event->event_y >= (con->window_rect.y + con->window_rect.height)) { DLOG("BORDER bottom\n"); first = con; - second = TAILQ_NEXT(con, nodes); - if (second == TAILQ_END(&(con->parent->nodes_head))) { - DLOG("cannot go further\n"); - return 0; - } - - resize_graphical_handler(first, second, VERT, event); + if ((second = con_get_next(con, 'n', VERT)) != NULL) + resize_graphical_handler(first, second, VERT, event); } else { + /* Set first and second to NULL to trigger a replay of the event */ + first = second = NULL; + } + + if (first == NULL || second == NULL) { + DLOG("Replaying click\n"); /* No border click, replay the click */ xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); xcb_flush(conn); + return 0; } + DLOG("After resize handler, rendering\n"); tree_render(); return 0; diff --git a/src/con.c b/src/con.c index 8e94c326..dc8bc354 100644 --- a/src/con.c +++ b/src/con.c @@ -515,6 +515,43 @@ Con *con_next_focused(Con *con) { return next; } +/* + * Get the next/previous container in the specified orientation. This may + * travel up until it finds a container with suitable orientation. + * + */ +Con *con_get_next(Con *con, char way, orientation_t orientation) { + DLOG("con_get_next(way=%c, orientation=%d)\n", way, orientation); + /* 1: get the first parent with the same orientation */ + Con *cur = con; + while (con_orientation(cur->parent) != orientation) { + LOG("need to go one level further up\n"); + if (cur->parent->type == CT_WORKSPACE) { + LOG("that's a workspace, we can't go further up\n"); + return NULL; + } + cur = cur->parent; + } + + /* 2: chose next (or previous) */ + Con *next; + if (way == 'n') { + next = TAILQ_NEXT(cur, nodes); + /* if we are at the end of the list, we need to wrap */ + if (next == TAILQ_END(&(parent->nodes_head))) + return NULL; + } else { + next = TAILQ_PREV(cur, nodes_head, nodes); + /* if we are at the end of the list, we need to wrap */ + if (next == TAILQ_END(&(cur->nodes_head))) + return NULL; + } + DLOG("next = %p\n", next); + + return next; +} + + /* * Returns a "relative" Rect which contains the amount of pixels that need to * be added to the original Rect to get the final position (obviously the From 307c59bde67f03fa6ac06934be3ddf2ea39d2464 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 28 Dec 2010 19:54:56 +0100 Subject: [PATCH 345/867] Bugfix: Fix closing windows in tabbed mode with border_style == 1pixel / none --- src/render.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/render.c b/src/render.c index f4d1e830..6e3cd0fe 100644 --- a/src/render.c +++ b/src/render.c @@ -190,8 +190,10 @@ void render_con(Con *con, bool render_fullscreen) { child->deco_rect.x = x - con->rect.x + i * child->deco_rect.width; child->deco_rect.y = y - con->rect.y; - child->rect.y += deco_height; - child->rect.height -= deco_height; + if (children > 1 || (child->border_style != BS_1PIXEL && child->border_style != BS_NONE)) { + child->rect.y += deco_height; + child->rect.height -= deco_height; + } } printf("child at (%d, %d) with (%d x %d)\n", From f65e4f5b16dc613e03711c5edc27c736c2f943c7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 30 Dec 2010 02:39:14 +0100 Subject: [PATCH 346/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20redistribute?= =?UTF-8?q?=20resize=20percentage=20values=20when=20closing=20floating=20(?= =?UTF-8?q?!)=20windows=20(Thanks=20Merovius)=20(+testcase)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tree.c | 6 +++- testcases/t/44-regress-floating-resize.t | 38 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 testcases/t/44-regress-floating-resize.t diff --git a/src/tree.c b/src/tree.c index f86ae312..baa11ced 100644 --- a/src/tree.c +++ b/src/tree.c @@ -180,7 +180,11 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { x_con_kill(con); con_detach(con); - con_fix_percent(parent, WINDOW_REMOVE); + if (con->type != CT_FLOATING_CON) { + /* If the container is *not* floating, we might need to re-distribute + * percentage values for the resized containers. */ + con_fix_percent(parent, WINDOW_REMOVE); + } if (con_is_floating(con)) { DLOG("Container was floating, killing floating container\n"); diff --git a/testcases/t/44-regress-floating-resize.t b/testcases/t/44-regress-floating-resize.t new file mode 100644 index 00000000..d7102cec --- /dev/null +++ b/testcases/t/44-regress-floating-resize.t @@ -0,0 +1,38 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Regression: when resizing two containers on a workspace, opening a floating +# client, then closing it again, i3 will re-distribute the space on the +# workspace as if a tiling container was closed, leading to the containers +# taking much more space than they possibly could. +# +use i3test tests => 1; +use X11::XCB qw(:all); +use Time::HiRes qw(sleep); +use List::Util qw(sum); + +my $tmp = get_unused_workspace(); +cmd "workspace $tmp"; + +cmd 'exec /usr/bin/urxvt'; +sleep 0.5; +cmd 'exec /usr/bin/urxvt'; +sleep 0.5; +my ($nodes, $focus) = get_ws_content($tmp); +my $old_sum = sum map { $_->{rect}->{width} } @{$nodes}; +#cmd 'open'; +cmd 'resize grow left 10 px or 25 ppt'; +cmd 'split v'; +#cmd 'open'; +cmd 'exec /usr/bin/urxvt'; +sleep 0.5; +cmd 'mode toggle'; +sleep 0.5; +cmd 'kill'; + +sleep 0.5; + +($nodes, $focus) = get_ws_content($tmp); +my $new_sum = sum map { $_->{rect}->{width} } @{$nodes}; + +is($old_sum, $new_sum, 'combined container width is still equal'); From 50914e04833105eb12cfb0916365c8365be1cdb8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 30 Dec 2010 23:01:58 +0100 Subject: [PATCH 347/867] Bugfix: Correctly change focus when closing a split-container The problem was i3 leaving an invalid focus pointer valid (after killing the container) because the container itself is not mapped (if it has no x11 window, for example split containers). --- src/tree.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/tree.c b/src/tree.c index baa11ced..5b3f0df5 100644 --- a/src/tree.c +++ b/src/tree.c @@ -140,6 +140,16 @@ static void fix_floating_parent(Con *con, Con *vanishing) { fix_floating_parent(child, vanishing); } +static bool _is_con_mapped(Con *con) { + Con *child; + + TAILQ_FOREACH(child, &(con->nodes_head), nodes) + if (_is_con_mapped(child)) + return true; + + return con->mapped; +} + /* * Closes the given container including all children * @@ -148,6 +158,13 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { bool was_mapped = con->mapped; Con *parent = con->parent; + if (!was_mapped) { + /* Even if the container itself is not mapped, its children may be + * mapped (for example split containers don't have a mapped window on + * their own but usually contain mapped children). */ + was_mapped = _is_con_mapped(con); + } + /* check floating clients and adjust old_parent if necessary */ fix_floating_parent(croot, con); @@ -231,6 +248,10 @@ void tree_close_con() { return; } + /* There *should* be no possibility to focus outputs / root container */ + assert(focused->type != CT_OUTPUT); + assert(focused->type != CT_ROOT); + /* Kill con */ tree_close(focused, true, false); } From daf00a932f0c6b59821b11ad8b09e150ef608f94 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 30 Dec 2010 23:09:18 +0100 Subject: [PATCH 348/867] For floating mode on workspace level, create a container around the content (Thanks Merovius) Like when setting a workspace to stacked, we need to create a new container around the content and set *that* to floating. --- src/floating.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/floating.c b/src/floating.c index 6506941a..a88d498a 100644 --- a/src/floating.c +++ b/src/floating.c @@ -22,6 +22,44 @@ void floating_enable(Con *con, bool automatic) { return; } + /* 1: If the container is a workspace container, we need to create a new + * split-container with the same orientation and make that one floating. We + * cannot touch the workspace container itself because floating containers + * are children of the workspace. */ + if (con->type == CT_WORKSPACE) { + LOG("This is a workspace, creating new container around content\n"); + /* TODO: refactor this with src/con.c:con_set_layout */ + Con *new = con_new(NULL); + new->parent = con; + new->orientation = con->orientation; + + /* since the new container will be set into floating mode directly + * afterwards, we need to copy the workspace rect. */ + memcpy(&(new->rect), &(con->rect), sizeof(Rect)); + + Con *old_focused = TAILQ_FIRST(&(con->focus_head)); + if (old_focused == TAILQ_END(&(con->focus_head))) + old_focused = NULL; + + /* 4: move the existing cons of this workspace below the new con */ + DLOG("Moving cons\n"); + Con *child; + while (!TAILQ_EMPTY(&(con->nodes_head))) { + child = TAILQ_FIRST(&(con->nodes_head)); + con_detach(child); + con_attach(child, new, true); + } + + /* 4: attach the new split container to the workspace */ + DLOG("Attaching new split to ws\n"); + con_attach(new, con, false); + + if (old_focused) + con_focus(old_focused); + + con = new; + } + /* 1: detach the container from its parent */ /* TODO: refactor this with tree_close() */ TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); From 2d05c3a37d56d7de8d75fc4f79e07428d82956f4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 31 Dec 2010 01:38:17 +0100 Subject: [PATCH 349/867] Fix dragging floating containers / click handling --- include/con.h | 7 +++++ include/floating.h | 6 ++++ src/click.c | 72 +++++++++++++++++++++++++++++----------------- src/con.c | 21 ++++++++++++++ src/floating.c | 10 +++++++ 5 files changed, 90 insertions(+), 26 deletions(-) diff --git a/include/con.h b/include/con.h index 820d847d..1abc09a0 100644 --- a/include/con.h +++ b/include/con.h @@ -54,6 +54,13 @@ Con *con_get_fullscreen_con(Con *con); */ bool con_is_floating(Con *con); +/** + * Checks if the given container is either floating or inside some floating + * container. It returns the FLOATING_CON container. + * + */ +Con *con_inside_floating(Con *con); + /** * Returns the container with the given client window ID or NULL if no such * container exists. diff --git a/include/floating.h b/include/floating.h index a312daee..c3935252 100644 --- a/include/floating.h +++ b/include/floating.h @@ -53,6 +53,12 @@ void floating_disable(Con *con, bool automatic); */ void toggle_floating_mode(Con *con, bool automatic); +/** + * Raises the given container in the list of floating containers + * + */ +void floating_raise_con(Con *con); + #if 0 /** * Removes the floating client from its workspace and attaches it to the new diff --git a/src/click.c b/src/click.c index a4d9181a..243820cb 100644 --- a/src/click.c +++ b/src/click.c @@ -237,26 +237,47 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client, #endif int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) { + /* TODO: dragging floating windows by grabbing their decoration does not + * work right now. We need to somehow recognize that special case: either + * we check if the con with the clicked decoration is the only con inside + * its parent (so that you can only drag single floating windows, not + * floating containers with multiple windows) *or* we somehow find out + * which decoration(s) are at the top and enable grabbing for them while + * resizing for the others. Maybe we could process the resizing parameters + * first and check if resizing is possible: if yes, resize, if not, drag. + * + * Also raise on click on decoration is not working. */ Con *con; DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event); con = con_by_window_id(event->event); - bool border_click = false; - if (con == NULL) { + bool border_click = (con == NULL); + const uint32_t mod = config.floating_modifier; + bool mod_pressed = (mod != 0 && (event->state & mod) == mod); + + if (border_click) con = con_by_frame_id(event->event); - border_click = true; + + DLOG("border_click = %d, mod_pressed = %d\n", border_click, mod_pressed); + + Con *clicked_into = NULL; + + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + if (!rect_contains(child->deco_rect, event->event_x, event->event_y)) + continue; + + clicked_into = child; + break; } - DLOG("border_click = %d\n", border_click); - //if (con && con->type == CT_FLOATING_CON) - //con = TAILQ_FIRST(&(con->nodes_head)); + + DLOG("clicked_into = %p\n", clicked_into); /* See if this was a click with the configured modifier. If so, we need * to move around the client if it was floating. if not, we just process * as usual. */ DLOG("state = %d, floating_modifier = %d\n", event->state, config.floating_modifier); - if (border_click || - (config.floating_modifier != 0 && - (event->state & config.floating_modifier) == config.floating_modifier)) { + if (border_click || mod_pressed) { if (con == NULL) { LOG("Not handling, floating_modifier was pressed and no client found\n"); return 1; @@ -268,11 +289,10 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ return 1; } #endif + Con *floatingcon = con; if ((border_click && con->type == CT_FLOATING_CON) || - (!border_click && con_is_floating(con))) { - /* floating operations are always on the container around - * the "payload container", so make sure we use the right one */ - Con *floatingcon = (border_click ? con : con->parent); + ((floatingcon = con_inside_floating(con)) != NULL && + clicked_into == NULL)) { LOG("button %d pressed\n", event->detail); if (event->detail == 1) { LOG("left mouse button, dragging\n"); @@ -287,23 +307,23 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ } } - /* click to focus */ - con_focus(con); + /* click to focus, either on the clicked window or its child if thas was a + * click into a child decoration */ + con_focus((clicked_into ? clicked_into : con)); - Con *clicked_into = NULL; - - Con *child; - TAILQ_FOREACH(child, &(con->nodes_head), nodes) { - if (!rect_contains(child->deco_rect, event->event_x, event->event_y)) - continue; - - clicked_into = child; - con_focus(child); - break; - } + /* for floating containers, we also want to raise them on click */ + Con *floatingcon = con_inside_floating(con); + if (floatingcon != NULL) + floating_raise_con(floatingcon); tree_render(); + /* if we clicked into a child decoration on a stacked/tabbed container, we + * are done and don’t want to resize */ + if (clicked_into && + (con->layout == L_STACKED || con->layout == L_TABBED)) + return 1; + /* check if this was a click on the window border (and on which one) */ Rect bsr = con_border_style_rect(con); DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x, border_click = %d, clicked_into = %p\n", diff --git a/src/con.c b/src/con.c index dc8bc354..0f2374f6 100644 --- a/src/con.c +++ b/src/con.c @@ -157,6 +157,7 @@ void con_detach(Con *con) { */ void con_focus(Con *con) { assert(con != NULL); + DLOG("con_focus = %p\n", con); /* 1: set focused-pointer to the new con */ /* 2: exchange the position of the container in focus stack of the parent all the way up */ @@ -170,6 +171,7 @@ void con_focus(Con *con) { con->urgent = false; workspace_update_urgent_flag(con_get_workspace(con)); } + DLOG("con_focus done = %p\n", con); } /* @@ -287,6 +289,25 @@ bool con_is_floating(Con *con) { return (con->floating >= FLOATING_AUTO_ON); } +/* + * Checks if the given container is either floating or inside some floating + * container. It returns the FLOATING_CON container. + * + */ +Con *con_inside_floating(Con *con) { + assert(con != NULL); + if (con->type == CT_FLOATING_CON) + return con; + + if (con->floating >= FLOATING_AUTO_ON) + return con->parent; + + if (con->type == CT_WORKSPACE) + return NULL; + + return con_inside_floating(con->parent); +} + /* * Returns the container with the given client window ID or NULL if no such * container exists. diff --git a/src/floating.c b/src/floating.c index a88d498a..c50cf494 100644 --- a/src/floating.c +++ b/src/floating.c @@ -172,6 +172,16 @@ void toggle_floating_mode(Con *con, bool automatic) { floating_enable(con, automatic); } +/* + * Raises the given container in the list of floating containers + * + */ +void floating_raise_con(Con *con) { + DLOG("Raising floating con %p / %s\n", con, con->name); + TAILQ_REMOVE(&(con->parent->floating_head), con, floating_windows); + TAILQ_INSERT_TAIL(&(con->parent->floating_head), con, floating_windows); +} + DRAGGING_CB(drag_window_callback) { struct xcb_button_press_event_t *event = extra; From fb6d117c42ce3d9988ff44c079814b3840b1e37f Mon Sep 17 00:00:00 2001 From: Axel Wagner Date: Thu, 30 Dec 2010 21:09:32 +0100 Subject: [PATCH 350/867] Port sighandler to tree-branch --- Makefile | 2 +- include/all.h | 1 + src/main.c | 6 ++++++ src/sighandler.c | 12 +++++------- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 4f2ab77f..89647f6c 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c -FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c +FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) diff --git a/include/all.h b/include/all.h index 23943b29..3cc28940 100644 --- a/include/all.h +++ b/include/all.h @@ -53,5 +53,6 @@ #include "cmdparse.h" #include "xcursor.h" #include "resize.h" +#include "sighandler.h" #endif diff --git a/src/main.c b/src/main.c index b6923d6a..e30887c9 100644 --- a/src/main.c +++ b/src/main.c @@ -385,6 +385,12 @@ int main(int argc, char *argv[]) { manage_existing_windows(root); + setup_signal_handler(); + + /* Ignore SIGPIPE to survive errors when an IPC client disconnects + * while we are sending him a message */ + signal(SIGPIPE, SIG_IGN); + /* Autostarting exec-lines */ if (autostart) { struct Autostart *exec; diff --git a/src/sighandler.c b/src/sighandler.c index 92cbc5cb..26ed1a68 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -50,17 +50,17 @@ static int crash_text_longest = 1; * Draw the window containing the info text * */ -static int sig_draw_window(xcb_connection_t *conn, xcb_window_t win, int width, int height, int font_height) { +static int sig_draw_window(xcb_window_t win, int width, int height, int font_height) { /* re-draw the background */ xcb_rectangle_t border = { 0, 0, width, height}, inner = { 2, 2, width - 4, height - 4}; - xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000")); + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#FF0000")); xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); - xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#000000")); xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); /* restore font color */ - xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF")); + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#FFFFFF")); for (int i = 0; i < sizeof(crash_text) / sizeof(char*); i++) { int text_len = strlen(crash_text[i]); @@ -156,8 +156,6 @@ void handle_signal(int sig, siginfo_t *info, void *data) { sigaction(sig, &action, NULL); raised_signal = sig; - xcb_connection_t *conn = global_conn; - /* setup event handler for key presses */ xcb_event_handlers_t sig_evenths; memset(&sig_evenths, 0, sizeof(xcb_event_handlers_t)); @@ -200,7 +198,7 @@ void handle_signal(int sig, siginfo_t *info, void *data) { xcb_grab_pointer(conn, false, win, XCB_NONE, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, win, XCB_NONE, XCB_CURRENT_TIME); - sig_draw_window(conn, win, width, height, font->height); + sig_draw_window(win, width, height, font->height); xcb_flush(conn); } From 6ec468ba1e74ff690d42e8184078c2c3be541098 Mon Sep 17 00:00:00 2001 From: Axel Wagner Date: Thu, 30 Dec 2010 21:15:55 +0100 Subject: [PATCH 351/867] Retab sighandler.c --- include/sighandler.h | 2 +- src/sighandler.c | 228 +++++++++++++++++++++---------------------- 2 files changed, 115 insertions(+), 115 deletions(-) diff --git a/include/sighandler.h b/include/sighandler.h index 49317438..02a4f5dd 100644 --- a/include/sighandler.h +++ b/include/sighandler.h @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * diff --git a/src/sighandler.c b/src/sighandler.c index 26ed1a68..1cc4548d 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * @@ -38,11 +38,11 @@ static xcb_pixmap_t pixmap; static int raised_signal; static char *crash_text[] = { - "i3 just crashed.", - "To debug this problem, either attach gdb now", - "or press 'e' to exit and get a core-dump.", - "If you want to keep your session,", - "press 'r' to restart i3 in-place." + "i3 just crashed.", + "To debug this problem, either attach gdb now", + "or press 'e' to exit and get a core-dump.", + "If you want to keep your session,", + "press 'r' to restart i3 in-place." }; static int crash_text_longest = 1; @@ -51,31 +51,31 @@ static int crash_text_longest = 1; * */ static int sig_draw_window(xcb_window_t win, int width, int height, int font_height) { - /* re-draw the background */ - xcb_rectangle_t border = { 0, 0, width, height}, - inner = { 2, 2, width - 4, height - 4}; - xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#FF0000")); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); - xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#000000")); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); + /* re-draw the background */ + xcb_rectangle_t border = { 0, 0, width, height}, + inner = { 2, 2, width - 4, height - 4}; + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#FF0000")); + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#000000")); + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); - /* restore font color */ - xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#FFFFFF")); + /* restore font color */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#FFFFFF")); - for (int i = 0; i < sizeof(crash_text) / sizeof(char*); i++) { - int text_len = strlen(crash_text[i]); - char *full_text = convert_utf8_to_ucs2(crash_text[i], &text_len); - xcb_image_text_16(conn, text_len, pixmap, pixmap_gc, 8 /* X */, - 3 + (i + 1) * font_height /* Y = baseline of font */, - (xcb_char2b_t*)full_text); - free(full_text); - } + for (int i = 0; i < sizeof(crash_text) / sizeof(char*); i++) { + int text_len = strlen(crash_text[i]); + char *full_text = convert_utf8_to_ucs2(crash_text[i], &text_len); + xcb_image_text_16(conn, text_len, pixmap, pixmap_gc, 8 /* X */, + 3 + (i + 1) * font_height /* Y = baseline of font */, + (xcb_char2b_t*)full_text); + free(full_text); + } - /* Copy the contents of the pixmap to the real window */ - xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, width, height); - xcb_flush(conn); + /* Copy the contents of the pixmap to the real window */ + xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, width, height); + xcb_flush(conn); - return 1; + return 1; } /* @@ -83,25 +83,25 @@ static int sig_draw_window(xcb_window_t win, int width, int height, int font_hei * */ static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) { - uint16_t state = event->state; + uint16_t state = event->state; - /* Apparantly, after activating numlock once, the numlock modifier - * stays turned on (use xev(1) to verify). So, to resolve useful - * keysyms, we remove the numlock flag from the event state */ - state &= ~xcb_numlock_mask; + /* Apparantly, after activating numlock once, the numlock modifier + * stays turned on (use xev(1) to verify). So, to resolve useful + * keysyms, we remove the numlock flag from the event state */ + state &= ~xcb_numlock_mask; - xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, state); + xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, state); - if (sym == 'e') { - DLOG("User issued exit-command, raising error again.\n"); - raise(raised_signal); - exit(1); - } + if (sym == 'e') { + DLOG("User issued exit-command, raising error again.\n"); + raise(raised_signal); + exit(1); + } - if (sym == 'r') - i3_restart(); + if (sym == 'r') + i3_restart(); - return 1; + return 1; } /* @@ -109,37 +109,37 @@ static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_p * */ static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, uint32_t width, uint32_t height) { - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; - xcb_window_t win = xcb_generate_id(conn); + xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; + xcb_window_t win = xcb_generate_id(conn); - uint32_t mask = 0; - uint32_t values[2]; + uint32_t mask = 0; + uint32_t values[2]; - mask |= XCB_CW_BACK_PIXEL; - values[0] = 0; + mask |= XCB_CW_BACK_PIXEL; + values[0] = 0; - mask |= XCB_CW_OVERRIDE_REDIRECT; - values[1] = 1; + mask |= XCB_CW_OVERRIDE_REDIRECT; + values[1] = 1; - /* center each popup on the specified screen */ - uint32_t x = screen_rect.x + ((screen_rect.width / 2) - (width / 2)), - y = screen_rect.y + ((screen_rect.height / 2) - (height / 2)); + /* center each popup on the specified screen */ + uint32_t x = screen_rect.x + ((screen_rect.width / 2) - (width / 2)), + y = screen_rect.y + ((screen_rect.height / 2) - (height / 2)); - xcb_create_window(conn, - XCB_COPY_FROM_PARENT, - win, /* the window id */ - root, /* parent == root */ - x, y, width, height, /* dimensions */ - 0, /* border = 0, we draw our own */ - XCB_WINDOW_CLASS_INPUT_OUTPUT, - XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ - mask, - values); + xcb_create_window(conn, + XCB_COPY_FROM_PARENT, + win, /* the window id */ + root, /* parent == root */ + x, y, width, height, /* dimensions */ + 0, /* border = 0, we draw our own */ + XCB_WINDOW_CLASS_INPUT_OUTPUT, + XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ + mask, + values); - /* Map the window (= make it visible) */ - xcb_map_window(conn, win); + /* Map the window (= make it visible) */ + xcb_map_window(conn, win); - return win; + return win; } /* @@ -149,60 +149,60 @@ static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, * */ void handle_signal(int sig, siginfo_t *info, void *data) { - DLOG("i3 crashed. SIG: %d\n", sig); + DLOG("i3 crashed. SIG: %d\n", sig); - struct sigaction action; - action.sa_handler = SIG_DFL; - sigaction(sig, &action, NULL); - raised_signal = sig; + struct sigaction action; + action.sa_handler = SIG_DFL; + sigaction(sig, &action, NULL); + raised_signal = sig; - /* setup event handler for key presses */ - xcb_event_handlers_t sig_evenths; - memset(&sig_evenths, 0, sizeof(xcb_event_handlers_t)); - xcb_event_handlers_init(conn, &sig_evenths); - xcb_event_set_key_press_handler(&sig_evenths, sig_handle_key_press, NULL); + /* setup event handler for key presses */ + xcb_event_handlers_t sig_evenths; + memset(&sig_evenths, 0, sizeof(xcb_event_handlers_t)); + xcb_event_handlers_init(conn, &sig_evenths); + xcb_event_set_key_press_handler(&sig_evenths, sig_handle_key_press, NULL); - i3Font *font = load_font(conn, config.font); + i3Font *font = load_font(conn, config.font); - /* width and height of the popup window, so that the text fits in */ - int crash_text_num = sizeof(crash_text) / sizeof(char*); - int height = 13 + (crash_text_num * font->height); + /* width and height of the popup window, so that the text fits in */ + int crash_text_num = sizeof(crash_text) / sizeof(char*); + int height = 13 + (crash_text_num * font->height); - /* calculate width for longest text */ - int text_len = strlen(crash_text[crash_text_longest]); - char *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len); - int font_width = predict_text_width(conn, config.font, longest_text, text_len); - int width = font_width + 20; + /* calculate width for longest text */ + int text_len = strlen(crash_text[crash_text_longest]); + char *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len); + int font_width = predict_text_width(conn, config.font, longest_text, text_len); + int width = font_width + 20; - /* Open a popup window on each virtual screen */ - Output *screen; - xcb_window_t win; - TAILQ_FOREACH(screen, &outputs, outputs) { - if (!screen->active) - continue; - win = open_input_window(conn, screen->rect, width, height); + /* Open a popup window on each virtual screen */ + Output *screen; + xcb_window_t win; + TAILQ_FOREACH(screen, &outputs, outputs) { + if (!screen->active) + continue; + win = open_input_window(conn, screen->rect, width, height); - /* Create pixmap */ - pixmap = xcb_generate_id(conn); - pixmap_gc = xcb_generate_id(conn); - xcb_create_pixmap(conn, root_depth, pixmap, win, width, height); - xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + /* Create pixmap */ + pixmap = xcb_generate_id(conn); + pixmap_gc = xcb_generate_id(conn); + xcb_create_pixmap(conn, root_depth, pixmap, win, width, height); + xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); - /* Create graphics context */ - xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font->id); + /* Create graphics context */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font->id); - /* Grab the keyboard to get all input */ - xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + /* Grab the keyboard to get all input */ + xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); - /* Grab the cursor inside the popup */ - xcb_grab_pointer(conn, false, win, XCB_NONE, XCB_GRAB_MODE_ASYNC, - XCB_GRAB_MODE_ASYNC, win, XCB_NONE, XCB_CURRENT_TIME); + /* Grab the cursor inside the popup */ + xcb_grab_pointer(conn, false, win, XCB_NONE, XCB_GRAB_MODE_ASYNC, + XCB_GRAB_MODE_ASYNC, win, XCB_NONE, XCB_CURRENT_TIME); - sig_draw_window(win, width, height, font->height); - xcb_flush(conn); - } + sig_draw_window(win, width, height, font->height); + xcb_flush(conn); + } - xcb_event_wait_for_event_loop(&sig_evenths); + xcb_event_wait_for_event_loop(&sig_evenths); } /* @@ -210,13 +210,13 @@ void handle_signal(int sig, siginfo_t *info, void *data) { * */ void setup_signal_handler() { - struct sigaction action; + struct sigaction action; - action.sa_sigaction = handle_signal; - action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; - sigemptyset(&action.sa_mask); + action.sa_sigaction = handle_signal; + action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; + sigemptyset(&action.sa_mask); - if (sigaction(SIGSEGV, &action, NULL) == -1 || - sigaction(SIGFPE, &action, NULL) == -1) - ELOG("Could not setup signal handler"); + if (sigaction(SIGSEGV, &action, NULL) == -1 || + sigaction(SIGFPE, &action, NULL) == -1) + ELOG("Could not setup signal handler"); } From aa422c07c455a73ce1940494d1610d5a3a0aa51e Mon Sep 17 00:00:00 2001 From: Axel Wagner Date: Thu, 30 Dec 2010 21:43:34 +0100 Subject: [PATCH 352/867] Add forgetful restart to sighandler --- include/util.h | 2 +- src/cmdparse.y | 2 +- src/sighandler.c | 14 +++++++++----- src/util.c | 5 +++-- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/include/util.h b/include/util.h index 46a15655..91b533a1 100644 --- a/include/util.h +++ b/include/util.h @@ -122,6 +122,6 @@ bool path_exists(const char *path); * appends -a to argument list to disable autostart * */ -void i3_restart(); +void i3_restart(bool forget_layout); #endif diff --git a/src/cmdparse.y b/src/cmdparse.y index 28cff60c..98819183 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -340,7 +340,7 @@ restart: TOK_RESTART { printf("restarting i3\n"); - i3_restart(); + i3_restart(false); } ; diff --git a/src/sighandler.c b/src/sighandler.c index 1cc4548d..ea2bf9fe 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -40,11 +40,12 @@ static int raised_signal; static char *crash_text[] = { "i3 just crashed.", "To debug this problem, either attach gdb now", - "or press 'e' to exit and get a core-dump.", - "If you want to keep your session,", - "press 'r' to restart i3 in-place." + "or press", + "- 'e' to exit and get a core-dump,", + "- 'r' to restart i3 in-place or", + "- 'f' to forget the current layout and restart" }; -static int crash_text_longest = 1; +static int crash_text_longest = 5; /* * Draw the window containing the info text @@ -99,7 +100,10 @@ static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_p } if (sym == 'r') - i3_restart(); + i3_restart(false); + + if (sym == 'f') + i3_restart(true); return 1; } diff --git a/src/util.c b/src/util.c index b8c314c7..1ecce922 100644 --- a/src/util.c +++ b/src/util.c @@ -321,8 +321,9 @@ char *store_restart_layout() { * appends -a to argument list to disable autostart * */ -void i3_restart() { - char *restart_filename = store_restart_layout(); +void i3_restart(bool forget_layout) { + char *restart_filename = forget_layout ? NULL : store_restart_layout(); + restore_geometry(); ipc_shutdown(); From 3d274cf2f998d6366030ab07ec632a0b80d5c03d Mon Sep 17 00:00:00 2001 From: Axel Wagner Date: Sat, 1 Jan 2011 22:11:44 +0100 Subject: [PATCH 353/867] Add font-option to i3-input --- i3-input/main.c | 8 ++++++-- man/i3-input.man | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/i3-input/main.c b/i3-input/main.c index f5229c08..1010e584 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -251,11 +251,12 @@ int main(int argc, char *argv[]) { {"limit", required_argument, 0, 'l'}, {"prompt", required_argument, 0, 'P'}, {"prefix", required_argument, 0, 'p'}, + {"font", required_argument, 0, 'f'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; - char *options_string = "s:p:P:l:vh"; + char *options_string = "s:p:P:f:l:vh"; while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { switch (o) { @@ -274,9 +275,12 @@ int main(int argc, char *argv[]) { case 'P': prompt = strdup(optarg); break; + case 'f': + pattern = strdup(optarg); + break; case 'h': printf("i3-input " I3_VERSION); - printf("i3-input [-s ] [-p ] [-l ] [-P ] [-v]\n"); + printf("i3-input [-s ] [-p ] [-l ] [-P ] [-f ] [-v]\n"); return 0; } } diff --git a/man/i3-input.man b/man/i3-input.man index 5b7ce6d9..fee4d92e 100644 --- a/man/i3-input.man +++ b/man/i3-input.man @@ -9,7 +9,7 @@ i3-input - interactively take a command for i3 window manager == SYNOPSIS -i3-input [-s ] [-p ] [-l ] [-P ] [-v] +i3-input [-s ] [-p ] [-l ] [-P ] [-f ] [-v] == DESCRIPTION From 0609c1bf3e6ff051b382c006bd1368ea6da081ae Mon Sep 17 00:00:00 2001 From: Axel Wagner Date: Sat, 1 Jan 2011 22:11:44 +0100 Subject: [PATCH 354/867] Add font-option to i3-input --- i3-input/main.c | 8 ++++++-- man/i3-input.man | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/i3-input/main.c b/i3-input/main.c index 2a3f02fd..581386d2 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -267,11 +267,12 @@ int main(int argc, char *argv[]) { {"limit", required_argument, 0, 'l'}, {"prompt", required_argument, 0, 'P'}, {"prefix", required_argument, 0, 'p'}, + {"font", required_argument, 0, 'f'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; - char *options_string = "s:p:P:l:vh"; + char *options_string = "s:p:P:f:l:vh"; while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { switch (o) { @@ -290,9 +291,12 @@ int main(int argc, char *argv[]) { case 'P': prompt = strdup(optarg); break; + case 'f': + pattern = strdup(optarg); + break; case 'h': printf("i3-input " I3_VERSION); - printf("i3-input [-s ] [-p ] [-l ] [-P ] [-v]\n"); + printf("i3-input [-s ] [-p ] [-l ] [-P ] [-f ] [-v]\n"); return 0; } } diff --git a/man/i3-input.man b/man/i3-input.man index 5b7ce6d9..fee4d92e 100644 --- a/man/i3-input.man +++ b/man/i3-input.man @@ -9,7 +9,7 @@ i3-input - interactively take a command for i3 window manager == SYNOPSIS -i3-input [-s ] [-p ] [-l ] [-P ] [-v] +i3-input [-s ] [-p ] [-l ] [-P ] [-f ] [-v] == DESCRIPTION From 9713419327ef6056ae9c03b03a66261a8171704c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Jan 2011 18:08:45 +0100 Subject: [PATCH 355/867] Bugfix: Also change focus when the killed container was focused (Thanks fernandotcl) --- src/tree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index 5b3f0df5..fd0a3ec7 100644 --- a/src/tree.c +++ b/src/tree.c @@ -218,7 +218,7 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { if (!next) return; - if (was_mapped) { + if (was_mapped || con == focused) { DLOG("focusing %p / %s\n", next, next->name); /* TODO: check if the container (or one of its children) was focused */ con_focus(next); From fa44383cc6efac430e0503c4c29dc6145a2a5d5d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Jan 2011 22:15:52 +0100 Subject: [PATCH 356/867] fix small memory leak (unused ->name) --- src/con.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/con.c b/src/con.c index 0f2374f6..57dda39b 100644 --- a/src/con.c +++ b/src/con.c @@ -34,7 +34,6 @@ Con *con_new(Con *parent) { Con *new = scalloc(sizeof(Con)); TAILQ_INSERT_TAIL(&all_cons, new, all_cons); new->type = CT_CON; - new->name = strdup(""); new->border_style = config.default_border; static int cnt = 0; LOG("opening window %d\n", cnt); From bdbda2029335447187273dbfb8f08d40bc97019d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Jan 2011 22:37:50 +0100 Subject: [PATCH 357/867] cmdparse: free strings --- src/cmdparse.y | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cmdparse.y b/src/cmdparse.y index 98819183..cea93d1a 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -315,6 +315,7 @@ exec: { printf("should execute %s\n", $3); start_application($3); + free($3); } ; @@ -540,6 +541,7 @@ move: printf("should move window to workspace %s\n", $5); /* get the workspace */ Con *ws = workspace_get($5); + free($5); /* check if the match is empty, not if the result is empty */ if (match_is_empty(¤t_match)) @@ -563,6 +565,7 @@ restore: { printf("restoring \"%s\"\n", $3); tree_append_json($3); + free($3); } ; From 80ecd157f66e65e727010ca277d3645379bcd14b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Jan 2011 22:38:33 +0100 Subject: [PATCH 358/867] fix memleak: free con->name before overwriting it --- src/tree.c | 3 +++ src/workspace.c | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index fd0a3ec7..5f2c3580 100644 --- a/src/tree.c +++ b/src/tree.c @@ -49,6 +49,7 @@ void tree_init() { Output *output; croot = con_new(NULL); + FREE(croot->name); croot->name = "root"; croot->type = CT_ROOT; @@ -60,6 +61,7 @@ void tree_init() { continue; Con *oc = con_new(croot); + FREE(oc->name); oc->name = strdup(output->name); oc->type = CT_OUTPUT; oc->rect = output->rect; @@ -74,6 +76,7 @@ void tree_init() { ws = con_new(NULL); ws->type = CT_WORKSPACE; ws->num = c; + FREE(ws->name); asprintf(&(ws->name), "%d", c); c++; con_attach(ws, oc, false); diff --git a/src/workspace.c b/src/workspace.c index 9ce28e52..eeb756b1 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -46,7 +46,8 @@ Con *workspace_get(const char *num) { x_set_name(workspace, name); free(name); workspace->type = CT_WORKSPACE; - workspace->name = strdup(num); + FREE(workspace->name); + workspace->name = sstrdup(num); /* We set ->num to the number if this workspace’s name consists only of * a positive number. Otherwise it’s a named ws and num will be -1. */ char *end; From 545566e6ba819c7307902b9cab95567d4caf97f0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Jan 2011 22:39:13 +0100 Subject: [PATCH 359/867] use sstrdup() instead of strdup() --- src/window.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/window.c b/src/window.c index 1cf167d8..6555be37 100644 --- a/src/window.c +++ b/src/window.c @@ -26,9 +26,9 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) { FREE(win->class_instance); FREE(win->class_class); - win->class_instance = strdup(new_class); + win->class_instance = sstrdup(new_class); if ((strlen(new_class) + 1) < xcb_get_property_value_length(prop)) - win->class_class = strdup(new_class + strlen(new_class) + 1); + win->class_class = sstrdup(new_class + strlen(new_class) + 1); else win->class_class = NULL; LOG("WM_CLASS changed to %s (instance), %s (class)\n", win->class_instance, win->class_class); @@ -93,7 +93,7 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop) { FREE(win->name_x); FREE(win->name_json); win->name_x = new_name; - win->name_json = strdup(new_name); + win->name_json = sstrdup(new_name); win->name_len = strlen(new_name); } From bf2c18cc331ddbc16c565fb3939ae55e6bd01070 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Jan 2011 22:39:24 +0100 Subject: [PATCH 360/867] fix memleak: free X state structure --- src/x.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/x.c b/src/x.c index fa003020..bcdb07ca 100644 --- a/src/x.c +++ b/src/x.c @@ -168,6 +168,8 @@ void x_con_kill(Con *con) { state = state_for_frame(con->frame); CIRCLEQ_REMOVE(&state_head, state, state); CIRCLEQ_REMOVE(&old_state_head, state, old_state); + FREE(state->name); + free(state); /* Invalidate focused_id to correctly focus new windows with the same ID */ focused_id = XCB_NONE; From 0416be18df58599d816cac1f1e2e43a98474b1b8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Jan 2011 22:39:45 +0100 Subject: [PATCH 361/867] fix memleak: free struct Window members --- src/tree.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tree.c b/src/tree.c index 5f2c3580..d5305d5b 100644 --- a/src/tree.c +++ b/src/tree.c @@ -193,6 +193,10 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { /* TODO: client_unmap to set state to withdrawn */ } + FREE(con->window->class_class); + FREE(con->window->class_instance); + FREE(con->window->name_x); + FREE(con->window->name_json); free(con->window); } From 1fb9b7c43124286c6a3dd4f2f74aded95c4a00e2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Jan 2011 22:40:05 +0100 Subject: [PATCH 362/867] Bugfix: Correctly change focus after closing floating containers (Thanks litemotiv!) --- src/tree.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/tree.c b/src/tree.c index d5305d5b..0233a125 100644 --- a/src/tree.c +++ b/src/tree.c @@ -173,6 +173,7 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { /* Get the container which is next focused */ Con *next = con_next_focused(con); + DLOG("next = %p, focused = %p\n", next, focused); DLOG("closing %p, kill_window = %d\n", con, kill_window); Con *child; @@ -211,9 +212,18 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { } if (con_is_floating(con)) { + Con *ws = con_get_workspace(con); DLOG("Container was floating, killing floating container\n"); tree_close(parent, false, false); - next = NULL; + DLOG("parent container killed\n"); + if (con == focused) { + DLOG("This is the focused container, i need to find another one to focus. I start looking at ws = %p\n", ws); + next = con_next_focused(ws); + dont_kill_parent = true; + DLOG("Alright, focusing %p\n", next); + } else { + next = NULL; + } } free(con->name); @@ -222,8 +232,10 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { /* in the case of floating windows, we already focused another container * when closing the parent, so we can exit now. */ - if (!next) + if (!next) { + DLOG("No next container, i will just exit now\n"); return; + } if (was_mapped || con == focused) { DLOG("focusing %p / %s\n", next, next->name); From f54ce1ddda7870ca8be9581b4033c8d8e89f1095 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Jan 2011 22:51:42 +0100 Subject: [PATCH 363/867] retab! randr.c --- src/randr.c | 520 ++++++++++++++++++++++++++-------------------------- 1 file changed, 260 insertions(+), 260 deletions(-) diff --git a/src/randr.c b/src/randr.c index 6c63069f..e0ad4eb2 100644 --- a/src/randr.c +++ b/src/randr.c @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * @@ -36,12 +36,12 @@ static bool randr_disabled = false; * */ static Output *get_output_by_id(xcb_randr_output_t id) { - Output *output; - TAILQ_FOREACH(output, &outputs, outputs) - if (output->id == id) - return output; + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->id == id) + return output; - return NULL; + return NULL; } /* @@ -49,13 +49,13 @@ static Output *get_output_by_id(xcb_randr_output_t id) { * */ Output *get_output_by_name(const char *name) { - Output *output; - TAILQ_FOREACH(output, &outputs, outputs) - if (output->active && - strcasecmp(output->name, name) == 0) - return output; + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->active && + strcasecmp(output->name, name) == 0) + return output; - return NULL; + return NULL; } /* @@ -63,13 +63,13 @@ Output *get_output_by_name(const char *name) { * */ Output *get_first_output() { - Output *output; + Output *output; - TAILQ_FOREACH(output, &outputs, outputs) - if (output->active) - return output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->active) + return output; - return NULL; + return NULL; } /* @@ -78,18 +78,18 @@ Output *get_first_output() { * */ Output *get_output_containing(int x, int y) { - Output *output; - TAILQ_FOREACH(output, &outputs, outputs) { - if (!output->active) - continue; - DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n", - x, y, output->rect.x, output->rect.y, output->rect.width, output->rect.height); - if (x >= output->rect.x && x < (output->rect.x + output->rect.width) && - y >= output->rect.y && y < (output->rect.y + output->rect.height)) - return output; - } + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active) + continue; + DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n", + x, y, output->rect.x, output->rect.y, output->rect.width, output->rect.height); + if (x >= output->rect.x && x < (output->rect.x + output->rect.width) && + y >= output->rect.y && y < (output->rect.y + output->rect.height)) + return output; + } - return NULL; + return NULL; } /* @@ -101,43 +101,43 @@ Output *get_output_containing(int x, int y) { * */ Output *get_output_most(direction_t direction, Output *current) { - Output *output, *candidate = NULL; - int position = 0; - TAILQ_FOREACH(output, &outputs, outputs) { - if (!output->active) - continue; + Output *output, *candidate = NULL; + int position = 0; + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active) + continue; - /* Repeated calls of WIN determine the winner of the comparison */ - #define WIN(variable, condition) \ - if (variable condition) { \ - candidate = output; \ - position = variable; \ - } \ - break; + /* Repeated calls of WIN determine the winner of the comparison */ + #define WIN(variable, condition) \ + if (variable condition) { \ + candidate = output; \ + position = variable; \ + } \ + break; - if (((direction == D_UP) || (direction == D_DOWN)) && - (current->rect.x != output->rect.x)) - continue; + if (((direction == D_UP) || (direction == D_DOWN)) && + (current->rect.x != output->rect.x)) + continue; - if (((direction == D_LEFT) || (direction == D_RIGHT)) && - (current->rect.y != output->rect.y)) - continue; + if (((direction == D_LEFT) || (direction == D_RIGHT)) && + (current->rect.y != output->rect.y)) + continue; - switch (direction) { - case D_UP: - WIN(output->rect.y, <= position); - case D_DOWN: - WIN(output->rect.y, >= position); - case D_LEFT: - WIN(output->rect.x, <= position); - case D_RIGHT: - WIN(output->rect.x, >= position); - } + switch (direction) { + case D_UP: + WIN(output->rect.y, <= position); + case D_DOWN: + WIN(output->rect.y, >= position); + case D_LEFT: + WIN(output->rect.x, <= position); + case D_RIGHT: + WIN(output->rect.x, >= position); } + } - assert(candidate != NULL); + assert(candidate != NULL); - return candidate; + return candidate; } #if 0 @@ -197,22 +197,22 @@ void initialize_output(xcb_connection_t *conn, Output *output, Workspace *worksp * */ void disable_randr(xcb_connection_t *conn) { - xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; + xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; - DLOG("RandR extension unusable, disabling.\n"); + DLOG("RandR extension unusable, disabling.\n"); - Output *s = scalloc(sizeof(Output)); + Output *s = scalloc(sizeof(Output)); - s->active = true; - s->rect.x = 0; - s->rect.y = 0; - s->rect.width = root_screen->width_in_pixels; - s->rect.height = root_screen->height_in_pixels; - s->name = "xroot-0"; + s->active = true; + s->rect.x = 0; + s->rect.y = 0; + s->rect.width = root_screen->width_in_pixels; + s->rect.height = root_screen->height_in_pixels; + s->name = "xroot-0"; - TAILQ_INSERT_TAIL(&outputs, s, outputs); + TAILQ_INSERT_TAIL(&outputs, s, outputs); - randr_disabled = true; + randr_disabled = true; } /* @@ -227,15 +227,15 @@ void disable_randr(xcb_connection_t *conn) { * */ static void output_change_mode(xcb_connection_t *conn, Output *output) { - i3Font *font = load_font(conn, config.font); + i3Font *font = load_font(conn, config.font); - DLOG("Output mode changed, reconfiguring bar, updating workspaces\n"); - Rect bar_rect = {output->rect.x, - output->rect.y + output->rect.height - (font->height + 6), - output->rect.x + output->rect.width, - font->height + 6}; + DLOG("Output mode changed, reconfiguring bar, updating workspaces\n"); + Rect bar_rect = {output->rect.x, + output->rect.y + output->rect.height - (font->height + 6), + output->rect.x + output->rect.width, + font->height + 6}; - xcb_set_window_rect(conn, output->bar, bar_rect); + xcb_set_window_rect(conn, output->bar, bar_rect); #if 0 /* go through all workspaces and set force_reconfigure */ @@ -286,65 +286,65 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, xcb_randr_get_output_info_reply_t *output, xcb_timestamp_t cts, resources_reply *res) { - /* each CRT controller has a position in which we are interested in */ - crtc_info *crtc; + /* each CRT controller has a position in which we are interested in */ + crtc_info *crtc; - Output *new = get_output_by_id(id); - bool existing = (new != NULL); + Output *new = get_output_by_id(id); + bool existing = (new != NULL); + if (!existing) + new = scalloc(sizeof(Output)); + new->id = id; + FREE(new->name); + asprintf(&new->name, "%.*s", + xcb_randr_get_output_info_name_length(output), + xcb_randr_get_output_info_name(output)); + + DLOG("found output with name %s\n", new->name); + + /* Even if no CRTC is used at the moment, we store the output so that + * we do not need to change the list ever again (we only update the + * position/size) */ + if (output->crtc == XCB_NONE) { if (!existing) - new = scalloc(sizeof(Output)); - new->id = id; - FREE(new->name); - asprintf(&new->name, "%.*s", - xcb_randr_get_output_info_name_length(output), - xcb_randr_get_output_info_name(output)); + TAILQ_INSERT_TAIL(&outputs, new, outputs); + else if (new->active) + new->to_be_disabled = true; + return; + } - DLOG("found output with name %s\n", new->name); + xcb_randr_get_crtc_info_cookie_t icookie; + icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts); + if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) { + DLOG("Skipping output %s: could not get CRTC (%p)\n", + new->name, crtc); + free(new); + return; + } - /* Even if no CRTC is used at the moment, we store the output so that - * we do not need to change the list ever again (we only update the - * position/size) */ - if (output->crtc == XCB_NONE) { - if (!existing) - TAILQ_INSERT_TAIL(&outputs, new, outputs); - else if (new->active) - new->to_be_disabled = true; - return; - } + bool updated = update_if_necessary(&(new->rect.x), crtc->x) | + update_if_necessary(&(new->rect.y), crtc->y) | + update_if_necessary(&(new->rect.width), crtc->width) | + update_if_necessary(&(new->rect.height), crtc->height); + free(crtc); + new->active = (new->rect.width != 0 && new->rect.height != 0); + if (!new->active) { + DLOG("width/height 0/0, disabling output\n"); + return; + } - xcb_randr_get_crtc_info_cookie_t icookie; - icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts); - if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) { - DLOG("Skipping output %s: could not get CRTC (%p)\n", - new->name, crtc); - free(new); - return; - } + DLOG("mode: %dx%d+%d+%d\n", new->rect.width, new->rect.height, + new->rect.x, new->rect.y); - bool updated = update_if_necessary(&(new->rect.x), crtc->x) | - update_if_necessary(&(new->rect.y), crtc->y) | - update_if_necessary(&(new->rect.width), crtc->width) | - update_if_necessary(&(new->rect.height), crtc->height); - free(crtc); - new->active = (new->rect.width != 0 && new->rect.height != 0); - if (!new->active) { - DLOG("width/height 0/0, disabling output\n"); - return; - } + /* If we don’t need to change an existing output or if the output + * does not exist in the first place, the case is simple: we either + * need to insert the new output or we are done. */ + if (!updated || !existing) { + if (!existing) + TAILQ_INSERT_TAIL(&outputs, new, outputs); + return; + } - DLOG("mode: %dx%d+%d+%d\n", new->rect.width, new->rect.height, - new->rect.x, new->rect.y); - - /* If we don’t need to change an existing output or if the output - * does not exist in the first place, the case is simple: we either - * need to insert the new output or we are done. */ - if (!updated || !existing) { - if (!existing) - TAILQ_INSERT_TAIL(&outputs, new, outputs); - return; - } - - new->changed = true; + new->changed = true; } /* @@ -352,145 +352,145 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, * */ void randr_query_outputs() { - Output *output, *other, *first; - xcb_randr_get_screen_resources_current_cookie_t rcookie; - resources_reply *res; - /* timestamp of the configuration so that we get consistent replies to all - * requests (if the configuration changes between our different calls) */ - xcb_timestamp_t cts; + Output *output, *other, *first; + xcb_randr_get_screen_resources_current_cookie_t rcookie; + resources_reply *res; + /* timestamp of the configuration so that we get consistent replies to all + * requests (if the configuration changes between our different calls) */ + xcb_timestamp_t cts; - /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */ - xcb_randr_output_t *randr_outputs; + /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */ + xcb_randr_output_t *randr_outputs; - if (randr_disabled) - return; + if (randr_disabled) + return; - /* Get screen resources (crtcs, outputs, modes) */ - rcookie = xcb_randr_get_screen_resources_current(conn, root); - if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) { - disable_randr(conn); - return; + /* Get screen resources (crtcs, outputs, modes) */ + rcookie = xcb_randr_get_screen_resources_current(conn, root); + if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) { + disable_randr(conn); + return; + } + cts = res->config_timestamp; + + int len = xcb_randr_get_screen_resources_current_outputs_length(res); + randr_outputs = xcb_randr_get_screen_resources_current_outputs(res); + + /* Request information for each output */ + xcb_randr_get_output_info_cookie_t ocookie[len]; + for (int i = 0; i < len; i++) + ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts); + + /* Loop through all outputs available for this X11 screen */ + for (int i = 0; i < len; i++) { + xcb_randr_get_output_info_reply_t *output; + + if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) + continue; + + handle_output(conn, randr_outputs[i], output, cts, res); + free(output); + } + + free(res); + /* Check for clones, disable the clones and reduce the mode to the + * lowest common mode */ + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active || output->to_be_disabled) + continue; + DLOG("output %p, position (%d, %d), checking for clones\n", + output, output->rect.x, output->rect.y); + + for (other = output; + other != TAILQ_END(&outputs); + other = TAILQ_NEXT(other, outputs)) { + if (other == output || !other->active || other->to_be_disabled) + continue; + + if (other->rect.x != output->rect.x || + other->rect.y != output->rect.y) + continue; + + DLOG("output %p has the same position, his mode = %d x %d\n", + other, other->rect.width, other->rect.height); + uint32_t width = min(other->rect.width, output->rect.width); + uint32_t height = min(other->rect.height, output->rect.height); + + if (update_if_necessary(&(output->rect.width), width) | + update_if_necessary(&(output->rect.height), height)) + output->changed = true; + + update_if_necessary(&(other->rect.width), width); + update_if_necessary(&(other->rect.height), height); + + DLOG("disabling output %p (%s)\n", other, other->name); + other->to_be_disabled = true; + + DLOG("new output mode %d x %d, other mode %d x %d\n", + output->rect.width, output->rect.height, + other->rect.width, other->rect.height); } - cts = res->config_timestamp; + } - int len = xcb_randr_get_screen_resources_current_outputs_length(res); - randr_outputs = xcb_randr_get_screen_resources_current_outputs(res); + /* Handle outputs which have a new mode or are disabled now (either + * because the user disabled them or because they are clones) */ + TAILQ_FOREACH(output, &outputs, outputs) { + if (output->to_be_disabled) { + output->active = false; + DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name); - /* Request information for each output */ - xcb_randr_get_output_info_cookie_t ocookie[len]; - for (int i = 0; i < len; i++) - ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts); + if ((first = get_first_output()) == NULL) + die("No usable outputs available\n"); - /* Loop through all outputs available for this X11 screen */ - for (int i = 0; i < len; i++) { - xcb_randr_get_output_info_reply_t *output; - - if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) - continue; - - handle_output(conn, randr_outputs[i], output, cts, res); - free(output); - } - - free(res); - /* Check for clones, disable the clones and reduce the mode to the - * lowest common mode */ - TAILQ_FOREACH(output, &outputs, outputs) { - if (!output->active || output->to_be_disabled) - continue; - DLOG("output %p, position (%d, %d), checking for clones\n", - output, output->rect.x, output->rect.y); - - for (other = output; - other != TAILQ_END(&outputs); - other = TAILQ_NEXT(other, outputs)) { - if (other == output || !other->active || other->to_be_disabled) - continue; - - if (other->rect.x != output->rect.x || - other->rect.y != output->rect.y) - continue; - - DLOG("output %p has the same position, his mode = %d x %d\n", - other, other->rect.width, other->rect.height); - uint32_t width = min(other->rect.width, output->rect.width); - uint32_t height = min(other->rect.height, output->rect.height); - - if (update_if_necessary(&(output->rect.width), width) | - update_if_necessary(&(output->rect.height), height)) - output->changed = true; - - update_if_necessary(&(other->rect.width), width); - update_if_necessary(&(other->rect.height), height); - - DLOG("disabling output %p (%s)\n", other, other->name); - other->to_be_disabled = true; - - DLOG("new output mode %d x %d, other mode %d x %d\n", - output->rect.width, output->rect.height, - other->rect.width, other->rect.height); - } - } - - /* Handle outputs which have a new mode or are disabled now (either - * because the user disabled them or because they are clones) */ - TAILQ_FOREACH(output, &outputs, outputs) { - if (output->to_be_disabled) { - output->active = false; - DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name); - - if ((first = get_first_output()) == NULL) - die("No usable outputs available\n"); - - //bool needs_init = (first->current_workspace == NULL); + //bool needs_init = (first->current_workspace == NULL); #if 0 - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->output != output) - continue; + TAILQ_FOREACH(ws, workspaces, workspaces) { + if (ws->output != output) + continue; - workspace_assign_to(ws, first, true); - if (!needs_init) - continue; - //initialize_output(conn, first, ws); - needs_init = false; - } + workspace_assign_to(ws, first, true); + if (!needs_init) + continue; + //initialize_output(conn, first, ws); + needs_init = false; + } - Client *dock; - while (!SLIST_EMPTY(&(output->dock_clients))) { - dock = SLIST_FIRST(&(output->dock_clients)); - SLIST_REMOVE_HEAD(&(output->dock_clients), dock_clients); - SLIST_INSERT_HEAD(&(first->dock_clients), dock, dock_clients); - } + Client *dock; + while (!SLIST_EMPTY(&(output->dock_clients))) { + dock = SLIST_FIRST(&(output->dock_clients)); + SLIST_REMOVE_HEAD(&(output->dock_clients), dock_clients); + SLIST_INSERT_HEAD(&(first->dock_clients), dock, dock_clients); + } #endif - //output->current_workspace = NULL; - output->to_be_disabled = false; - } else if (output->changed) { - output_change_mode(conn, output); - output->changed = false; - } + //output->current_workspace = NULL; + output->to_be_disabled = false; + } else if (output->changed) { + output_change_mode(conn, output); + output->changed = false; } + } - if (TAILQ_EMPTY(&outputs)) { - ELOG("No outputs found via RandR, disabling\n"); - disable_randr(conn); - } + if (TAILQ_EMPTY(&outputs)) { + ELOG("No outputs found via RandR, disabling\n"); + disable_randr(conn); + } - //ewmh_update_workarea(); + //ewmh_update_workarea(); #if 0 - /* Just go through each active output and associate one workspace */ - TAILQ_FOREACH(output, &outputs, outputs) { - if (!output->active || output->current_workspace != NULL) - continue; - ws = get_first_workspace_for_output(output); - initialize_output(conn, output, ws); - } + /* Just go through each active output and associate one workspace */ + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active || output->current_workspace != NULL) + continue; + ws = get_first_workspace_for_output(output); + initialize_output(conn, output, ws); + } #endif - /* render_layout flushes */ - tree_render(); + /* render_layout flushes */ + tree_render(); } /* @@ -499,21 +499,21 @@ void randr_query_outputs() { * */ void randr_init(int *event_base) { - const xcb_query_extension_reply_t *extreply; + const xcb_query_extension_reply_t *extreply; - extreply = xcb_get_extension_data(conn, &xcb_randr_id); - if (!extreply->present) - disable_randr(conn); - else randr_query_outputs(conn); + extreply = xcb_get_extension_data(conn, &xcb_randr_id); + if (!extreply->present) + disable_randr(conn); + else randr_query_outputs(conn); - if (event_base != NULL) - *event_base = extreply->first_event; + if (event_base != NULL) + *event_base = extreply->first_event; - xcb_randr_select_input(conn, root, - XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE | - XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE | - XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE | - XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY); + xcb_randr_select_input(conn, root, + XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE | + XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE | + XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE | + XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY); - xcb_flush(conn); + xcb_flush(conn); } From 5098e45f234d9fd49af43f82c32fcbc9be9e1a60 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 5 Jan 2011 00:16:10 +0100 Subject: [PATCH 364/867] Re-Implement support for RandR changes --- include/data.h | 2 + include/handlers.h | 2 +- include/randr.h | 10 ++++ src/handlers.c | 10 ++-- src/main.c | 29 +++++----- src/randr.c | 136 ++++++++++++++++++++++++++++++++++++--------- src/tree.c | 45 +-------------- src/xinerama.c | 3 +- 8 files changed, 147 insertions(+), 90 deletions(-) diff --git a/include/data.h b/include/data.h index 6f4a4738..6174e4e4 100644 --- a/include/data.h +++ b/include/data.h @@ -202,6 +202,7 @@ struct xoutput { /** x, y, width, height */ Rect rect; +#if 0 /** The bar window */ xcb_window_t bar; xcb_gcontext_t bargc; @@ -209,6 +210,7 @@ struct xoutput { /** Contains all clients with _NET_WM_WINDOW_TYPE == * _NET_WM_WINDOW_TYPE_DOCK */ SLIST_HEAD(dock_clients_head, Client) dock_clients; +#endif TAILQ_ENTRY(xoutput) outputs; }; diff --git a/include/handlers.h b/include/handlers.h index 1f2e4b18..2d592e64 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -71,6 +71,7 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, * */ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event); +#endif /** * Gets triggered upon a RandR screen change event, that is when the user @@ -79,7 +80,6 @@ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_n */ int handle_screen_change(void *prophs, xcb_connection_t *conn, xcb_generic_event_t *e); -#endif /** * Configure requests are received when the application wants to resize diff --git a/include/randr.h b/include/randr.h index 7a501b8e..779a1316 100644 --- a/include/randr.h +++ b/include/randr.h @@ -31,6 +31,16 @@ void randr_init(int *event_base); */ void disable_randr(xcb_connection_t *conn); +/** + * Initializes a CT_OUTPUT Con (searches existing ones from inplace restart + * before) to use for the given Output. + * + * XXX: for assignments, we probably need to move workspace creation from here + * to after the loop in randr_query_outputs(). + * + */ +void output_init_con(Output *output); + /** * Initializes the specified output, assigning the specified workspace to it. * diff --git a/src/handlers.c b/src/handlers.c index ed59f528..21ca4e3b 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -428,6 +428,7 @@ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_n return 1; } +#endif /* * Gets triggered upon a RandR screen change event, that is when the user @@ -436,15 +437,14 @@ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_n */ int handle_screen_change(void *prophs, xcb_connection_t *conn, xcb_generic_event_t *e) { - DLOG("RandR screen change\n"); + DLOG("RandR screen change\n"); - randr_query_outputs(conn); + randr_query_outputs(); - ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}"); + ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}"); - return 1; + return 1; } -#endif /* * Our window decorations were unmapped. That means, the window will be killed diff --git a/src/main.c b/src/main.c index e30887c9..ea7f82f2 100644 --- a/src/main.c +++ b/src/main.c @@ -325,21 +325,6 @@ int main(int argc, char *argv[]) { translate_keysyms(); grab_all_keys(conn, false); - int randr_base; - if (force_xinerama) { - xinerama_init(); - } else { - DLOG("Checking for XRandR...\n"); - randr_init(&randr_base); - -#if 0 - xcb_event_set_handler(&evenths, - randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY, - handle_screen_change, - NULL); -#endif - } - bool needs_tree_init = true; if (layout_path) { LOG("Trying to restore the layout from %s...", layout_path); @@ -350,6 +335,20 @@ int main(int argc, char *argv[]) { } if (needs_tree_init) tree_init(); + + int randr_base; + if (force_xinerama) { + xinerama_init(); + } else { + DLOG("Checking for XRandR...\n"); + randr_init(&randr_base); + + xcb_event_set_handler(&evenths, + randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY, + handle_screen_change, + NULL); + } + tree_render(); struct ev_loop *loop = ev_loop_new(0); diff --git a/src/randr.c b/src/randr.c index e0ad4eb2..999181ef 100644 --- a/src/randr.c +++ b/src/randr.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009-2010 Michael Stapelberg and contributors + * © 2009-2011 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -215,6 +215,74 @@ void disable_randr(xcb_connection_t *conn) { randr_disabled = true; } +/* + * Initializes a CT_OUTPUT Con (searches existing ones from inplace restart + * before) to use for the given Output. + * + * XXX: for assignments, we probably need to move workspace creation from here + * to after the loop in randr_query_outputs(). + * + */ +void output_init_con(Output *output) { + Con *con = NULL, *current; + bool reused = false; + static int c = 1; + + DLOG("init_con for output %s\n", output->name); + + /* Search for a Con with that name directly below the root node. There + * might be one from a restored layout. */ + TAILQ_FOREACH(current, &(croot->nodes_head), nodes) { + if (strcmp(current->name, output->name) != 0) + continue; + + con = current; + reused = true; + DLOG("Using existing con %p / %s\n", con, con->name); + break; + } + + if (con == NULL) { + con = con_new(croot); + FREE(con->name); + con->name = sstrdup(output->name); + con->type = CT_OUTPUT; + } + con->rect = output->rect; + output->con = con; + + char *name; + asprintf(&name, "[i3 con] output %s", con->name); + x_set_name(con, name); + free(name); + + if (reused) { + DLOG("Not adding workspace, this was a reused con\n"); + return; + } + DLOG("Now adding a workspace\n"); + + /* add a workspace to this output */ + Con *ws = con_new(NULL); + ws->type = CT_WORKSPACE; + /* TODO: don't just number workspaces, but get the next assigned one / unused one */ + ws->num = c; + FREE(ws->name); + asprintf(&(ws->name), "%d", c); + c++; + con_attach(ws, con, false); + + asprintf(&name, "[i3 con] workspace %s", ws->name); + x_set_name(ws, name); + free(name); + + ws->fullscreen_mode = CF_OUTPUT; + ws->orientation = HORIZ; + + /* TODO: Set focus in main.c */ + con_focus(ws); +} + /* * This function needs to be called when changing the mode of an output when * it already has some workspaces (or a bar window) assigned. @@ -227,9 +295,12 @@ void disable_randr(xcb_connection_t *conn) { * */ static void output_change_mode(xcb_connection_t *conn, Output *output) { - i3Font *font = load_font(conn, config.font); + //i3Font *font = load_font(conn, config.font); - DLOG("Output mode changed, reconfiguring bar, updating workspaces\n"); + DLOG("Output mode changed, updating rect\n"); + assert(output->con != NULL); + output->con->rect = output->rect; +#if 0 Rect bar_rect = {output->rect.x, output->rect.y + output->rect.height - (font->height + 6), output->rect.x + output->rect.width, @@ -237,7 +308,6 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { xcb_set_window_rect(conn, output->bar, bar_rect); -#if 0 /* go through all workspaces and set force_reconfigure */ TAILQ_FOREACH(ws, workspaces, workspaces) { if (ws->output != output) @@ -442,31 +512,47 @@ void randr_query_outputs() { if ((first = get_first_output()) == NULL) die("No usable outputs available\n"); - //bool needs_init = (first->current_workspace == NULL); - -#if 0 - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->output != output) - continue; - - workspace_assign_to(ws, first, true); - if (!needs_init) - continue; - //initialize_output(conn, first, ws); - needs_init = false; + /* We need to move the workspaces from the disappearing output to the first output */ + /* 1: Get the con to focus next, if the disappearing ws is focused */ + Con *next = NULL; + if (TAILQ_FIRST(&(croot->focus_head)) == output->con) { + DLOG("This output (%p) was focused! Getting next\n", output->con); + next = con_next_focused(output->con); + DLOG("next = %p\n", next); } - Client *dock; - while (!SLIST_EMPTY(&(output->dock_clients))) { - dock = SLIST_FIRST(&(output->dock_clients)); - SLIST_REMOVE_HEAD(&(output->dock_clients), dock_clients); - SLIST_INSERT_HEAD(&(first->dock_clients), dock, dock_clients); + /* 2: iterate through workspaces and re-assign them */ + Con *current; + while (!TAILQ_EMPTY(&(output->con->nodes_head))) { + current = TAILQ_FIRST(&(output->con->nodes_head)); + DLOG("Detaching current = %p / %s\n", current, current->name); + con_detach(current); + DLOG("Re-attaching current = %p / %s\n", current, current->name); + con_attach(current, first->con, false); + DLOG("Done, next\n"); + } + DLOG("re-attached all workspaces\n"); + + if (next) { + DLOG("now focusing next = %p\n", next); + con_focus(next); } -#endif - //output->current_workspace = NULL; + DLOG("destroying disappearing con %p\n", output->con); + tree_close(output->con, false, true); + DLOG("Done. Should be fine now\n"); + output->con = NULL; + output->to_be_disabled = false; - } else if (output->changed) { + } + + if (output->active && output->con == NULL) { + DLOG("Need to initialize a Con for output %s\n", output->name); + output_init_con(output); + output->changed = false; + } + + if (output->changed) { output_change_mode(conn, output); output->changed = false; } @@ -504,7 +590,7 @@ void randr_init(int *event_base) { extreply = xcb_get_extension_data(conn, &xcb_randr_id); if (!extreply->present) disable_randr(conn); - else randr_query_outputs(conn); + else randr_query_outputs(); if (event_base != NULL) *event_base = extreply->first_event; diff --git a/src/tree.c b/src/tree.c index 0233a125..dfe5f7be 100644 --- a/src/tree.c +++ b/src/tree.c @@ -40,56 +40,15 @@ bool tree_restore(const char *path) { } /* - * Initializes the tree by creating the root node, adding all RandR outputs - * to the tree (that means randr_init() has to be called before) and - * assigning a workspace to each RandR output. + * Initializes the tree by creating the root node. The CT_OUTPUT Cons below the + * root node are created in randr.c for each Output. * */ void tree_init() { - Output *output; - croot = con_new(NULL); FREE(croot->name); croot->name = "root"; croot->type = CT_ROOT; - - Con *ws; - int c = 1; - /* add the outputs */ - TAILQ_FOREACH(output, &outputs, outputs) { - if (!output->active) - continue; - - Con *oc = con_new(croot); - FREE(oc->name); - oc->name = strdup(output->name); - oc->type = CT_OUTPUT; - oc->rect = output->rect; - output->con = oc; - - char *name; - asprintf(&name, "[i3 con] output %s", oc->name); - x_set_name(oc, name); - free(name); - - /* add a workspace to this output */ - ws = con_new(NULL); - ws->type = CT_WORKSPACE; - ws->num = c; - FREE(ws->name); - asprintf(&(ws->name), "%d", c); - c++; - con_attach(ws, oc, false); - - asprintf(&name, "[i3 con] workspace %s", ws->name); - x_set_name(ws, name); - free(name); - - ws->fullscreen_mode = CF_OUTPUT; - ws->orientation = HORIZ; - } - - con_focus(ws); } /* diff --git a/src/xinerama.c b/src/xinerama.c index 369e8ab7..8bb1b43f 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009-2010 Michael Stapelberg and contributors + * © 2009-2011 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -70,6 +70,7 @@ static void query_screens(xcb_connection_t *conn) { if (s->rect.x == 0 && s->rect.y == 0) TAILQ_INSERT_HEAD(&outputs, s, outputs); else TAILQ_INSERT_TAIL(&outputs, s, outputs); + output_init_con(s); num_screens++; } From f73c02ce92eb2dc47e9907553a7f9d4d4517767a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 5 Jan 2011 00:19:51 +0100 Subject: [PATCH 365/867] Also initialize output->con when using neither RandR nor Xinerama --- src/randr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/randr.c b/src/randr.c index 999181ef..5da9ab2c 100644 --- a/src/randr.c +++ b/src/randr.c @@ -209,6 +209,7 @@ void disable_randr(xcb_connection_t *conn) { s->rect.width = root_screen->width_in_pixels; s->rect.height = root_screen->height_in_pixels; s->name = "xroot-0"; + output_init_con(s); TAILQ_INSERT_TAIL(&outputs, s, outputs); From 2312187439d8efcc656023d57dde351f0700a7c8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 5 Jan 2011 00:26:23 +0100 Subject: [PATCH 366/867] RandR: use the next unused workspace instead of fixed counting --- src/randr.c | 31 ++++++++++++++++++++++++++----- src/workspace.c | 2 +- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/randr.c b/src/randr.c index 5da9ab2c..26f9102b 100644 --- a/src/randr.c +++ b/src/randr.c @@ -227,7 +227,6 @@ void disable_randr(xcb_connection_t *conn) { void output_init_con(Output *output) { Con *con = NULL, *current; bool reused = false; - static int c = 1; DLOG("init_con for output %s\n", output->name); @@ -266,11 +265,33 @@ void output_init_con(Output *output) { /* add a workspace to this output */ Con *ws = con_new(NULL); ws->type = CT_WORKSPACE; - /* TODO: don't just number workspaces, but get the next assigned one / unused one */ + + /* get the next unused workspace number */ + DLOG("Getting next unused workspace\n"); + int c = 0; + bool exists = true; + while (exists) { + Con *out, *current; + + c++; + + FREE(ws->name); + asprintf(&(ws->name), "%d", c); + + exists = false; + TAILQ_FOREACH(out, &(croot->nodes_head), nodes) { + TAILQ_FOREACH(current, &(out->nodes_head), nodes) { + if (strcasecmp(current->name, ws->name) != 0) + continue; + + exists = true; + break; + } + } + + DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists); + } ws->num = c; - FREE(ws->name); - asprintf(&(ws->name), "%d", c); - c++; con_attach(ws, con, false); asprintf(&name, "[i3 con] workspace %s", ws->name); diff --git a/src/workspace.c b/src/workspace.c index eeb756b1..b5095d11 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -26,7 +26,7 @@ Con *workspace_get(const char *num) { TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { TAILQ_FOREACH(current, &(output->nodes_head), nodes) { if (strcasecmp(current->name, num) != 0) - continue; + continue; workspace = current; break; From 55b6d31e4ab1a79e4acb7e4d53e531fc1bc9b9da Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 6 Jan 2011 14:35:04 +0100 Subject: [PATCH 367/867] =?UTF-8?q?Bugfix:=20randr:=20Don=E2=80=99t=20clos?= =?UTF-8?q?e=20container=20if=20it=20was=20not=20initialized=20before?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/randr.c | 56 +++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/randr.c b/src/randr.c index 26f9102b..43a73442 100644 --- a/src/randr.c +++ b/src/randr.c @@ -534,36 +534,38 @@ void randr_query_outputs() { if ((first = get_first_output()) == NULL) die("No usable outputs available\n"); - /* We need to move the workspaces from the disappearing output to the first output */ - /* 1: Get the con to focus next, if the disappearing ws is focused */ - Con *next = NULL; - if (TAILQ_FIRST(&(croot->focus_head)) == output->con) { - DLOG("This output (%p) was focused! Getting next\n", output->con); - next = con_next_focused(output->con); - DLOG("next = %p\n", next); - } + if (output->con != NULL) { + /* We need to move the workspaces from the disappearing output to the first output */ + /* 1: Get the con to focus next, if the disappearing ws is focused */ + Con *next = NULL; + if (TAILQ_FIRST(&(croot->focus_head)) == output->con) { + DLOG("This output (%p) was focused! Getting next\n", output->con); + next = con_next_focused(output->con); + DLOG("next = %p\n", next); + } - /* 2: iterate through workspaces and re-assign them */ - Con *current; - while (!TAILQ_EMPTY(&(output->con->nodes_head))) { - current = TAILQ_FIRST(&(output->con->nodes_head)); - DLOG("Detaching current = %p / %s\n", current, current->name); - con_detach(current); - DLOG("Re-attaching current = %p / %s\n", current, current->name); - con_attach(current, first->con, false); - DLOG("Done, next\n"); - } - DLOG("re-attached all workspaces\n"); + /* 2: iterate through workspaces and re-assign them */ + Con *current; + while (!TAILQ_EMPTY(&(output->con->nodes_head))) { + current = TAILQ_FIRST(&(output->con->nodes_head)); + DLOG("Detaching current = %p / %s\n", current, current->name); + con_detach(current); + DLOG("Re-attaching current = %p / %s\n", current, current->name); + con_attach(current, first->con, false); + DLOG("Done, next\n"); + } + DLOG("re-attached all workspaces\n"); - if (next) { - DLOG("now focusing next = %p\n", next); - con_focus(next); - } + if (next) { + DLOG("now focusing next = %p\n", next); + con_focus(next); + } - DLOG("destroying disappearing con %p\n", output->con); - tree_close(output->con, false, true); - DLOG("Done. Should be fine now\n"); - output->con = NULL; + DLOG("destroying disappearing con %p\n", output->con); + tree_close(output->con, false, true); + DLOG("Done. Should be fine now\n"); + output->con = NULL; + } output->to_be_disabled = false; } From 5ccd7b01e72fef5f9ab4b04880681ea94cbb2bd0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 7 Jan 2011 02:50:35 +0100 Subject: [PATCH 368/867] Bugfix: fix fullscreen mode for floating windows --- src/con.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/con.c b/src/con.c index 57dda39b..a2597b88 100644 --- a/src/con.c +++ b/src/con.c @@ -273,6 +273,12 @@ Con *con_get_fullscreen_con(Con *con) { entry->con = child; TAILQ_INSERT_TAIL(&bfs_head, entry, entries); } + + TAILQ_FOREACH(child, &(current->floating_head), floating_windows) { + entry = smalloc(sizeof(struct bfs_entry)); + entry->con = child; + TAILQ_INSERT_TAIL(&bfs_head, entry, entries); + } } return NULL; From 23b4271e1ca17d269c0f7a06b435ea25c51f4863 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 7 Jan 2011 03:01:58 +0100 Subject: [PATCH 369/867] fix enum value --- include/data.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/data.h b/include/data.h index 6174e4e4..91f4afcd 100644 --- a/include/data.h +++ b/include/data.h @@ -42,7 +42,7 @@ typedef struct Window i3Window; *****************************************************************************/ typedef enum { D_LEFT, D_RIGHT, D_UP, D_DOWN } direction_t; typedef enum { NO_ORIENTATION = 0, HORIZ, VERT } orientation_t; -typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_1PIXEL = 3 } border_style_t; +typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_1PIXEL = 2 } border_style_t; enum { BIND_NONE = 0, From 186d2c7bfa7f0eceaa361f95914104e97f123d6a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 7 Jan 2011 20:48:01 +0100 Subject: [PATCH 370/867] ipc: change 'orientation' to human readable string instead of raw integer value --- src/ipc.c | 12 +++++++++++- testcases/t/22-split.t | 10 +++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index f953f9ae..a0dd64cc 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -178,7 +178,17 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { y(integer, con->type); ystr("orientation"); - y(integer, con->orientation); + switch (con->orientation) { + case NO_ORIENTATION: + ystr("none"); + break; + case HORIZ: + ystr("horizontal"); + break; + case VERT: + ystr("vertical"); + break; + } ystr("percent"); y(double, con->percent); diff --git a/testcases/t/22-split.t b/testcases/t/22-split.t index fdf7bb21..dde9cba1 100644 --- a/testcases/t/22-split.t +++ b/testcases/t/22-split.t @@ -12,10 +12,10 @@ my $tmp = get_unused_workspace(); $i3->command("workspace $tmp")->recv; my $ws = get_ws($tmp); -is($ws->{orientation}, 1, 'orientation horizontal by default'); +is($ws->{orientation}, 'horizontal', 'orientation horizontal by default'); $i3->command('split v')->recv; $ws = get_ws($tmp); -is($ws->{orientation}, 2, 'split v changes workspace orientation'); +is($ws->{orientation}, 'vertical', 'split v changes workspace orientation'); ###################################################################### # Open two containers, split, open another container. Then verify @@ -45,7 +45,7 @@ $second = $content->[1]; is(@{$first->{nodes}}, 0, 'first container has no children'); isnt($second->{name}, $old_name, 'second container was replaced'); -is($second->{orientation}, 1, 'orientation is horizontal'); +is($second->{orientation}, 'horizontal', 'orientation is horizontal'); is(@{$second->{nodes}}, 2, 'second container has 2 children'); is($second->{nodes}->[0]->{name}, $old_name, 'found old second container'); @@ -61,10 +61,10 @@ $tmp = get_unused_workspace(); $i3->command("workspace $tmp")->recv; $ws = get_ws($tmp); -is($ws->{orientation}, 1, 'orientation horizontal by default'); +is($ws->{orientation}, 'horizontal', 'orientation horizontal by default'); $i3->command('split v')->recv; $ws = get_ws($tmp); -is($ws->{orientation}, 2, 'split v changes workspace orientation'); +is($ws->{orientation}, 'vertical', 'split v changes workspace orientation'); $i3->command('open')->recv; my @content = @{get_ws_content($tmp)}; From 228b5c51ff012913ed159ac417864aa5e6e8e923 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 7 Jan 2011 20:58:58 +0100 Subject: [PATCH 371/867] change many LOG/printf messages to use DLOG --- src/con.c | 16 ++++++++-------- src/handlers.c | 4 ++-- src/manage.c | 22 +++++++++++----------- src/render.c | 24 ++++++++++++------------ src/tree.c | 6 +++--- src/x.c | 40 ++++++++++++++++++++-------------------- 6 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/con.c b/src/con.c index a2597b88..e7c48b69 100644 --- a/src/con.c +++ b/src/con.c @@ -36,10 +36,10 @@ Con *con_new(Con *parent) { new->type = CT_CON; new->border_style = config.default_border; static int cnt = 0; - LOG("opening window %d\n", cnt); + DLOG("opening window %d\n", cnt); /* TODO: remove window coloring after test-phase */ - LOG("color %s\n", colors[cnt]); + DLOG("color %s\n", colors[cnt]); new->name = strdup(colors[cnt]); //uint32_t cp = get_colorpixel(colors[cnt]); cnt++; @@ -290,7 +290,7 @@ Con *con_get_fullscreen_con(Con *con) { */ bool con_is_floating(Con *con) { assert(con != NULL); - LOG("checking if con %p is floating\n", con); + DLOG("checking if con %p is floating\n", con); return (con->floating >= FLOATING_AUTO_ON); } @@ -347,8 +347,8 @@ Con *con_by_frame_id(xcb_window_t frame) { Con *con_for_window(i3Window *window, Match **store_match) { Con *con; Match *match; - LOG("searching con for window %p\n", window); - LOG("class == %s\n", window->class_class); + DLOG("searching con for window %p\n", window); + DLOG("class == %s\n", window->class_class); TAILQ_FOREACH(con, &all_cons, all_cons) TAILQ_FOREACH(match, &(con->swallow_head), matches) { @@ -406,7 +406,7 @@ void con_fix_percent(Con *con, int action) { */ void con_toggle_fullscreen(Con *con) { Con *workspace, *fullscreen; - LOG("toggling fullscreen for %p / %s\n", con, con->name); + DLOG("toggling fullscreen for %p / %s\n", con, con->name); if (con->fullscreen_mode == CF_NONE) { /* 1: check if there already is a fullscreen con */ workspace = con_get_workspace(con); @@ -422,7 +422,7 @@ void con_toggle_fullscreen(Con *con) { /* 1: disable fullscreen */ con->fullscreen_mode = CF_NONE; } - LOG("mode now: %d\n", con->fullscreen_mode); + DLOG("mode now: %d\n", con->fullscreen_mode); /* update _NET_WM_STATE if this container has a window */ /* TODO: when a window is assigned to a container which is already @@ -551,7 +551,7 @@ Con *con_get_next(Con *con, char way, orientation_t orientation) { /* 1: get the first parent with the same orientation */ Con *cur = con; while (con_orientation(cur->parent) != orientation) { - LOG("need to go one level further up\n"); + DLOG("need to go one level further up\n"); if (cur->parent->type == CT_WORKSPACE) { LOG("that's a workspace, we can't go further up\n"); return NULL; diff --git a/src/handlers.c b/src/handlers.c index 21ca4e3b..3fc18cf5 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -619,7 +619,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * x_draw_decoration(parent); TAILQ_FOREACH(con, &(parent->nodes_head), nodes) { - LOG("expose for con %p / %s\n", con, con->name); + DLOG("expose for con %p / %s\n", con, con->name); if (con->window) x_draw_decoration(con); } @@ -627,7 +627,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * /* We also need to render the decorations of other Cons nearby the Con * itself to not get overlapping decorations */ TAILQ_FOREACH(con, &(parent->parent->nodes_head), nodes) { - LOG("expose for con %p / %s\n", con, con->name); + DLOG("expose for con %p / %s\n", con, con->name); if (con->window) x_draw_decoration(con); } diff --git a/src/manage.c b/src/manage.c index 53dac3d5..a3bad45f 100644 --- a/src/manage.c +++ b/src/manage.c @@ -50,12 +50,12 @@ void manage_existing_windows(xcb_window_t root) { * */ void restore_geometry() { - LOG("Restoring geometry\n"); + DLOG("Restoring geometry\n"); Con *con; TAILQ_FOREACH(con, &all_cons, all_cons) if (con->window) { - printf("placing window at %d %d\n", con->rect.x, con->rect.y); + DLOG("placing window at %d %d\n", con->rect.x, con->rect.y); xcb_reparent_window(conn, con->window->id, root, con->rect.x, con->rect.y); } @@ -75,7 +75,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki xcb_get_geometry_reply_t *geom; xcb_get_window_attributes_reply_t *attr = 0; - printf("---> looking at window 0x%08x\n", window); + DLOG("---> looking at window 0x%08x\n", window); xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie, utf8_title_cookie, title_cookie, @@ -96,33 +96,33 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* Check if the window is mapped (it could be not mapped when intializing and calling manage_window() for every window) */ if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) { - LOG("Could not get attributes\n"); + DLOG("Could not get attributes\n"); return; } if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) { - LOG("map_state unviewable\n"); + DLOG("map_state unviewable\n"); goto out; } /* Don’t manage clients with the override_redirect flag */ - LOG("override_redirect is %d\n", attr->override_redirect); + DLOG("override_redirect is %d\n", attr->override_redirect); if (attr->override_redirect) goto out; /* Check if the window is already managed */ if (con_by_window_id(window) != NULL) { - LOG("already managed (by con %p)\n", con_by_window_id(window)); + DLOG("already managed (by con %p)\n", con_by_window_id(window)); goto out; } /* Get the initial geometry (position, size, …) */ if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL) { - LOG("could not get geometry\n"); + DLOG("could not get geometry\n"); goto out; } - LOG("reparenting!\n"); + DLOG("reparenting!\n"); uint32_t mask = 0; uint32_t values[1]; @@ -172,7 +172,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if (match != NULL && match->insert_where == M_ACTIVE) { /* We need to go down the focus stack starting from nc */ while (TAILQ_FIRST(&(nc->focus_head)) != TAILQ_END(&(nc->focus_head))) { - printf("walking down one step...\n"); + DLOG("walking down one step...\n"); nc = TAILQ_FIRST(&(nc->focus_head)); } /* We need to open a new con */ @@ -215,7 +215,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki * are bigger than our minimal useful size (75x50). */ nc->rect.width = max(geom->width, 75); nc->rect.height = max(geom->height, 50); - LOG("geometry = %d x %d\n", nc->rect.width, nc->rect.height); + DLOG("geometry = %d x %d\n", nc->rect.width, nc->rect.height); floating_enable(nc, false); } diff --git a/src/render.c b/src/render.c index 6e3cd0fe..f503ec14 100644 --- a/src/render.c +++ b/src/render.c @@ -17,10 +17,10 @@ static bool show_debug_borders = false; * */ void render_con(Con *con, bool render_fullscreen) { - printf("currently rendering node %p / %s / layout %d\n", + DLOG("currently rendering node %p / %s / layout %d\n", con, con->name, con->layout); int children = con_num_children(con); - printf("children: %d, orientation = %d\n", children, con->orientation); + DLOG("children: %d, orientation = %d\n", children, con->orientation); /* Copy container rect, subtract container border */ /* This is the actually usable space inside this container for clients */ @@ -97,7 +97,7 @@ void render_con(Con *con, bool render_fullscreen) { /* Check for fullscreen nodes */ Con *fullscreen = con_get_fullscreen_con(con); if (fullscreen) { - LOG("got fs node: %p\n", fullscreen); + DLOG("got fs node: %p\n", fullscreen); fullscreen->rect = rect; x_raise_con(fullscreen); render_con(fullscreen, true); @@ -147,7 +147,7 @@ void render_con(Con *con, bool render_fullscreen) { /* first we have the decoration, if this is a leaf node */ if (con_is_leaf(child) && child->border_style == BS_NORMAL) { - printf("that child is a leaf node, subtracting deco\n"); + DLOG("that child is a leaf node, subtracting deco\n"); /* TODO: make a function for relative coords? */ child->deco_rect.x = child->rect.x - con->rect.x; child->deco_rect.y = child->rect.y - con->rect.y; @@ -162,7 +162,7 @@ void render_con(Con *con, bool render_fullscreen) { /* stacked layout */ else if (con->layout == L_STACKED) { - printf("stacked con\n"); + DLOG("stacked con\n"); child->rect.x = x; child->rect.y = y; child->rect.width = rect.width; @@ -179,7 +179,7 @@ void render_con(Con *con, bool render_fullscreen) { /* tabbed layout */ else if (con->layout == L_TABBED) { - printf("tabbed con\n"); + DLOG("tabbed con\n"); child->rect.x = x; child->rect.y = y; child->rect.width = rect.width; @@ -196,9 +196,9 @@ void render_con(Con *con, bool render_fullscreen) { } } - printf("child at (%d, %d) with (%d x %d)\n", + DLOG("child at (%d, %d) with (%d x %d)\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height); - printf("x now %d, y now %d\n", x, y); + DLOG("x now %d, y now %d\n", x, y); x_raise_con(child); render_con(child, false); i++; @@ -208,7 +208,7 @@ void render_con(Con *con, bool render_fullscreen) { if (con->layout == L_STACKED || con->layout == L_TABBED) { Con *foc = TAILQ_FIRST(&(con->focus_head)); if (foc != TAILQ_END(&(con->focus_head))) { - LOG("con %p is stacking, raising %p\n", con, foc); + DLOG("con %p is stacking, raising %p\n", con, foc); x_raise_con(foc); /* by rendering the stacked container again, we handle the case * that we have a non-leaf-container inside the stack. */ @@ -217,11 +217,11 @@ void render_con(Con *con, bool render_fullscreen) { } TAILQ_FOREACH(child, &(con->floating_head), floating_windows) { - LOG("render floating:\n"); - LOG("floating child at (%d,%d) with %d x %d\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height); + DLOG("render floating:\n"); + DLOG("floating child at (%d,%d) with %d x %d\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height); x_raise_con(child); render_con(child, false); } - printf("-- level up\n"); + DLOG("-- level up\n"); } diff --git a/src/tree.c b/src/tree.c index dfe5f7be..949dfcdb 100644 --- a/src/tree.c +++ b/src/tree.c @@ -326,7 +326,7 @@ void tree_render() { if (croot == NULL) return; - printf("-- BEGIN RENDERING --\n"); + DLOG("-- BEGIN RENDERING --\n"); /* Reset map state for all nodes in tree */ /* TODO: a nicer method to walk all nodes would be good, maybe? */ mark_unmapped(croot); @@ -335,11 +335,11 @@ void tree_render() { /* We start rendering at an output */ Con *output; TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - printf("output %p / %s\n", output, output->name); + DLOG("output %p / %s\n", output, output->name); render_con(output, false); } x_push_changes(croot); - printf("-- END RENDERING --\n"); + DLOG("-- END RENDERING --\n"); } /* diff --git a/src/x.c b/src/x.c index bcdb07ca..b929fdcd 100644 --- a/src/x.c +++ b/src/x.c @@ -93,7 +93,7 @@ void x_con_init(Con *con) { state->initial = true; CIRCLEQ_INSERT_HEAD(&state_head, state, state); CIRCLEQ_INSERT_HEAD(&old_state_head, state, old_state); - LOG("adding new state for window id 0x%08x\n", state->id); + DLOG("adding new state for window id 0x%08x\n", state->id); } /* @@ -110,7 +110,7 @@ void x_reinit(Con *con) { return; } - LOG("resetting state %p to initial\n", state); + DLOG("resetting state %p to initial\n", state); state->initial = true; state->child_mapped = false; memset(&(state->window_rect), 0, sizeof(Rect)); @@ -153,7 +153,7 @@ void x_move_win(Con *src, Con *dest) { memset(&zero, 0, sizeof(Rect)); if (memcmp(&(state_dest->window_rect), &(zero), sizeof(Rect)) == 0) { memcpy(&(state_dest->window_rect), &(state_src->window_rect), sizeof(Rect)); - LOG("COPYING RECT\n"); + DLOG("COPYING RECT\n"); } } @@ -382,7 +382,7 @@ static void x_push_node(Con *con) { con_state *state; Rect rect = con->rect; - LOG("Pushing changes for node %p / %s\n", con, con->name); + DLOG("Pushing changes for node %p / %s\n", con, con->name); state = state_for_frame(con->frame); if (state->name != NULL) { @@ -414,7 +414,7 @@ static void x_push_node(Con *con) { /* reparent the child window (when the window was moved due to a sticky * container) */ if (state->need_reparent && con->window != NULL) { - LOG("Reparenting child window\n"); + DLOG("Reparenting child window\n"); /* Temporarily set the event masks to XCB_NONE so that we won’t get * UnmapNotify events (otherwise the handler would close the container). @@ -441,7 +441,7 @@ static void x_push_node(Con *con) { bool fake_notify = false; /* set new position if rect changed */ if (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0) { - LOG("setting rect (%d, %d, %d, %d)\n", rect.x, rect.y, rect.width, rect.height); + DLOG("setting rect (%d, %d, %d, %d)\n", rect.x, rect.y, rect.width, rect.height); xcb_set_window_rect(conn, con->frame, rect); memcpy(&(state->rect), &rect, sizeof(Rect)); fake_notify = true; @@ -450,7 +450,7 @@ static void x_push_node(Con *con) { /* dito, but for child windows */ if (con->window != NULL && memcmp(&(state->window_rect), &(con->window_rect), sizeof(Rect)) != 0) { - LOG("setting window rect (%d, %d, %d, %d)\n", + DLOG("setting window rect (%d, %d, %d, %d)\n", con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height); xcb_set_window_rect(conn, con->window->id, con->window_rect); memcpy(&(state->window_rect), &(con->window_rect), sizeof(Rect)); @@ -475,21 +475,21 @@ static void x_push_node(Con *con) { if (!state->child_mapped && con->window != NULL) { cookie = xcb_map_window(conn, con->window->id); - LOG("mapping child window (serial %d)\n", cookie.sequence); + DLOG("mapping child window (serial %d)\n", cookie.sequence); /* Ignore enter_notifies which are generated when mapping */ add_ignore_event(cookie.sequence); state->child_mapped = true; } cookie = xcb_map_window(conn, con->frame); - LOG("mapping container (serial %d)\n", cookie.sequence); + DLOG("mapping container (serial %d)\n", cookie.sequence); /* Ignore enter_notifies which are generated when mapping */ add_ignore_event(cookie.sequence); state->mapped = con->mapped; } if (fake_notify) { - LOG("Sending fake configure notify\n"); + DLOG("Sending fake configure notify\n"); fake_absolute_configure_notify(con); } @@ -517,7 +517,7 @@ static void x_push_node_unmaps(Con *con) { Con *current; con_state *state; - LOG("Pushing changes (with unmaps) for node %p / %s\n", con, con->name); + DLOG("Pushing changes (with unmaps) for node %p / %s\n", con, con->name); state = state_for_frame(con->frame); /* map/unmap if map state changed, also ensure that the child window @@ -534,7 +534,7 @@ static void x_push_node_unmaps(Con *con) { } cookie = xcb_unmap_window(conn, con->frame); - LOG("unmapping container (serial %d)\n", cookie.sequence); + DLOG("unmapping container (serial %d)\n", cookie.sequence); /* we need to increase ignore_unmap for this container (if it * contains a window) and for every window "under" this one which * contains a window */ @@ -563,20 +563,20 @@ static void x_push_node_unmaps(Con *con) { void x_push_changes(Con *con) { con_state *state; - LOG("\n\n PUSHING CHANGES\n\n"); + DLOG("\n\n PUSHING CHANGES\n\n"); x_push_node(con); - LOG("-- PUSHING WINDOW STACK --\n"); + DLOG("-- PUSHING WINDOW STACK --\n"); bool order_changed = false; /* X11 correctly represents the stack if we push it from bottom to top */ CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { - LOG("stack: 0x%08x\n", state->id); + DLOG("stack: 0x%08x\n", state->id); con_state *prev = CIRCLEQ_PREV(state, state); con_state *old_prev = CIRCLEQ_PREV(state, old_state); if (prev != old_prev) order_changed = true; if ((state->initial || order_changed) && prev != CIRCLEQ_END(&state_head)) { - LOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id); + DLOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id); uint32_t mask = 0; mask |= XCB_CONFIG_WINDOW_SIBLING; mask |= XCB_CONFIG_WINDOW_STACK_MODE; @@ -596,14 +596,14 @@ void x_push_changes(Con *con) { if (!focused->mapped) { DLOG("Not updating focus (to %p / %s), focused window is not mapped.\n", focused, focused->name); } else { - LOG("Updating focus (focused: %p / %s)\n", focused, focused->name); + DLOG("Updating focus (focused: %p / %s)\n", focused, focused->name); xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME); focused_id = to_focus; } } xcb_flush(conn); - LOG("\n\n ENDING CHANGES\n\n"); + DLOG("\n\n ENDING CHANGES\n\n"); x_push_node_unmaps(con); @@ -613,7 +613,7 @@ void x_push_changes(Con *con) { CIRCLEQ_INSERT_TAIL(&old_state_head, state, old_state); } CIRCLEQ_FOREACH(state, &old_state_head, old_state) { - LOG("old stack: 0x%08x\n", state->id); + DLOG("old stack: 0x%08x\n", state->id); } } @@ -624,7 +624,7 @@ void x_push_changes(Con *con) { */ void x_raise_con(Con *con) { con_state *state; - LOG("raising in new stack: %p / %s\n", con, con->name); + DLOG("raising in new stack: %p / %s\n", con, con->name); state = state_for_frame(con->frame); CIRCLEQ_REMOVE(&state_head, state, state); From 115462f103fc11b2d5a50a747bf8742f8811c9ce Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 7 Jan 2011 22:21:41 +0100 Subject: [PATCH 372/867] Implement tree flattening to automatically solve situations of redundant chains of split containers This should fix the move problems. See comment of tree_flatten() for a little example. --- include/tree.h | 15 ++++++ src/con.c | 2 + src/tree.c | 96 +++++++++++++++++++++++++++++++++++++ testcases/t/45-flattening.t | 33 +++++++++++++ 4 files changed, 146 insertions(+) create mode 100644 testcases/t/45-flattening.t diff --git a/include/tree.h b/include/tree.h index 6376ed92..c93d4c22 100644 --- a/include/tree.h +++ b/include/tree.h @@ -84,4 +84,19 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent); */ bool tree_restore(const char *path); +/** + * tree_flatten() removes pairs of redundant split containers, e.g.: + * [workspace, horizontal] + * [v-split] [child3] + * [h-split] + * [child1] [child2] + * In this example, the v-split and h-split container are redundant. + * Such a situation can be created by moving containers in a direction which is + * not the orientation of their parent container. i3 needs to create a new + * split container then and if you move containers this way multiple times, + * redundant chains of split-containers can be the result. + * + */ +void tree_flatten(Con *child); + #endif diff --git a/src/con.c b/src/con.c index e7c48b69..f105a279 100644 --- a/src/con.c +++ b/src/con.c @@ -669,6 +669,8 @@ void con_set_layout(Con *con, int layout) { if (old_focused) con_focus(old_focused); + tree_flatten(croot); + return; } diff --git a/src/tree.c b/src/tree.c index 949dfcdb..aa55b29a 100644 --- a/src/tree.c +++ b/src/tree.c @@ -518,4 +518,100 @@ void tree_move(char way, orientation_t orientation) { DLOG("Old container empty after moving. Let's close it\n"); tree_close(old_parent, false, false); } + + tree_flatten(croot); +} + +/* + * tree_flatten() removes pairs of redundant split containers, e.g.: + * [workspace, horizontal] + * [v-split] [child3] + * [h-split] + * [child1] [child2] + * In this example, the v-split and h-split container are redundant. + * Such a situation can be created by moving containers in a direction which is + * not the orientation of their parent container. i3 needs to create a new + * split container then and if you move containers this way multiple times, + * redundant chains of split-containers can be the result. + * + */ +void tree_flatten(Con *con) { + Con *current, *child, *parent = con->parent; + DLOG("Checking if I can flatten con = %p / %s\n", con, con->name); + + /* We only consider normal containers without windows */ + if (con->type != CT_CON || con->window != NULL) + goto recurse; + + /* Ensure it got only one child */ + child = TAILQ_FIRST(&(con->nodes_head)); + if (TAILQ_NEXT(child, nodes) != NULL) + goto recurse; + + /* The child must have a different orientation than the con but the same as + * the con’s parent to be redundant */ + if (con->orientation == NO_ORIENTATION || + child->orientation == NO_ORIENTATION || + con->orientation == child->orientation || + child->orientation != parent->orientation) + goto recurse; + + DLOG("Alright, I have to flatten this situation now. Stay calm.\n"); + /* 1: save focus */ + Con *focus_next = TAILQ_FIRST(&(child->focus_head)); + + DLOG("detaching...\n"); + /* 2: re-attach the children to the parent before con */ + while (!TAILQ_EMPTY(&(child->nodes_head))) { + current = TAILQ_FIRST(&(child->nodes_head)); + DLOG("detaching current=%p / %s\n", current, current->name); + con_detach(current); + DLOG("re-attaching\n"); + /* We don’t use con_attach() here because for a CT_CON, the special + * case handling of con_attach() does not trigger. So all it would do + * is calling TAILQ_INSERT_AFTER, but with the wrong container. So we + * directly use the TAILQ macros. */ + current->parent = parent; + TAILQ_INSERT_BEFORE(con, current, nodes); + DLOG("attaching to focus list\n"); + TAILQ_INSERT_TAIL(&(parent->focus_head), current, focused); + } + DLOG("re-attached all\n"); + + /* 3: restore focus, if con was focused */ + if (focus_next != NULL && + TAILQ_FIRST(&(parent->focus_head)) == con) { + DLOG("restoring focus to focus_next=%p\n", focus_next); + TAILQ_REMOVE(&(parent->focus_head), focus_next, focused); + TAILQ_INSERT_HEAD(&(parent->focus_head), focus_next, focused); + DLOG("restored focus.\n"); + } + + /* 4: close the redundant cons */ + DLOG("closing redundant cons\n"); + tree_close(con, false, true); + + /* Well, we got to abort the recursion here because we destroyed the + * container. However, if tree_flatten() is called sufficiently often, + * there can’t be the situation of having two pairs of redundant containers + * at once. Therefore, we can safely abort the recursion on this level + * after flattening. */ + return; + +recurse: + /* We cannot use normal foreach here because tree_flatten might close the + * current container. */ + current = TAILQ_FIRST(&(con->nodes_head)); + while (current != NULL) { + Con *next = TAILQ_NEXT(current, nodes); + tree_flatten(current); + current = next; + } + + current = TAILQ_FIRST(&(con->floating_head)); + while (current != NULL) { + Con *next = TAILQ_NEXT(current, floating_windows); + tree_flatten(current); + current = next; + } } diff --git a/testcases/t/45-flattening.t b/testcases/t/45-flattening.t new file mode 100644 index 00000000..c508ea7f --- /dev/null +++ b/testcases/t/45-flattening.t @@ -0,0 +1,33 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# by moving the window in the opposite orientation that its parent has, we +# force i3 to create a new split container with the appropriate orientation. +# However, when doing that two times in a row, we end up with two split +# containers which are then redundant (workspace is horizontal, then v-split, +# then h-split – we could just append the children of the latest h-split to the +# workspace itself). +# +# This testcase checks that the tree is properly flattened after moving. +# +use X11::XCB qw(:all); +use i3test tests => 2; + +my $x = X11::XCB::Connection->new; + +my $tmp = get_unused_workspace; +cmd "workspace $tmp"; + +my $left = open_standard_window($x); +sleep 0.25; +my $mid = open_standard_window($x); +sleep 0.25; +my $right = open_standard_window($x); +sleep 0.25; + +cmd 'move before v'; +cmd 'move after h'; +my $ws = get_ws($tmp); + +is($ws->{orientation}, 'horizontal', 'workspace orientation is horizontal'); +is(@{$ws->{nodes}}, 3, 'all three windows on workspace level'); From b660769fe0f306552ee92a00c60650d6f3ef06c8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 7 Jan 2011 23:56:32 +0100 Subject: [PATCH 373/867] Bugfix: Correctly move to other workspaces when a floating window is focused on the target ws (Thanks mseed) --- src/con.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/con.c b/src/con.c index f105a279..ba42a13d 100644 --- a/src/con.c +++ b/src/con.c @@ -467,12 +467,21 @@ void con_move_to_workspace(Con *con, Con *workspace) { if (next->type != CT_WORKSPACE) next = next->parent; + /* 4: if the target container is floating, we get the workspace instead. + * Only tiling windows need to get inserted next to the current container. + * */ + Con *floatingcon = con_inside_floating(next); + if (floatingcon != NULL) { + DLOG("floatingcon, going up even further\n"); + next = floatingcon->parent; + } + DLOG("Re-attaching container to %p / %s\n", next, next->name); - /* 4: re-attach the con to the parent of this focused container */ + /* 5: re-attach the con to the parent of this focused container */ con_detach(con); con_attach(con, next, false); - /* 5: keep focus on the current workspace */ + /* 6: keep focus on the current workspace */ con_focus(focus_next); } From e4bb6d859ec3bf8cc2c80af4ff54701b87d32e5b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 8 Jan 2011 00:10:30 +0100 Subject: [PATCH 374/867] Bugfix: Correctly maintain focus when setting a workspace to floating --- src/floating.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/floating.c b/src/floating.c index c50cf494..b342c56e 100644 --- a/src/floating.c +++ b/src/floating.c @@ -17,6 +17,8 @@ extern xcb_connection_t *conn; void floating_enable(Con *con, bool automatic) { + bool set_focus = true; + if (con_is_floating(con)) { LOG("Container is already in floating mode, not doing anything.\n"); return; @@ -58,6 +60,7 @@ void floating_enable(Con *con, bool automatic) { con_focus(old_focused); con = new; + set_focus = false; } /* 1: detach the container from its parent */ @@ -121,7 +124,8 @@ void floating_enable(Con *con, bool automatic) { TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes); TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused); // TODO: don’t influence focus handling when Con was not focused before. - con_focus(con); + if (set_focus) + con_focus(con); } void floating_disable(Con *con, bool automatic) { From 54b95497135ca051da94ec1b84ceeb4111a9a902 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 8 Jan 2011 00:10:49 +0100 Subject: [PATCH 375/867] Bugfix: Look for cons to focus *starting* at the ws, not beneath the ws (Thanks mseed) This should fix #286. --- src/tree.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index aa55b29a..b601cdd4 100644 --- a/src/tree.c +++ b/src/tree.c @@ -177,7 +177,12 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { DLOG("parent container killed\n"); if (con == focused) { DLOG("This is the focused container, i need to find another one to focus. I start looking at ws = %p\n", ws); - next = con_next_focused(ws); + next = ws; + /* now go down the focus stack as far as + * possible, excluding the current container */ + while (!TAILQ_EMPTY(&(next->focus_head))) + next = TAILQ_FIRST(&(next->focus_head)); + dont_kill_parent = true; DLOG("Alright, focusing %p\n", next); } else { From 0ea15ed9620e9cc57305c5d66b0b115f3aec8552 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 8 Jan 2011 00:38:10 +0100 Subject: [PATCH 376/867] fix a problem with workspace switching when the focus got to the target workspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This could explain some strange effects where workspaces would just stay blank. We’ll see. --- src/workspace.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/workspace.c b/src/workspace.c index b5095d11..0694b977 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -201,12 +201,21 @@ static void workspace_reassign_sticky(Con *con) { void workspace_show(const char *num) { Con *workspace, *current, *old; - old = con_get_workspace(focused); - workspace = workspace_get(num); + + /* disable fullscreen for the other workspaces and get the workspace we are + * currently on. */ + TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) { + if (current->fullscreen_mode == CF_OUTPUT) + old = current; + current->fullscreen_mode = CF_NONE; + } + + /* enable fullscreen for the target workspace. If it happens to be the + * same one we are currently on anyways, we can stop here. */ + workspace->fullscreen_mode = CF_OUTPUT; if (workspace == old) return; - workspace->fullscreen_mode = CF_OUTPUT; /* disable fullscreen */ TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) current->fullscreen_mode = CF_NONE; From 83f6e445a0f046601bbd9a44df8e03e4f8873e96 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 8 Jan 2011 00:44:03 +0100 Subject: [PATCH 377/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20use=20->old?= =?UTF-8?q?=5Fparent=20for=20floating=20cons=20(Thanks=20eelvex)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead, we attach them to their workspace when toggling back to tiling. This makes more sense; afterall, floating clients are always directly below a CT_WORKSPACE container. --- include/data.h | 2 -- src/floating.c | 5 +---- src/tree.c | 24 ------------------------ 3 files changed, 1 insertion(+), 30 deletions(-) diff --git a/include/data.h b/include/data.h index 91f4afcd..fb6ae4e0 100644 --- a/include/data.h +++ b/include/data.h @@ -275,8 +275,6 @@ struct Con { enum { CT_ROOT = 0, CT_OUTPUT = 1, CT_CON = 2, CT_FLOATING_CON = 3, CT_WORKSPACE = 4 } type; orientation_t orientation; struct Con *parent; - /* parent before setting it to floating */ - struct Con *old_parent; struct Rect rect; struct Rect window_rect; diff --git a/src/floating.c b/src/floating.c index b342c56e..3cf8ab5f 100644 --- a/src/floating.c +++ b/src/floating.c @@ -99,7 +99,6 @@ void floating_enable(Con *con, bool automatic) { TAILQ_INSERT_TAIL(&(nc->parent->focus_head), nc, focused); /* 3: attach the child to the new parent container */ - con->old_parent = con->parent; con->parent = nc; con->floating = FLOATING_USER_ON; @@ -134,8 +133,6 @@ void floating_disable(Con *con, bool automatic) { return; } - assert(con->old_parent != NULL); - /* 1: detach from parent container */ TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); TAILQ_REMOVE(&(con->parent->focus_head), con, focused); @@ -146,7 +143,7 @@ void floating_disable(Con *con, bool automatic) { tree_close(con->parent, false, false); /* 3: re-attach to previous parent */ - con->parent = con->old_parent; + con->parent = con_get_workspace(con); TAILQ_INSERT_TAIL(&(con->parent->nodes_head), con, nodes); TAILQ_INSERT_TAIL(&(con->parent->focus_head), con, focused); diff --git a/src/tree.c b/src/tree.c index b601cdd4..3e8be524 100644 --- a/src/tree.c +++ b/src/tree.c @@ -81,27 +81,6 @@ Con *tree_open_con(Con *con) { return new; } -/* - * vanishing is the container that is about to be closed (so any floating - * client which has old_parent == vanishing needs to be "re-parented"). - * - */ -static void fix_floating_parent(Con *con, Con *vanishing) { - Con *child; - - if (con->old_parent == vanishing) { - LOG("Fixing vanishing old_parent (%p) of container %p to be %p\n", - vanishing, con, vanishing->parent); - con->old_parent = vanishing->parent; - } - - TAILQ_FOREACH(child, &(con->floating_head), floating_windows) - fix_floating_parent(child, vanishing); - - TAILQ_FOREACH(child, &(con->nodes_head), nodes) - fix_floating_parent(child, vanishing); -} - static bool _is_con_mapped(Con *con) { Con *child; @@ -127,9 +106,6 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { was_mapped = _is_con_mapped(con); } - /* check floating clients and adjust old_parent if necessary */ - fix_floating_parent(croot, con); - /* Get the container which is next focused */ Con *next = con_next_focused(con); DLOG("next = %p, focused = %p\n", next, focused); From d5388147f122c86123840b8f20d89aebef59d393 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 8 Jan 2011 00:45:10 +0100 Subject: [PATCH 378/867] ipc: adapt dump-asy.pl for the new orientation format --- dump-asy.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dump-asy.pl b/dump-asy.pl index dc2cbeab..a8eab04c 100755 --- a/dump-asy.pl +++ b/dump-asy.pl @@ -22,7 +22,7 @@ say $tmp "treeLevelStep = 2cm;"; sub dump_node { my ($n, $parent) = @_; - my $o = ($n->{orientation} == 0 ? "u" : ($n->{orientation} == 1 ? "h" : "v")); + my $o = ($n->{orientation} eq 'none' ? "u" : ($n->{orientation} eq 'horizontal' ? "h" : "v")); my $w = (defined($n->{window}) ? $n->{window} : "N"); my $na = $n->{name}; $na =~ s/#/\\#/g; From a6f0dcd2501c7e79694c3e7ff60d330496c76ad3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 8 Jan 2011 12:03:03 +0100 Subject: [PATCH 379/867] Fix switching to a workspace on a different output --- src/workspace.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/workspace.c b/src/workspace.c index 0694b977..a1e653ea 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -211,14 +211,17 @@ void workspace_show(const char *num) { current->fullscreen_mode = CF_NONE; } + /* Check if the the currently focused con is on the same Output as the + * workspace we chose as 'old'. If not, use the workspace of the currently + * focused con */ + if (con_get_workspace(focused)->parent != old->parent) + old = con_get_workspace(focused); + /* enable fullscreen for the target workspace. If it happens to be the * same one we are currently on anyways, we can stop here. */ workspace->fullscreen_mode = CF_OUTPUT; if (workspace == old) return; - /* disable fullscreen */ - TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) - current->fullscreen_mode = CF_NONE; workspace_reassign_sticky(workspace); From cd2ee61ee88f53ffadeeddcc0f3fac8aa50a06de Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 12 Jan 2011 10:12:24 +0100 Subject: [PATCH 380/867] fix some memory leaks when user passes command line arguments twice (Thanks Tiago) --- i3-input/i3-input.h | 7 +++++++ i3-input/main.c | 4 ++++ i3-msg/main.c | 2 ++ src/main.c | 3 +++ 4 files changed, 16 insertions(+) diff --git a/i3-input/i3-input.h b/i3-input/i3-input.h index 8d8b467f..c699f6c5 100644 --- a/i3-input/i3-input.h +++ b/i3-input/i3-input.h @@ -4,6 +4,13 @@ #include #define die(...) errx(EXIT_FAILURE, __VA_ARGS__); +#define FREE(pointer) do { \ + if (pointer != NULL) { \ + free(pointer); \ + pointer = NULL; \ + } \ +} \ +while (0) char *convert_ucs_to_utf8(char *input); char *convert_utf8_to_ucs2(char *input, int *real_strlen); diff --git a/i3-input/main.c b/i3-input/main.c index 1010e584..6c2b35a1 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -261,21 +261,25 @@ int main(int argc, char *argv[]) { while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { switch (o) { case 's': + FREE(socket_path); socket_path = strdup(optarg); break; case 'v': printf("i3-input " I3_VERSION); return 0; case 'p': + FREE(command_prefix); command_prefix = strdup(optarg); break; case 'l': limit = atoi(optarg); break; case 'P': + FREE(prompt); prompt = strdup(optarg); break; case 'f': + FREE(pattern); pattern = strdup(optarg); break; case 'h': diff --git a/i3-msg/main.c b/i3-msg/main.c index a945d3b1..a2cd9e0a 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -126,6 +126,8 @@ int main(int argc, char *argv[]) { while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { if (o == 's') { + if (socket_path != NULL) + free(socket_path); socket_path = strdup(optarg); } else if (o == 't') { if (strcasecmp(optarg, "command") == 0) diff --git a/src/main.c b/src/main.c index ea7f82f2..64a6e309 100644 --- a/src/main.c +++ b/src/main.c @@ -108,10 +108,12 @@ int main(int argc, char *argv[]) { autostart = false; break; case 'L': + FREE(layout_path); layout_path = sstrdup(optarg); delete_layout_path = false; break; case 'c': + FREE(override_configpath); override_configpath = sstrdup(optarg); break; case 'C': @@ -141,6 +143,7 @@ int main(int argc, char *argv[]) { "and disable this option as soon as you can.\n"); break; } else if (strcmp(long_options[option_index].name, "restart") == 0) { + FREE(layout_path); layout_path = sstrdup(optarg); delete_layout_path = true; break; From 4caf85aa0be4b5d60213fbe2b8f8115df1607c6d Mon Sep 17 00:00:00 2001 From: Axel Wagner Date: Tue, 11 Jan 2011 04:39:48 +0100 Subject: [PATCH 381/867] Use I3SOCK-environment-variable --- docs/ipc | 8 ++++++-- docs/userguide | 2 ++ i3-input/main.c | 5 ++++- i3-msg/main.c | 5 ++++- man/i3-input.man | 8 ++++++++ man/i3-msg.man | 8 ++++++++ man/i3.man | 8 ++++++++ src/main.c | 2 ++ 8 files changed, 42 insertions(+), 4 deletions(-) diff --git a/docs/ipc b/docs/ipc index 4e46bc9e..1c31d061 100644 --- a/docs/ipc +++ b/docs/ipc @@ -11,8 +11,12 @@ workspace bar. The method of choice for IPC in our case is a unix socket because it has very little overhead on both sides and is usually available without headaches in most languages. In the default configuration file, no ipc-socket path is -specified and thus no socket is created. The standard path (which +i3-msg+ and -+i3-input+ use) is +/tmp/i3-ipc.sock+. +specified and thus no socket is created. Alternatively you can set the +environment-variable +I3SOCK+. Setting a path in the configfile will override ++I3SOCK+. + ++i3-msg+ and +i3-input+ will use +I3SOCK+ to connect to i3, unless -s is passed. +If neither is given, they will default to +/tmp/i3-ipc.sock+. == Establishing a connection diff --git a/docs/userguide b/docs/userguide index 7e2438f0..35e587af 100644 --- a/docs/userguide +++ b/docs/userguide @@ -521,6 +521,8 @@ programs to get information from i3, such as the current workspaces To enable it, you have to configure a path where the unix socket will be stored. The default path is +/tmp/i3-ipc.sock+. +You can override the default path through the environment-variable +I3SOCK+. + *Examples*: ---------------------------- ipc-socket /tmp/i3-ipc.sock diff --git a/i3-input/main.c b/i3-input/main.c index 6c2b35a1..b9b4ba81 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -241,7 +241,10 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press } int main(int argc, char *argv[]) { - char *socket_path = "/tmp/i3-ipc.sock"; + char *socket_path; + if ((socket_path = getenv("I3SOCK")) == NULL) { + socket_path = "/tmp/i3-ipc.sock"; + } char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; int o, option_index = 0; diff --git a/i3-msg/main.c b/i3-msg/main.c index a2cd9e0a..6a51ff55 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -107,7 +107,10 @@ static void ipc_recv_message(int sockfd, uint32_t message_type, } int main(int argc, char *argv[]) { - char *socket_path = "/tmp/i3-ipc.sock"; + char *socket_path; + if ((socket_path = getenv("I3SOCK")) == NULL) { + socket_path = "/tmp/i3-ipc.sock"; + } int o, option_index = 0; int message_type = I3_IPC_MESSAGE_TYPE_COMMAND; char *payload = ""; diff --git a/man/i3-input.man b/man/i3-input.man index fee4d92e..cd85c92c 100644 --- a/man/i3-input.man +++ b/man/i3-input.man @@ -23,6 +23,14 @@ mark/goto command. i3-input -p 'mark ' -l 1 -P 'Mark: ' ------------------------------------------------ +== ENVIRONMENT + +=== I3SOCK + +If no ipc-socket is specified on the commandline, this variable is used +to determine the path, at wich the unix domain socket is expected, on which +to connect to i3. + == SEE ALSO i3(1) diff --git a/man/i3-msg.man b/man/i3-msg.man index c723bd1e..116195b7 100644 --- a/man/i3-msg.man +++ b/man/i3-msg.man @@ -24,6 +24,14 @@ future (staying backwards-compatible, of course). i3-msg "bp" # Use 1-px border for current client ------------------------------------------------ +== ENVIRONMENT + +=== I3SOCK + +If no ipc-socket is specified on the commandline, this variable is used +to determine the path, at wich the unix domain socket is expected, on which +to connect to i3. + == SEE ALSO i3(1) diff --git a/man/i3.man b/man/i3.man index 5877f143..7dbe0dfa 100644 --- a/man/i3.man +++ b/man/i3.man @@ -282,6 +282,14 @@ echo "Starting at $(date)" >> ~/.i3/logfile exec /usr/bin/i3 >> ~/.i3/logfile ------------------------------------------------------------- +== ENVIRONMENT + +=== I3SOCK + +If no ipc-socket is specified in the configfile, this variable is used +to determine the path, at wich the unix domain socket is created, on which +i3 listenes to incoming connections. + == TODO There is still lot of work to do. Please check our bugtracker for up-to-date diff --git a/src/main.c b/src/main.c index 64a6e309..9cc70b9b 100644 --- a/src/main.c +++ b/src/main.c @@ -99,6 +99,8 @@ int main(int argc, char *argv[]) { if (!isatty(fileno(stdout))) setbuf(stdout, NULL); + config.ipc_socket_path = getenv("I3SOCK"); + start_argv = argv; while ((opt = getopt_long(argc, argv, "c:CvaL:hld:V", long_options, &option_index)) != -1) { From 92a038dd251efd511787f1d9cbcd73f8470ceea6 Mon Sep 17 00:00:00 2001 From: Axel Wagner Date: Tue, 11 Jan 2011 05:25:04 +0100 Subject: [PATCH 382/867] Save environment-variable AFTER reading the configfile --- src/main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main.c b/src/main.c index 9cc70b9b..5000d8a4 100644 --- a/src/main.c +++ b/src/main.c @@ -99,8 +99,6 @@ int main(int argc, char *argv[]) { if (!isatty(fileno(stdout))) setbuf(stdout, NULL); - config.ipc_socket_path = getenv("I3SOCK"); - start_argv = argv; while ((opt = getopt_long(argc, argv, "c:CvaL:hld:V", long_options, &option_index)) != -1) { @@ -180,6 +178,10 @@ int main(int argc, char *argv[]) { exit(0); } + if (config.ipc_socket_path == NULL) { + config.ipc_socket_path = getenv("I3SOCK"); + } + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); root = root_screen->root; root_depth = root_screen->root_depth; From d6d4c962f43a57d57afbf9bd8579bffda26cc44e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 17 Jan 2011 14:11:56 +0100 Subject: [PATCH 383/867] Bugfix: Call mark_unmapped() on floating nodes aswell (Thanks mseed) This fixes #292. --- src/tree.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/tree.c b/src/tree.c index 3e8be524..721e0b9c 100644 --- a/src/tree.c +++ b/src/tree.c @@ -290,11 +290,10 @@ static void mark_unmapped(Con *con) { TAILQ_FOREACH(current, &(con->nodes_head), nodes) mark_unmapped(current); if (con->type == CT_WORKSPACE) { - TAILQ_FOREACH(current, &(con->floating_head), floating_windows) { - current->mapped = false; - Con *child = TAILQ_FIRST(&(current->nodes_head)); - child->mapped = false; - } + /* We need to call mark_unmapped on floating nodes aswell since we can + * make containers floating. */ + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) + mark_unmapped(current); } } From ae4331113e26df2146dae61ae5d58537ea4b9b77 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 17 Jan 2011 14:27:49 +0100 Subject: [PATCH 384/867] re-implement xkb support for detecting keyboard layout changes --- src/main.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/main.c b/src/main.c index 5000d8a4..5736324f 100644 --- a/src/main.c +++ b/src/main.c @@ -71,6 +71,74 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { } } + +/* + * When using xmodmap to change the keyboard mapping, this event + * is only sent via XKB. Therefore, we need this special handler. + * + */ +static void xkb_got_event(EV_P_ struct ev_io *w, int revents) { + DLOG("Handling XKB event\n"); + XkbEvent ev; + + /* When using xmodmap, every change (!) gets an own event. + * Therefore, we just read all events and only handle the + * mapping_notify once. */ + bool mapping_changed = false; + while (XPending(xkbdpy)) { + XNextEvent(xkbdpy, (XEvent*)&ev); + /* While we should never receive a non-XKB event, + * better do sanity checking */ + if (ev.type != xkb_event_base) + continue; + + if (ev.any.xkb_type == XkbMapNotify) { + mapping_changed = true; + continue; + } + + if (ev.any.xkb_type != XkbStateNotify) { + ELOG("Unknown XKB event received (type %d)\n", ev.any.xkb_type); + continue; + } + + /* See The XKB Extension: Library Specification, section 14.1 */ + /* We check if the current group (each group contains + * two levels) has been changed. Mode_switch activates + * group XkbGroup2Index */ + if (xkb_current_group == ev.state.group) + continue; + + xkb_current_group = ev.state.group; + + if (ev.state.group == XkbGroup2Index) { + DLOG("Mode_switch enabled\n"); + grab_all_keys(conn, true); + } + + if (ev.state.group == XkbGroup1Index) { + DLOG("Mode_switch disabled\n"); + ungrab_all_keys(conn); + grab_all_keys(conn, false); + } + } + + if (!mapping_changed) + return; + + DLOG("Keyboard mapping changed, updating keybindings\n"); + xcb_key_symbols_free(keysyms); + keysyms = xcb_key_symbols_alloc(conn); + + xcb_get_numlock_mask(conn); + + ungrab_all_keys(conn); + DLOG("Re-grabbing...\n"); + translate_keysyms(); + grab_all_keys(conn, (xkb_current_group == XkbGroup2Index)); + DLOG("Done\n"); +} + int main(int argc, char *argv[]) { //parse_cmd("[ foo ] attach, attach ; focus"); int screens; @@ -239,6 +307,31 @@ int main(int argc, char *argv[]) { /*init_xkb();*/ } + if (xkb_supported) { + int errBase, + major = XkbMajorVersion, + minor = XkbMinorVersion; + + if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) { + fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n"); + return 1; + } + + int i1; + if (!XkbQueryExtension(xkbdpy,&i1,&xkb_event_base,&errBase,&major,&minor)) { + fprintf(stderr, "XKB not supported by X-server\n"); + return 1; + } + /* end of ugliness */ + + if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, + XkbMapNotifyMask | XkbStateNotifyMask, + XkbMapNotifyMask | XkbStateNotifyMask)) { + fprintf(stderr, "Could not set XKB event mask\n"); + return 1; + } + } + memset(&evenths, 0, sizeof(xcb_event_handlers_t)); memset(&prophs, 0, sizeof(xcb_property_handlers_t)); @@ -375,12 +468,22 @@ int main(int argc, char *argv[]) { } struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io)); + struct ev_io *xkb = scalloc(sizeof(struct ev_io)); struct ev_check *xcb_check = scalloc(sizeof(struct ev_check)); struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare)); ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); ev_io_start(loop, xcb_watcher); + + if (xkb_supported) { + ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ); + ev_io_start(loop, xkb); + + /* Flush the buffer so that libev can properly get new events */ + XFlush(xkbdpy); + } + ev_check_init(xcb_check, xcb_check_cb); ev_check_start(loop, xcb_check); From 0eb5eb34cdb88527b31d7827cf803ccdcbce4eea Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 17 Jan 2011 14:38:16 +0100 Subject: [PATCH 385/867] When in stacking mode with only one child, respect border styles 1pixel and none (Thanks Merovius) --- src/con.c | 4 ++-- src/render.c | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/con.c b/src/con.c index ba42a13d..d5389ff4 100644 --- a/src/con.c +++ b/src/con.c @@ -625,10 +625,10 @@ int con_border_style(Con *con) { } if (con->parent->layout == L_STACKED) - return BS_NORMAL; + return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL); if (con->parent->layout == L_TABBED && con->border_style != BS_NORMAL) - return con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL; + return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL); return con->border_style; } diff --git a/src/render.c b/src/render.c index f503ec14..9d214ce6 100644 --- a/src/render.c +++ b/src/render.c @@ -168,13 +168,15 @@ void render_con(Con *con, bool render_fullscreen) { child->rect.width = rect.width; child->rect.height = rect.height; - child->rect.y += (deco_height * children); - child->rect.height -= (deco_height * children); - child->deco_rect.x = x - con->rect.x; child->deco_rect.y = y - con->rect.y + (i * deco_height); child->deco_rect.width = child->rect.width; child->deco_rect.height = deco_height; + + if (children > 1 || (child->border_style != BS_1PIXEL && child->border_style != BS_NONE)) { + child->rect.y += (deco_height * children); + child->rect.height -= (deco_height * children); + } } /* tabbed layout */ From 3fe4146e24b3830f2a6a5d8c5d4bbb0e2f256070 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 19 Jan 2011 09:31:31 +0100 Subject: [PATCH 386/867] Bugfix: fix crash in tree_flatten (Thanks mseed) --- src/tree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index 721e0b9c..48a25eb2 100644 --- a/src/tree.c +++ b/src/tree.c @@ -525,7 +525,7 @@ void tree_flatten(Con *con) { /* Ensure it got only one child */ child = TAILQ_FIRST(&(con->nodes_head)); - if (TAILQ_NEXT(child, nodes) != NULL) + if (child == NULL || TAILQ_NEXT(child, nodes) != NULL) goto recurse; /* The child must have a different orientation than the con but the same as From 1fecbb3e5ad5e4f6ddb1e8656ee8d043d698f29f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 20 Jan 2011 10:09:43 +0100 Subject: [PATCH 387/867] Bugfix: also close empty split containers when the clients are moved away (Thanks mseed) Also update the testcase (which used only 'kill' before). --- src/con.c | 10 +++++++ testcases/t/30-close-empty-split.t | 42 +++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/con.c b/src/con.c index d5389ff4..b151d297 100644 --- a/src/con.c +++ b/src/con.c @@ -478,11 +478,21 @@ void con_move_to_workspace(Con *con, Con *workspace) { DLOG("Re-attaching container to %p / %s\n", next, next->name); /* 5: re-attach the con to the parent of this focused container */ + Con *parent = con->parent; con_detach(con); con_attach(con, next, false); /* 6: keep focus on the current workspace */ con_focus(focus_next); + + /* 7: check if the parent container is empty now and close it */ + if (parent->type != CT_WORKSPACE && + TAILQ_EMPTY(&(parent->nodes_head))) { + DLOG("Closing empty parent container\n"); + /* TODO: check if this container would swallow any other client and + * don’t close it automatically. */ + tree_close(parent, false, false); + } } /* diff --git a/testcases/t/30-close-empty-split.t b/testcases/t/30-close-empty-split.t index f1b7c2ab..44aa3372 100644 --- a/testcases/t/30-close-empty-split.t +++ b/testcases/t/30-close-empty-split.t @@ -3,7 +3,7 @@ # # Check if empty split containers are automatically closed. # -use i3test tests => 4; +use i3test tests => 8; use Time::HiRes qw(sleep); my $i3 = i3("/tmp/nestedcons"); @@ -42,4 +42,44 @@ $i3->command('kill')->recv; ($nodes, $focus) = get_ws_content($tmp); isnt($nodes->[0]->{id}, $split, 'split container closed'); +############################################################## +# same thing but this time we are moving the cons away instead +# of killing them +############################################################## + +$tmp = get_unused_workspace(); +$i3->command("workspace $tmp")->recv; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + +$first = open_empty_con($i3); +$second = open_empty_con($i3); +$i3->command(qq|[con_id="$first"] focus|)->recv; + +$i3->command('split v')->recv; + +($nodes, $focus) = get_ws_content($tmp); + +is($nodes->[0]->{focused}, 0, 'split container not focused'); + +# focus the split container +$i3->command('level up')->recv; +($nodes, $focus) = get_ws_content($tmp); +my $split = $focus->[0]; +$i3->command('level down')->recv; + +my $second = open_empty_con($i3); + +isnt($first, $second, 'different container focused'); + +############################################################## +# close both windows and see if the split container still exists +############################################################## + +my $otmp = get_unused_workspace(); +$i3->command("move workspace $otmp")->recv; +$i3->command("move workspace $otmp")->recv; +($nodes, $focus) = get_ws_content($tmp); +isnt($nodes->[0]->{id}, $split, 'split container closed'); + diag( "Testing i3, Perl $], $^X" ); From 81ff1f976df43ebda6fdcfb794f926b5d14f3364 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Jan 2011 21:01:02 +0100 Subject: [PATCH 388/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20send=20fake?= =?UTF-8?q?=20configure=20notify=20with=20not=20yet=20rendered=20rect=20fo?= =?UTF-8?q?r=20floating=20windows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a bug where opening the Xpdf find dialog when Xpdf is in fullscreen mode would crash Xpdf due to a zero-width and zero-height ConfigureNotify rect. --- src/floating.c | 6 ++++++ src/xcb.c | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/floating.c b/src/floating.c index 3cf8ab5f..85b2c0ae 100644 --- a/src/floating.c +++ b/src/floating.c @@ -87,12 +87,14 @@ void floating_enable(Con *con, bool automatic) { i3Font *font = load_font(conn, config.font); int deco_height = font->height + 5; + DLOG("Original rect: (%d, %d) with %d x %d\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height); nc->rect = con->rect; /* add pixels for the decoration */ /* TODO: don’t add them when the user automatically puts new windows into * 1pixel/borderless mode */ nc->rect.height += deco_height + 4; nc->rect.width += 4; + DLOG("Floating rect: (%d, %d) with %d x %d\n", nc->rect.x, nc->rect.y, nc->rect.width, nc->rect.height); nc->orientation = NO_ORIENTATION; nc->type = CT_FLOATING_CON; TAILQ_INSERT_TAIL(&(nc->parent->floating_head), nc, floating_windows); @@ -120,6 +122,10 @@ void floating_enable(Con *con, bool automatic) { } } + /* render the cons to get initial window_rect correct */ + render_con(nc, false); + render_con(con, false); + TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes); TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused); // TODO: don’t influence focus handling when Con was not focused before. diff --git a/src/xcb.c b/src/xcb.c index 4b400ae5..ff7dadbc 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -192,6 +192,8 @@ void fake_absolute_configure_notify(Con *con) { absolute.width = con->window_rect.width; absolute.height = con->window_rect.height; + DLOG("fake rect = (%d, %d, %d, %d)\n", absolute.x, absolute.y, absolute.width, absolute.height); + fake_configure_notify(conn, absolute, con->window->id); } From cbf4fcb9b5b05146dae464d6414c9a8b8144ddba Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Jan 2011 21:49:56 +0100 Subject: [PATCH 389/867] Bugfix: Fix focus problems when switching workspaces by pushing the window stack before mapping --- src/x.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/x.c b/src/x.c index b929fdcd..f99dbcfc 100644 --- a/src/x.c +++ b/src/x.c @@ -559,13 +559,16 @@ static void x_push_node_unmaps(Con *con) { * Pushes all changes (state of each node, see x_push_node() and the window * stack) to X11. * + * NOTE: We need to push the stack first so that the windows have the correct + * stacking order. This is relevant for workspace switching where we map the + * windows because mapping may generate EnterNotify events. When they are + * generated in the wrong order, this will cause focus problems when switching + * workspaces. + * */ void x_push_changes(Con *con) { con_state *state; - DLOG("\n\n PUSHING CHANGES\n\n"); - x_push_node(con); - DLOG("-- PUSHING WINDOW STACK --\n"); bool order_changed = false; /* X11 correctly represents the stack if we push it from bottom to top */ @@ -587,6 +590,9 @@ void x_push_changes(Con *con) { state->initial = false; } + DLOG("\n\n PUSHING CHANGES\n\n"); + x_push_node(con); + xcb_window_t to_focus = focused->frame; if (focused->window != NULL) to_focus = focused->window->id; From ad95d5bb1febf068f782dc43264c7db3e0d1b4e1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Jan 2011 22:09:04 +0100 Subject: [PATCH 390/867] =?UTF-8?q?bugfix:=20you=20can=E2=80=99t=20unfulls?= =?UTF-8?q?creen=20workspaces=20(Thanks=20Merovius)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/con.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/con.c b/src/con.c index b151d297..82574b34 100644 --- a/src/con.c +++ b/src/con.c @@ -406,6 +406,12 @@ void con_fix_percent(Con *con, int action) { */ void con_toggle_fullscreen(Con *con) { Con *workspace, *fullscreen; + + if (con->type == CT_WORKSPACE) { + DLOG("You cannot make a workspace fullscreen.\n"); + return; + } + DLOG("toggling fullscreen for %p / %s\n", con, con->name); if (con->fullscreen_mode == CF_NONE) { /* 1: check if there already is a fullscreen con */ From d9bfd8843fb42e27f633ffed9f9578394cdc39aa Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Jan 2011 22:58:22 +0100 Subject: [PATCH 391/867] Bugfix: fix restoring the orientation (Thanks Merovius/fernandotcl) --- src/load_layout.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/load_layout.c b/src/load_layout.c index 8d532da4..7602d620 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -95,6 +95,17 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) { json_node->sticky_group = scalloc((len+1) * sizeof(char)); memcpy(json_node->sticky_group, val, len); LOG("sticky_group of this container is %s\n", json_node->sticky_group); + } else if (strcasecmp(last_key, "orientation") == 0) { + char *buf = NULL; + asprintf(&buf, "%.*s", len, val); + if (strcasecmp(buf, "none") == 0) + json_node->orientation = NO_ORIENTATION; + else if (strcasecmp(buf, "horizontal") == 0) + json_node->orientation = HORIZ; + else if (strcasecmp(buf, "vertical") == 0) + json_node->orientation = VERT; + else LOG("Unhandled orientation: %s\n", buf); + free(buf); } } return 1; @@ -102,9 +113,6 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) { static int json_int(void *ctx, long val) { LOG("int %d for key %s\n", val, last_key); - if (strcasecmp(last_key, "orientation") == 0) { - json_node->orientation = val; - } if (strcasecmp(last_key, "layout") == 0) { json_node->layout = val; } From d9dd245bcc420f2e0d151fe5f1ee659463222221 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 22 Jan 2011 17:00:27 +0100 Subject: [PATCH 392/867] Bugfix: Use strdup() for the initial value of socket_path (Thanks mseed) --- i3-msg/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3-msg/main.c b/i3-msg/main.c index 6a51ff55..c6862fae 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -109,7 +109,7 @@ static void ipc_recv_message(int sockfd, uint32_t message_type, int main(int argc, char *argv[]) { char *socket_path; if ((socket_path = getenv("I3SOCK")) == NULL) { - socket_path = "/tmp/i3-ipc.sock"; + socket_path = strdup("/tmp/i3-ipc.sock"); } int o, option_index = 0; int message_type = I3_IPC_MESSAGE_TYPE_COMMAND; From 9223a39a65d93f2dd025451369c900297c4c4b0d Mon Sep 17 00:00:00 2001 From: Raphael Kubo da Costa Date: Sat, 22 Jan 2011 16:03:29 -0200 Subject: [PATCH 393/867] Build fix: Explicitly include stdint.h before cfgparse.tab.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cfgparse.tab.h uses uint32_t, which is defined in stdint.h. Should fix the build of 3.ε-bf2 on FreeBSD. Signed-off-by: Raphael Kubo da Costa --- src/cfgparse.l | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cfgparse.l b/src/cfgparse.l index 0c0cee70..2d319134 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -10,6 +10,7 @@ */ #include #include +#include /* Defines uint32_t, required by cfgparse.tab.h */ #include "cfgparse.tab.h" #include From 485555ef72dc99cff68cab78cc0c9da914702618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sat, 22 Jan 2011 15:54:49 -0200 Subject: [PATCH 394/867] Round up as well if needed (thanks Merovius). --- src/render.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/render.c b/src/render.c index 9d214ce6..b79d463f 100644 --- a/src/render.c +++ b/src/render.c @@ -118,10 +118,11 @@ void render_con(Con *con, bool render_fullscreen) { double percentage = child->percent > 0.0 ? child->percent : 1.0 / children; assigned += sizes[i++] = percentage * total; } - while (assigned < total) { + int signal = assigned < total ? 1 : -1; + while (assigned != total) { for (i = 0; i < children && assigned < total; ++i) { - ++sizes[i]; - ++assigned; + sizes[i] += signal; + assigned += signal; } } } From d04da62bb440583979b9bc11bff62c559cb1e8dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sat, 22 Jan 2011 16:09:11 -0200 Subject: [PATCH 395/867] Round up as well if needed (thanks Merovius). --- src/layout.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/layout.c b/src/layout.c index 9af1ffd9..c8a21bff 100644 --- a/src/layout.c +++ b/src/layout.c @@ -713,9 +713,10 @@ void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) { /* Correct rounding errors */ int error = r_ws->rect.width - total_col_width, error_index = r_ws->cols - 1; + int signal = error < 0 ? 1 : -1; while (error) { - ++col_width[error_index]; - --error; + col_width[error_index] -= signal; + error += signal; error_index = error_index == 0 ? r_ws->cols - 1 : error_index - 1; } @@ -732,9 +733,10 @@ void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) { /* Correct rounding errors */ error = workspace_height(r_ws) - total_row_height; error_index = r_ws->rows - 1; + signal = error < 0 ? 1 : -1; while (error) { - ++row_height[error_index]; - --error; + row_height[error_index] -= signal; + error += signal; error_index = error_index == 0 ? r_ws->rows - 1 : error_index - 1; } From 89917976c7689b6466bc8cfce05d981d26d98183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Tue, 25 Jan 2011 21:47:37 -0200 Subject: [PATCH 396/867] Crash when we get the percentages wrong. Better to crash with an assertion than to get into an infinite loop. We cold work around this, but there's a bug here and it's not a rounding bug, so it's better not to conceal it. --- src/render.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/render.c b/src/render.c index b79d463f..c6ebb5cc 100644 --- a/src/render.c +++ b/src/render.c @@ -118,9 +118,12 @@ void render_con(Con *con, bool render_fullscreen) { double percentage = child->percent > 0.0 ? child->percent : 1.0 / children; assigned += sizes[i++] = percentage * total; } + assert(assigned == total || + (assigned > total && assigned - total <= children * 2) || + (assigned < total && total - assigned <= children * 2)); int signal = assigned < total ? 1 : -1; while (assigned != total) { - for (i = 0; i < children && assigned < total; ++i) { + for (i = 0; i < children && assigned != total; ++i) { sizes[i] += signal; assigned += signal; } From a93f4643ecfc9b8ad9d806090e531fb125906e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Tue, 25 Jan 2011 21:49:22 -0200 Subject: [PATCH 397/867] Only fix the percentages after we insert the container. This is what floating.c does and it allows us to unify the logic that calculates those percentages. --- src/tree.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tree.c b/src/tree.c index 48a25eb2..1f477133 100644 --- a/src/tree.c +++ b/src/tree.c @@ -71,11 +71,13 @@ Con *tree_open_con(Con *con) { assert(con != NULL); - /* 3: re-calculate child->percent for each child */ + /* 3. create the container and attach it to its parent */ + Con *new = con_new(con); + + /* 4: re-calculate child->percent for each child */ con_fix_percent(con, WINDOW_ADD); - /* 4: add a new container leaf to this con */ - Con *new = con_new(con); + /* 5: focus the new container */ con_focus(new); return new; From 45227fba547fe8d264dce91a48143b6d1d413530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Tue, 25 Jan 2011 21:50:23 -0200 Subject: [PATCH 398/867] A new logic to calculate the percentages. It's slower, but this way we make sure that the resulting percentages *ALWAYS* sum up to 1.0 (or as close to that as we get with double math). --- src/con.c | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/con.c b/src/con.c index 82574b34..6439b1ed 100644 --- a/src/con.c +++ b/src/con.c @@ -385,17 +385,38 @@ int con_num_children(Con *con) { void con_fix_percent(Con *con, int action) { Con *child; int children = con_num_children(con); - /* TODO: better document why this math works */ - double fix; - if (action == WINDOW_ADD) - fix = (1.0 - (1.0 / (children+1))); - else - fix = 1.0 / (1.0 - (1.0 / (children+1))); + // calculate how much we have distributed and how many containers + // with a percentage set we have + double total = 0.0; + int children_with_percent = 0; TAILQ_FOREACH(child, &(con->nodes_head), nodes) { - if (child->percent <= 0.0) - continue; - child->percent *= fix; + if (child->percent > 0.0) { + total += child->percent; + ++children_with_percent; + } + } + + // if there were children without a percentage set, set to a value that + // will make those children proportional to all others + if (children_with_percent != children) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + if (child->percent <= 0.0) { + if (children_with_percent == 0) + total += (child->percent = 1.0); + else total += (child->percent = total / children_with_percent); + } + } + } + + // if we got a zero, just distribute the space equally, otherwise + // distribute according to the proportions we got + if (total == 0.0) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) + child->percent = 1.0 / children; + } else if (total != 1.0) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) + child->percent /= total; } } From bc82fc7e9f12cfe3caed6f754ec548faf97ec9dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Tue, 25 Jan 2011 22:12:43 -0200 Subject: [PATCH 399/867] This parameter is no longer needed. The algorithm is now always the same, doesn't matter if we're adding or removing a container to/from its parent. --- include/con.h | 3 +-- src/con.c | 2 +- src/floating.c | 4 ++-- src/tree.c | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/include/con.h b/include/con.h index 1abc09a0..5788aea2 100644 --- a/include/con.h +++ b/include/con.h @@ -112,8 +112,7 @@ void con_detach(Con *con); * container. * */ -void con_fix_percent(Con *con, int action); -enum { WINDOW_ADD = 0, WINDOW_REMOVE = 1 }; +void con_fix_percent(Con *con); /** * Toggles fullscreen mode for the given container. Fullscreen mode will not be diff --git a/src/con.c b/src/con.c index 6439b1ed..598033c7 100644 --- a/src/con.c +++ b/src/con.c @@ -382,7 +382,7 @@ int con_num_children(Con *con) { * container. * */ -void con_fix_percent(Con *con, int action) { +void con_fix_percent(Con *con) { Con *child; int children = con_num_children(con); diff --git a/src/floating.c b/src/floating.c index 85b2c0ae..89350f94 100644 --- a/src/floating.c +++ b/src/floating.c @@ -68,7 +68,7 @@ void floating_enable(Con *con, bool automatic) { TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); TAILQ_REMOVE(&(con->parent->focus_head), con, focused); - con_fix_percent(con->parent, WINDOW_REMOVE); + con_fix_percent(con->parent); /* 2: create a new container to render the decoration on, add * it as a floating window to the workspace */ @@ -155,7 +155,7 @@ void floating_disable(Con *con, bool automatic) { con->floating = FLOATING_USER_OFF; - con_fix_percent(con->parent, WINDOW_ADD); + con_fix_percent(con->parent); // TODO: don’t influence focus handling when Con was not focused before. con_focus(con); } diff --git a/src/tree.c b/src/tree.c index 1f477133..bff101df 100644 --- a/src/tree.c +++ b/src/tree.c @@ -75,7 +75,7 @@ Con *tree_open_con(Con *con) { Con *new = con_new(con); /* 4: re-calculate child->percent for each child */ - con_fix_percent(con, WINDOW_ADD); + con_fix_percent(con); /* 5: focus the new container */ con_focus(new); @@ -145,7 +145,7 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { if (con->type != CT_FLOATING_CON) { /* If the container is *not* floating, we might need to re-distribute * percentage values for the resized containers. */ - con_fix_percent(parent, WINDOW_REMOVE); + con_fix_percent(parent); } if (con_is_floating(con)) { From 568cafd4ec1f9d818f4b2ee4167a7b5666f7836a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Tue, 25 Jan 2011 22:14:04 -0200 Subject: [PATCH 400/867] Fix the resize algorithm I broke earlier. The reason it was broken was that it was ok for the sum of the percentages to be something other than 1.0. Now this is no longer the case, the sum of the percentages must always be 1.0 or an assertion will fail when we render the containers. --- src/resize.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/resize.c b/src/resize.c index 94771551..9c465e18 100644 --- a/src/resize.c +++ b/src/resize.c @@ -107,11 +107,14 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, DLOG("Done, pixels = %d\n", pixels); + // if we got thus far, the containers must have + // percentages associated with them + assert(first->percent > 0.0); + assert(second->percent > 0.0); + + // calculate the new percentage for the first container double new_percent, difference; - int children = con_num_children(first->parent); - double percent = 1.0 / children; - if (first->percent > 0.0) - percent = first->percent; + double percent = first->percent; DLOG("percent = %f\n", percent); int original = (orientation == HORIZ ? first->rect.width : first->rect.height); DLOG("original = %d\n", original); @@ -119,15 +122,15 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, difference = percent - new_percent; DLOG("difference = %f\n", difference); DLOG("new percent = %f\n", new_percent); - first->percent = new_percent; - double s_percent = 1.0 / children; - if (second->percent > 0.0) - s_percent = second->percent; - + // calculate the new percentage for the second container + double s_percent = second->percent; second->percent = s_percent + difference; DLOG("second->percent = %f\n", second->percent); + // now we must make sure that the sum of the percentages remain 1.0 + con_fix_percent(first->parent); + return 0; } From 07eb20851f245dd1cef26195bc0319fefa3b572f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Tue, 25 Jan 2011 22:23:18 -0200 Subject: [PATCH 401/867] Fix floating mode according to the new requirements. At all times any given non-leaf container should have the sum of the percentages of its children == 1.0, otherwise we'll crash on an assertion failure. --- src/floating.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/floating.c b/src/floating.c index 89350f94..9e1637ab 100644 --- a/src/floating.c +++ b/src/floating.c @@ -102,6 +102,7 @@ void floating_enable(Con *con, bool automatic) { /* 3: attach the child to the new parent container */ con->parent = nc; + con->percent = 1.0; con->floating = FLOATING_USER_ON; /* Some clients (like GIMP’s color picker window) get mapped From 432563d6e709ea3caa77015a8a647c876f73fd88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Tue, 25 Jan 2011 22:49:23 -0200 Subject: [PATCH 402/867] Fix the percentages when moving containers. --- src/con.c | 8 ++++++-- src/tree.c | 20 +++++++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/con.c b/src/con.c index 598033c7..5e3f9283 100644 --- a/src/con.c +++ b/src/con.c @@ -509,10 +509,14 @@ void con_move_to_workspace(Con *con, Con *workspace) { con_detach(con); con_attach(con, next, false); - /* 6: keep focus on the current workspace */ + /* 6: fix the percentages */ + con_fix_percent(parent); + con_fix_percent(next); + + /* 7: keep focus on the current workspace */ con_focus(focus_next); - /* 7: check if the parent container is empty now and close it */ + /* 8: check if the parent container is empty now and close it */ if (parent->type != CT_WORKSPACE && TAILQ_EMPTY(&(parent->nodes_head))) { DLOG("Closing empty parent container\n"); diff --git a/src/tree.c b/src/tree.c index bff101df..0a18e88d 100644 --- a/src/tree.c +++ b/src/tree.c @@ -411,10 +411,14 @@ void tree_move(char way, orientation_t orientation) { /* 4: switch workspace orientation */ parent->orientation = orientation; - /* 4: attach the new split container to the workspace */ + /* 5: attach the new split container to the workspace */ DLOG("Attaching new split to ws\n"); con_attach(new, parent, false); + /* 6: fix the percentages */ + con_fix_percent(new); + con_fix_percent(parent); + if (old_focused) con_focus(old_focused); @@ -452,6 +456,7 @@ void tree_move(char way, orientation_t orientation) { } con_detach(focused); + con_fix_percent(focused->parent); focused->parent = next->parent; TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, focused, nodes); @@ -478,6 +483,7 @@ void tree_move(char way, orientation_t orientation) { } con_detach(focused); + con_fix_percent(focused); focused->parent = next->parent; /* After going down in the tree, we insert the container *after* @@ -491,6 +497,14 @@ void tree_move(char way, orientation_t orientation) { /* TODO: don’t influence focus handling? */ } + /* fix the percentages in the container we moved to */ + int children = con_num_children(next->parent); + if (children == 1) + focused->percent = 1.0; + else + focused->percent = 1.0 / (children - 1); + con_fix_percent(next->parent); + /* We need to call con_focus() to fix the focus stack "above" the container * we just inserted the focused container into (otherwise, the parent * container(s) would still point to the old container(s)). */ @@ -500,6 +514,10 @@ void tree_move(char way, orientation_t orientation) { DLOG("Old container empty after moving. Let's close it\n"); tree_close(old_parent, false, false); } + else { + /* fix the percentages in the container we moved from */ + con_fix_percent(old_parent); + } tree_flatten(croot); } From fe851b85f01237ea67ce0a90efa978a8cf873070 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 27 Jan 2011 15:40:02 +0100 Subject: [PATCH 403/867] RandR: respect primary output --- include/data.h | 1 + src/randr.c | 51 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/include/data.h b/include/data.h index fb6ae4e0..b3974a3a 100644 --- a/include/data.h +++ b/include/data.h @@ -198,6 +198,7 @@ struct xoutput { * two stages) */ bool changed; bool to_be_disabled; + bool primary; /** x, y, width, height */ Rect rect; diff --git a/src/randr.c b/src/randr.c index 43a73442..f6e925a9 100644 --- a/src/randr.c +++ b/src/randr.c @@ -24,6 +24,9 @@ typedef xcb_randr_get_crtc_info_reply_t crtc_info; typedef xcb_randr_mode_info_t mode_info; typedef xcb_randr_get_screen_resources_current_reply_t resources_reply; +/* Pointer to the result of the query for primary output */ +xcb_randr_get_output_primary_reply_t *primary; + /* Stores all outputs available in your current session. */ struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs); @@ -386,6 +389,7 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, if (!existing) new = scalloc(sizeof(Output)); new->id = id; + new->primary = (primary && primary->output == id); FREE(new->name); asprintf(&new->name, "%.*s", xcb_randr_get_output_info_name_length(output), @@ -397,9 +401,11 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, * we do not need to change the list ever again (we only update the * position/size) */ if (output->crtc == XCB_NONE) { - if (!existing) - TAILQ_INSERT_TAIL(&outputs, new, outputs); - else if (new->active) + if (!existing) { + if (new->primary) + TAILQ_INSERT_HEAD(&outputs, new, outputs); + else TAILQ_INSERT_TAIL(&outputs, new, outputs); + } else if (new->active) new->to_be_disabled = true; return; } @@ -431,8 +437,11 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, * does not exist in the first place, the case is simple: we either * need to insert the new output or we are done. */ if (!updated || !existing) { - if (!existing) - TAILQ_INSERT_TAIL(&outputs, new, outputs); + if (!existing) { + if (new->primary) + TAILQ_INSERT_HEAD(&outputs, new, outputs); + else TAILQ_INSERT_TAIL(&outputs, new, outputs); + } return; } @@ -445,8 +454,10 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, */ void randr_query_outputs() { Output *output, *other, *first; + xcb_randr_get_output_primary_cookie_t pcookie; xcb_randr_get_screen_resources_current_cookie_t rcookie; resources_reply *res; + /* timestamp of the configuration so that we get consistent replies to all * requests (if the configuration changes between our different calls) */ xcb_timestamp_t cts; @@ -457,8 +468,13 @@ void randr_query_outputs() { if (randr_disabled) return; - /* Get screen resources (crtcs, outputs, modes) */ + /* Get screen resources (primary output, crtcs, outputs, modes) */ rcookie = xcb_randr_get_screen_resources_current(conn, root); + pcookie = xcb_randr_get_output_primary(conn, root); + + if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL) + ELOG("Could not get RandR primary output\n"); + else DLOG("primary output is %08x\n", primary->output); if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) { disable_randr(conn); return; @@ -484,14 +500,13 @@ void randr_query_outputs() { free(output); } - free(res); /* Check for clones, disable the clones and reduce the mode to the * lowest common mode */ TAILQ_FOREACH(output, &outputs, outputs) { if (!output->active || output->to_be_disabled) continue; - DLOG("output %p, position (%d, %d), checking for clones\n", - output, output->rect.x, output->rect.y); + DLOG("output %p / %s, position (%d, %d), checking for clones\n", + output, output->name, output->rect.x, output->rect.y); for (other = output; other != TAILQ_END(&outputs); @@ -568,6 +583,7 @@ void randr_query_outputs() { } output->to_be_disabled = false; + output->changed = false; } if (output->active && output->con == NULL) { @@ -599,8 +615,25 @@ void randr_query_outputs() { } #endif + /* Focus the primary screen, if possible */ + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->primary || !output->con) + continue; + + DLOG("Focusing primary output %s\n", output->name); + Con *next = output->con; + while (!TAILQ_EMPTY(&(next->focus_head))) + next = TAILQ_FIRST(&(next->focus_head)); + + DLOG("focusing %p\n", next); + con_focus(next); + } + /* render_layout flushes */ tree_render(); + + FREE(res); + FREE(primary); } /* From 2f5d111936ea47e0c9c321596c7d23f9677f6907 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 27 Jan 2011 15:53:14 +0100 Subject: [PATCH 404/867] when re-inserting a floating con, start with a more fair percent value --- src/floating.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/floating.c b/src/floating.c index 9e1637ab..50987de4 100644 --- a/src/floating.c +++ b/src/floating.c @@ -151,6 +151,13 @@ void floating_disable(Con *con, bool automatic) { /* 3: re-attach to previous parent */ con->parent = con_get_workspace(con); + + /* XXX: We adjust the percentage value to start with a fair value. Floating + * cons always have 1.0 as percent which doesn’t work so well when + * re-inserting (the formerly floating con would get 50% of the target + * con). */ + con->percent = (1.0 / con_num_children(con->parent)); + TAILQ_INSERT_TAIL(&(con->parent->nodes_head), con, nodes); TAILQ_INSERT_TAIL(&(con->parent->focus_head), con, focused); From f462a9a215e1079bd9a047f7097972527df49184 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 27 Jan 2011 16:04:17 +0100 Subject: [PATCH 405/867] re-insert floating cons next to the currently focused con of the appropriate workspace --- include/con.h | 8 ++++++++ src/con.c | 12 ++++++++++++ src/floating.c | 6 ++++-- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/include/con.h b/include/con.h index 5788aea2..de537218 100644 --- a/include/con.h +++ b/include/con.h @@ -152,6 +152,14 @@ Con *con_next_focused(Con *con); */ Con *con_get_next(Con *con, char way, orientation_t orientation); +/** + * Returns the focused con inside this client, descending the tree as far as + * possible. This comes in handy when attaching a con to a workspace at the + * currently focused position, for example. + * + */ +Con *con_descend_focused(Con *con); + /** * Returns a "relative" Rect which contains the amount of pixels that need to * be added to the original Rect to get the final position (obviously the diff --git a/src/con.c b/src/con.c index 5e3f9283..2f02bfcd 100644 --- a/src/con.c +++ b/src/con.c @@ -627,6 +627,18 @@ Con *con_get_next(Con *con, char way, orientation_t orientation) { return next; } +/* + * Returns the focused con inside this client, descending the tree as far as + * possible. This comes in handy when attaching a con to a workspace at the + * currently focused position, for example. + * + */ +Con *con_descend_focused(Con *con) { + Con *next = con; + while (!TAILQ_EMPTY(&(next->focus_head))) + next = TAILQ_FIRST(&(next->focus_head)); + return next; +} /* * Returns a "relative" Rect which contains the amount of pixels that need to diff --git a/src/floating.c b/src/floating.c index 50987de4..b4d21963 100644 --- a/src/floating.c +++ b/src/floating.c @@ -149,8 +149,10 @@ void floating_disable(Con *con, bool automatic) { TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused); tree_close(con->parent, false, false); - /* 3: re-attach to previous parent */ - con->parent = con_get_workspace(con); + /* 3: re-attach to the parent of the currently focused con on the workspace + * this floating con was on */ + Con *focused = con_descend_focused(con_get_workspace(con)); + con->parent = focused->parent; /* XXX: We adjust the percentage value to start with a fair value. Floating * cons always have 1.0 as percent which doesn’t work so well when From 3383437705ad1979863dcb60a7138dd8471e3787 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 27 Jan 2011 16:08:25 +0100 Subject: [PATCH 406/867] refactor some places to use con_descend_focused instead of duplicating code --- src/con.c | 8 ++------ src/handlers.c | 5 +---- src/randr.c | 7 +------ src/tree.c | 16 ++++------------ src/workspace.c | 6 +----- 5 files changed, 9 insertions(+), 33 deletions(-) diff --git a/src/con.c b/src/con.c index 2f02bfcd..50e303ca 100644 --- a/src/con.c +++ b/src/con.c @@ -483,12 +483,8 @@ void con_move_to_workspace(Con *con, Con *workspace) { * container is moved away */ Con *focus_next = con_next_focused(con); - /* 2: get the focused container of this workspace by going down as far as - * possible */ - Con *next = workspace; - - while (!TAILQ_EMPTY(&(next->focus_head))) - next = TAILQ_FIRST(&(next->focus_head)); + /* 2: get the focused container of this workspace */ + Con *next = con_descend_focused(workspace); /* 3: we go up one level, but only when next is a normal container */ if (next->type != CT_WORKSPACE) diff --git a/src/handlers.c b/src/handlers.c index 3fc18cf5..4123caee 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -210,11 +210,8 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, if (config.disable_focus_follows_mouse) return 1; - Con *next = con; - while (!TAILQ_EMPTY(&(next->focus_head))) - next = TAILQ_FIRST(&(next->focus_head)); - con_focus(next); + con_focus(con_descend_focused(con)); x_push_changes(croot); return 1; diff --git a/src/randr.c b/src/randr.c index f6e925a9..e8b044bb 100644 --- a/src/randr.c +++ b/src/randr.c @@ -621,12 +621,7 @@ void randr_query_outputs() { continue; DLOG("Focusing primary output %s\n", output->name); - Con *next = output->con; - while (!TAILQ_EMPTY(&(next->focus_head))) - next = TAILQ_FIRST(&(next->focus_head)); - - DLOG("focusing %p\n", next); - con_focus(next); + con_focus(con_descend_focused(output->con)); } /* render_layout flushes */ diff --git a/src/tree.c b/src/tree.c index 0a18e88d..9af64def 100644 --- a/src/tree.c +++ b/src/tree.c @@ -155,11 +155,8 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { DLOG("parent container killed\n"); if (con == focused) { DLOG("This is the focused container, i need to find another one to focus. I start looking at ws = %p\n", ws); - next = ws; - /* now go down the focus stack as far as - * possible, excluding the current container */ - while (!TAILQ_EMPTY(&(next->focus_head))) - next = TAILQ_FIRST(&(next->focus_head)); + /* go down the focus stack as far as possible */ + next = con_descend_focused(ws); dont_kill_parent = true; DLOG("Alright, focusing %p\n", next); @@ -361,11 +358,7 @@ void tree_next(char way, orientation_t orientation) { /* 3: focus choice comes in here. at the moment we will go down * until we find a window */ /* TODO: check for window, atm we only go down as far as possible */ - while (!TAILQ_EMPTY(&(next->focus_head))) - next = TAILQ_FIRST(&(next->focus_head)); - - DLOG("focusing %p\n", next); - con_focus(next); + con_focus(con_descend_focused(next)); } /* @@ -450,8 +443,7 @@ void tree_move(char way, orientation_t orientation) { next = current; } else { /* if this is a split container, we need to go down */ - while (!TAILQ_EMPTY(&(next->focus_head))) - next = TAILQ_FIRST(&(next->focus_head)); + next = con_descend_focused(next); } } diff --git a/src/workspace.c b/src/workspace.c index a1e653ea..55f7dd17 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -226,11 +226,7 @@ void workspace_show(const char *num) { workspace_reassign_sticky(workspace); LOG("switching to %p\n", workspace); - Con *next = workspace; - - while (!TAILQ_EMPTY(&(next->focus_head))) - next = TAILQ_FIRST(&(next->focus_head)); - + Con *next = con_descend_focused(workspace); if (TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) { /* check if this workspace is currently visible */ From 3e08ceaff9ce03a700518fee6a457dd57b51e2f1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 27 Jan 2011 16:26:19 +0100 Subject: [PATCH 407/867] bugfix: resizing problem when resizing vertically on the top border (Thanks julien) --- src/click.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/click.c b/src/click.c index 243820cb..b6a752ef 100644 --- a/src/click.c +++ b/src/click.c @@ -329,13 +329,17 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x, border_click = %d, clicked_into = %p\n", event->event_x, event->event_y, con, event->event, border_click, clicked_into); DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width); - /* TODO: das problem ist, dass TAILQ_PREV etc. nicht die orientation beachtet. */ Con *first = NULL, *second = NULL; if (clicked_into) { DLOG("BORDER top\n"); - second = clicked_into; - if ((first = con_get_next(clicked_into, 'p', VERT)) != NULL) + if ((first = con_get_next(clicked_into, 'p', VERT)) != NULL) { + /* instead of setting second = clicked_into we get the container + * below the one which con_get_next returned. This way, if + * clicked_into is inside another split-con, we get the correct + * parent to work with. */ + second = TAILQ_NEXT(first, nodes); resize_graphical_handler(first, second, VERT, event); + } } else if (event->event_x >= 0 && event->event_x <= bsr.x && event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) { DLOG("BORDER left\n"); From 334e41daa45150e6aa66c768c1262f4f186918e8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 27 Jan 2011 16:51:16 +0100 Subject: [PATCH 408/867] =?UTF-8?q?bugfix:=20don=E2=80=99t=20assume=20a=20?= =?UTF-8?q?workspace=20always=20has=20tiling=20cons=20when=20focusing=20(T?= =?UTF-8?q?hanks=20mseed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tree.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tree.c b/src/tree.c index 9af64def..aaf57cfb 100644 --- a/src/tree.c +++ b/src/tree.c @@ -341,6 +341,11 @@ void tree_next(char way, orientation_t orientation) { Con *current = TAILQ_FIRST(&(parent->focus_head)); assert(current != TAILQ_END(&(parent->focus_head))); + if (TAILQ_EMPTY(&(parent->nodes_head))) { + DLOG("Nothing to focus here, move along...\n"); + return; + } + /* 2: chose next (or previous) */ Con *next; if (way == 'n') { From 676afce540c04ca925eb4ce9d6fa880562049b1c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 27 Jan 2011 16:51:41 +0100 Subject: [PATCH 409/867] bugfix: correctly move cons out of floating cons when the workspace has no other tiling cons (Thanks mseed) --- src/tree.c | 126 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 72 insertions(+), 54 deletions(-) diff --git a/src/tree.c b/src/tree.c index aaf57cfb..6df58e22 100644 --- a/src/tree.c +++ b/src/tree.c @@ -384,7 +384,13 @@ void tree_move(char way, orientation_t orientation) { * and the orientation still does not match. In this case, we split the * workspace to have the same look & feel as in older i3 releases. */ if (parent->type == CT_WORKSPACE) { - DLOG("Arrived at workspace, splitting...\n"); + DLOG("Arrived at workspace\n"); + /* In case of moving a window out of a floating con, there might be + * not a single tiling container. Makes no sense to split then, so + * just use the workspace as target */ + if (TAILQ_EMPTY(&(parent->nodes_head))) + break; + /* 1: create a new split container */ Con *new = con_new(NULL); new->parent = parent; @@ -430,77 +436,89 @@ void tree_move(char way, orientation_t orientation) { Con *current = TAILQ_FIRST(&(parent->focus_head)); assert(current != TAILQ_END(&(parent->focus_head))); - /* 2: chose next (or previous) */ - Con *next = current; - if (way == 'n') { - LOG("i would insert it after %p / %s\n", next, next->name); - - /* Have a look at the next container: If there is no next container or - * if it is a leaf node, we move the focused one left to it. However, - * for split containers, we descend into it. */ - next = TAILQ_NEXT(next, nodes); - if (next == TAILQ_END(&(next->parent->nodes_head))) { - if (focused == current) - return; - next = current; - } else { - if (level_changed && con_is_leaf(next)) { - next = current; - } else { - /* if this is a split container, we need to go down */ - next = con_descend_focused(next); - } - } - + /* If we have no tiling cons (when moving a window out of a floating con to + * an otherwise empty workspace for example), we just attach the window to + * the workspace. */ + if (TAILQ_EMPTY(&(parent->nodes_head))) { con_detach(focused); con_fix_percent(focused->parent); - focused->parent = next->parent; + focused->parent = parent; - TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, focused, nodes); - TAILQ_INSERT_HEAD(&(next->parent->focus_head), focused, focused); - /* TODO: don’t influence focus handling? */ + TAILQ_INSERT_HEAD(&(parent->nodes_head), focused, nodes); + TAILQ_INSERT_HEAD(&(parent->focus_head), focused, focused); } else { - LOG("i would insert it before %p / %s\n", current, current->name); - bool gone_down = false; - next = TAILQ_PREV(next, nodes_head, nodes); - if (next == TAILQ_END(&(next->parent->nodes_head))) { - if (focused == current) - return; - next = current; - } else { - if (level_changed && con_is_leaf(next)) { + /* 2: chose next (or previous) */ + Con *next = current; + if (way == 'n') { + LOG("i would insert it after %p / %s\n", next, next->name); + + /* Have a look at the next container: If there is no next container or + * if it is a leaf node, we move the focused one left to it. However, + * for split containers, we descend into it. */ + next = TAILQ_NEXT(next, nodes); + if (next == TAILQ_END(&(next->parent->nodes_head))) { + if (focused == current) + return; next = current; } else { - /* if this is a split container, we need to go down */ - while (!TAILQ_EMPTY(&(next->focus_head))) { - gone_down = true; - next = TAILQ_FIRST(&(next->focus_head)); + if (level_changed && con_is_leaf(next)) { + next = current; + } else { + /* if this is a split container, we need to go down */ + next = con_descend_focused(next); } } - } - con_detach(focused); - con_fix_percent(focused); - focused->parent = next->parent; + con_detach(focused); + con_fix_percent(focused->parent); + focused->parent = next->parent; - /* After going down in the tree, we insert the container *after* - * the currently focused one even though the command used "before". - * This is to keep the user experience clear, since the before/after - * only signifies the direction of the movement on top-level */ - if (gone_down) TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, focused, nodes); - else TAILQ_INSERT_BEFORE(next, focused, nodes); - TAILQ_INSERT_HEAD(&(next->parent->focus_head), focused, focused); - /* TODO: don’t influence focus handling? */ + TAILQ_INSERT_HEAD(&(next->parent->focus_head), focused, focused); + /* TODO: don’t influence focus handling? */ + } else { + LOG("i would insert it before %p / %s\n", current, current->name); + bool gone_down = false; + next = TAILQ_PREV(next, nodes_head, nodes); + if (next == TAILQ_END(&(next->parent->nodes_head))) { + if (focused == current) + return; + next = current; + } else { + if (level_changed && con_is_leaf(next)) { + next = current; + } else { + /* if this is a split container, we need to go down */ + while (!TAILQ_EMPTY(&(next->focus_head))) { + gone_down = true; + next = TAILQ_FIRST(&(next->focus_head)); + } + } + } + + con_detach(focused); + con_fix_percent(focused); + focused->parent = next->parent; + + /* After going down in the tree, we insert the container *after* + * the currently focused one even though the command used "before". + * This is to keep the user experience clear, since the before/after + * only signifies the direction of the movement on top-level */ + if (gone_down) + TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, focused, nodes); + else TAILQ_INSERT_BEFORE(next, focused, nodes); + TAILQ_INSERT_HEAD(&(next->parent->focus_head), focused, focused); + /* TODO: don’t influence focus handling? */ + } } /* fix the percentages in the container we moved to */ - int children = con_num_children(next->parent); + int children = con_num_children(focused->parent); if (children == 1) focused->percent = 1.0; else focused->percent = 1.0 / (children - 1); - con_fix_percent(next->parent); + con_fix_percent(focused->parent); /* We need to call con_focus() to fix the focus stack "above" the container * we just inserted the focused container into (otherwise, the parent From b2ba02f801dda492a5c2553624f22d956573b7e1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 27 Jan 2011 17:23:29 +0100 Subject: [PATCH 410/867] add a testcase for correct floating con reattaching --- testcases/t/46-floating-reinsert.t | 62 ++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 testcases/t/46-floating-reinsert.t diff --git a/testcases/t/46-floating-reinsert.t b/testcases/t/46-floating-reinsert.t new file mode 100644 index 00000000..e24a37e0 --- /dev/null +++ b/testcases/t/46-floating-reinsert.t @@ -0,0 +1,62 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +use X11::XCB qw(:all); +use Time::HiRes qw(sleep); +use i3test tests => 5; + +BEGIN { + use_ok('X11::XCB::Window'); +} + +my $x = X11::XCB::Connection->new; + +my $tmp = get_unused_workspace; +cmd "workspace $tmp"; + +my $left = open_standard_window($x); +sleep 0.25; +my $mid = open_standard_window($x); +sleep 0.25; + +cmd 'split v'; +my $bottom = open_standard_window($x); +sleep 0.25; + +my ($nodes, $focus) = get_ws_content($tmp); + +############################################################################# +# 1: open a floating window, get it mapped +############################################################################# + +my $x = X11::XCB::Connection->new; + +# Create a floating window +my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#C0C0C0', + # replace the type with 'utility' as soon as the coercion works again in X11::XCB + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), +); + +isa_ok($window, 'X11::XCB::Window'); + +$window->map; + +sleep 0.25; + +ok($window->mapped, 'Window is mapped'); + +($nodes, $focus) = get_ws_content($tmp); +is(@{$nodes->[1]->{nodes}}, 2, 'two windows in split con'); + +############################################################################# +# 2: make it tiling, see where it ends up +############################################################################# + +cmd 'mode toggle'; + +my ($nodes, $focus) = get_ws_content($tmp); + +is(@{$nodes->[1]->{nodes}}, 3, 'three windows in split con after mode toggle'); From 36e20a93a09ecfe9ae30576afa01aaa8c1ce7148 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 27 Jan 2011 23:25:36 +0100 Subject: [PATCH 411/867] add a regression test for the floating move thing --- testcases/t/47-regress-floatingmove.t | 39 +++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 testcases/t/47-regress-floatingmove.t diff --git a/testcases/t/47-regress-floatingmove.t b/testcases/t/47-regress-floatingmove.t new file mode 100644 index 00000000..ae6fe963 --- /dev/null +++ b/testcases/t/47-regress-floatingmove.t @@ -0,0 +1,39 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Regression test for moving a con outside of a floating con when there are no +# tiling cons on a workspace +# +use X11::XCB qw(:all); +use Time::HiRes qw(sleep); +use i3test tests => 2; + +BEGIN { + use_ok('X11::XCB::Window'); +} + +my $x = X11::XCB::Connection->new; + +my $tmp = get_unused_workspace; +cmd "workspace $tmp"; + +my $left = open_standard_window($x); +sleep 0.25; +my $mid = open_standard_window($x); +sleep 0.25; + +# go to workspace level +cmd 'level up'; +sleep 0.25; + +# make it floating +cmd 'mode toggle'; +sleep 0.25; + +# move the con outside the floating con +cmd 'move before v'; +sleep 0.25; + +my $tree = i3('/tmp/nestedcons')->get_tree->recv; +my @nodes = @{$tree->{nodes}}; +ok(@nodes > 0, 'i3 still lives'); From 5f4123f842ae26a6168237ed901d817b74e94316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Thu, 27 Jan 2011 19:42:03 -0200 Subject: [PATCH 412/867] Fix some resizing issues (thanks mseed). --- src/tree.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/tree.c b/src/tree.c index 6df58e22..98f50162 100644 --- a/src/tree.c +++ b/src/tree.c @@ -420,7 +420,6 @@ void tree_move(char way, orientation_t orientation) { con_attach(new, parent, false); /* 6: fix the percentages */ - con_fix_percent(new); con_fix_percent(parent); if (old_focused) @@ -439,6 +438,7 @@ void tree_move(char way, orientation_t orientation) { /* If we have no tiling cons (when moving a window out of a floating con to * an otherwise empty workspace for example), we just attach the window to * the workspace. */ + bool fix_percent = 0; if (TAILQ_EMPTY(&(parent->nodes_head))) { con_detach(focused); con_fix_percent(focused->parent); @@ -470,8 +470,11 @@ void tree_move(char way, orientation_t orientation) { } con_detach(focused); - con_fix_percent(focused->parent); - focused->parent = next->parent; + if (focused->parent != next->parent) { + con_fix_percent(focused->parent); + focused->parent = next->parent; + fix_percent = 1; + } TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, focused, nodes); TAILQ_INSERT_HEAD(&(next->parent->focus_head), focused, focused); @@ -497,8 +500,11 @@ void tree_move(char way, orientation_t orientation) { } con_detach(focused); - con_fix_percent(focused); - focused->parent = next->parent; + if (focused->parent != next->parent) { + con_fix_percent(focused->parent); + focused->parent = next->parent; + fix_percent = 1; + } /* After going down in the tree, we insert the container *after* * the currently focused one even though the command used "before". @@ -513,12 +519,14 @@ void tree_move(char way, orientation_t orientation) { } /* fix the percentages in the container we moved to */ - int children = con_num_children(focused->parent); - if (children == 1) - focused->percent = 1.0; - else - focused->percent = 1.0 / (children - 1); - con_fix_percent(focused->parent); + if (fix_percent) { + int children = con_num_children(focused->parent); + if (children == 1) + focused->percent = 1.0; + else + focused->percent = 1.0 / (children - 1); + con_fix_percent(focused->parent); + } /* We need to call con_focus() to fix the focus stack "above" the container * we just inserted the focused container into (otherwise, the parent @@ -528,8 +536,7 @@ void tree_move(char way, orientation_t orientation) { if (con_num_children(old_parent) == 0) { DLOG("Old container empty after moving. Let's close it\n"); tree_close(old_parent, false, false); - } - else { + } else if (level_changed) { /* fix the percentages in the container we moved from */ con_fix_percent(old_parent); } From b29af954f6299e3636d3027fce749b6365e5d9b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Thu, 27 Jan 2011 20:58:49 -0200 Subject: [PATCH 413/867] Don't mess with the percentages in tree_flatten. --- src/tree.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tree.c b/src/tree.c index 98f50162..aa79587b 100644 --- a/src/tree.c +++ b/src/tree.c @@ -438,7 +438,7 @@ void tree_move(char way, orientation_t orientation) { /* If we have no tiling cons (when moving a window out of a floating con to * an otherwise empty workspace for example), we just attach the window to * the workspace. */ - bool fix_percent = 0; + bool fix_percent = false; if (TAILQ_EMPTY(&(parent->nodes_head))) { con_detach(focused); con_fix_percent(focused->parent); @@ -473,7 +473,7 @@ void tree_move(char way, orientation_t orientation) { if (focused->parent != next->parent) { con_fix_percent(focused->parent); focused->parent = next->parent; - fix_percent = 1; + fix_percent = true; } TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, focused, nodes); @@ -503,7 +503,7 @@ void tree_move(char way, orientation_t orientation) { if (focused->parent != next->parent) { con_fix_percent(focused->parent); focused->parent = next->parent; - fix_percent = 1; + fix_percent = true; } /* After going down in the tree, we insert the container *after* @@ -597,6 +597,7 @@ void tree_flatten(Con *con) { TAILQ_INSERT_BEFORE(con, current, nodes); DLOG("attaching to focus list\n"); TAILQ_INSERT_TAIL(&(parent->focus_head), current, focused); + current->percent = con->percent; } DLOG("re-attached all\n"); From 79bbde8766f3cefdb1f5df001dcdb4223b2d421a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 Jan 2011 00:31:26 +0100 Subject: [PATCH 414/867] add a flag to disable the signalhandler --- src/main.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main.c b/src/main.c index 5736324f..27603357 100644 --- a/src/main.c +++ b/src/main.c @@ -148,6 +148,7 @@ int main(int argc, char *argv[]) { bool delete_layout_path; bool only_check_config = false; bool force_xinerama = false; + bool disable_signalhandler = false; xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS]; static struct option long_options[] = { {"no-autostart", no_argument, 0, 'a'}, @@ -157,6 +158,7 @@ int main(int argc, char *argv[]) { {"layout", required_argument, 0, 'L'}, {"restart", required_argument, 0, 0}, {"force-xinerama", no_argument, 0, 0}, + {"disable-signalhandler", no_argument, 0, 0}, {0, 0, 0, 0} }; int option_index = 0, opt; @@ -189,7 +191,7 @@ int main(int argc, char *argv[]) { only_check_config = true; break; case 'v': - printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n"); + printf("i3 version " I3_VERSION " © 2009-2011 Michael Stapelberg and contributors\n"); exit(EXIT_SUCCESS); case 'V': set_verbosity(true); @@ -210,6 +212,9 @@ int main(int argc, char *argv[]) { "Please check if your driver really does not support RandR " "and disable this option as soon as you can.\n"); break; + } else if (strcmp(long_options[option_index].name, "disable-signalhandler") == 0) { + disable_signalhandler = true; + break; } else if (strcmp(long_options[option_index].name, "restart") == 0) { FREE(layout_path); layout_path = sstrdup(optarg); @@ -494,7 +499,8 @@ int main(int argc, char *argv[]) { manage_existing_windows(root); - setup_signal_handler(); + if (!disable_signalhandler) + setup_signal_handler(); /* Ignore SIGPIPE to survive errors when an IPC client disconnects * while we are sending him a message */ From 7b01bc5eb703a8191f53a464cac6339db89420b5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 Jan 2011 00:41:53 +0100 Subject: [PATCH 415/867] =?UTF-8?q?Bugfix:=20use=20the=20global=20root=20v?= =?UTF-8?q?ariable,=20don=E2=80=99t=20get=20the=20first=20one=20(Thanks=20?= =?UTF-8?q?quaec)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The case of an X11 server having multiple displays is handled correctly by the code in src/mainx.c. However, due to some functions not being correctly refactored and still getting the first screen (and also the first root window) from the XCB connection, i3 was operating on the wrong root window. --- i3-input/i3-input.h | 2 ++ i3-input/main.c | 6 ++++-- i3-input/xcb.c | 1 - src/floating.c | 1 - src/sighandler.c | 1 - src/xcb.c | 1 - 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/i3-input/i3-input.h b/i3-input/i3-input.h index c699f6c5..581203d4 100644 --- a/i3-input/i3-input.h +++ b/i3-input/i3-input.h @@ -12,6 +12,8 @@ } \ while (0) +extern xcb_window_t root; + char *convert_ucs_to_utf8(char *input); char *convert_utf8_to_ucs2(char *input, int *real_strlen); uint32_t get_colorpixel(xcb_connection_t *conn, char *hex); diff --git a/i3-input/main.c b/i3-input/main.c index b9b4ba81..6efbbdec 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -50,6 +50,7 @@ static char *command_prefix; static char *prompt; static int prompt_len; static int limit; +xcb_window_t root; /* * Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for @@ -302,6 +303,9 @@ int main(int argc, char *argv[]) { if (xcb_connection_has_error(conn)) die("Cannot open display\n"); + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); + root = root_screen->root; + /* Set up event handlers for key press and key release */ xcb_event_handlers_t evenths; memset(&evenths, 0, sizeof(xcb_event_handlers_t)); @@ -320,8 +324,6 @@ int main(int argc, char *argv[]) { win = open_input_window(conn, 500, font_height + 8); /* Create pixmap */ - xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); - pixmap = xcb_generate_id(conn); pixmap_gc = xcb_generate_id(conn); xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8); diff --git a/i3-input/xcb.c b/i3-input/xcb.c index 661d4863..3c1d99e1 100644 --- a/i3-input/xcb.c +++ b/i3-input/xcb.c @@ -86,7 +86,6 @@ uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode) { * */ xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) { - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; xcb_window_t win = xcb_generate_id(conn); //xcb_cursor_t cursor_id = xcb_generate_id(conn); diff --git a/src/floating.c b/src/floating.c index b4d21963..c912dc71 100644 --- a/src/floating.c +++ b/src/floating.c @@ -325,7 +325,6 @@ void floating_resize_window(Con *con, bool proportional, void drag_pointer(Con *con, xcb_button_press_event_t *event, xcb_window_t confine_to, border_t border, callback_t callback, void *extra) { - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; uint32_t new_x, new_y; Rect old_rect; if (con != NULL) diff --git a/src/sighandler.c b/src/sighandler.c index ea2bf9fe..3a9ad82f 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -113,7 +113,6 @@ static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_p * */ static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, uint32_t width, uint32_t height) { - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; xcb_window_t win = xcb_generate_id(conn); uint32_t mask = 0; diff --git a/src/xcb.c b/src/xcb.c index ff7dadbc..53a90685 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -82,7 +82,6 @@ uint32_t get_colorpixel(char *hex) { */ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_class, enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values) { - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; xcb_window_t result = xcb_generate_id(conn); xcb_cursor_t cursor_id = xcb_generate_id(conn); From f395c141c8241eeef136ac115ad838865801af83 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 Jan 2011 00:42:55 +0100 Subject: [PATCH 416/867] bugfix: initialize root variable earlier --- src/main.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.c b/src/main.c index 27603357..c497ab9e 100644 --- a/src/main.c +++ b/src/main.c @@ -245,6 +245,10 @@ int main(int argc, char *argv[]) { if (xcb_connection_has_error(conn)) errx(EXIT_FAILURE, "Cannot open display\n"); + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); + root = root_screen->root; + root_depth = root_screen->root_depth; + load_configuration(conn, override_configpath, false); if (only_check_config) { LOG("Done checking configuration file. Exiting.\n"); @@ -255,10 +259,6 @@ int main(int argc, char *argv[]) { config.ipc_socket_path = getenv("I3SOCK"); } - xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); - root = root_screen->root; - root_depth = root_screen->root_depth; - uint32_t mask = XCB_CW_EVENT_MASK; uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video From 5b6ef3e665eacec38dcbcde93c83090864477eb7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 Jan 2011 00:47:49 +0100 Subject: [PATCH 417/867] make the sighandler handle SIGABRT --- src/sighandler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sighandler.c b/src/sighandler.c index 3a9ad82f..4b5fb103 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -220,6 +220,7 @@ void setup_signal_handler() { sigemptyset(&action.sa_mask); if (sigaction(SIGSEGV, &action, NULL) == -1 || + sigaction(SIGABRT, &action, NULL) == -1 || sigaction(SIGFPE, &action, NULL) == -1) ELOG("Could not setup signal handler"); } From 0e8fdab30252e6829f627236342b9ee677a17fb0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 Jan 2011 00:48:22 +0100 Subject: [PATCH 418/867] disable sighandler for testsuite runs --- testcases/complete-run.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index aeacc284..bae34ac4 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -12,7 +12,7 @@ use TAP::Harness; use TAP::Parser::Aggregator; use File::Basename qw(basename); -my $i3cmd = "export DISPLAY=:0; exec " . abs_path("../i3") . " -V -d all -c " . abs_path("../i3.config"); +my $i3cmd = "export DISPLAY=:0; exec " . abs_path("../i3") . " -V -d all --disable-sighandler -c " . abs_path("../i3.config"); # 1: get a list of all testcases my $curdir = getcwd(); From 2d82868a1a37708abea904f8230a486abd704bc2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 Jan 2011 01:10:45 +0100 Subject: [PATCH 419/867] tests: implement does_i3_live for regression tests --- testcases/t/lib/i3test.pm | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 79ca0266..e53bb4db 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -2,6 +2,7 @@ package i3test; # vim:ts=4:sw=4:expandtab use File::Temp qw(tmpnam); +use Test::Builder; use X11::XCB::Rect; use X11::XCB::Window; use X11::XCB qw(:all); @@ -10,7 +11,9 @@ use List::Util qw(first); use v5.10; use Exporter (); -our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window cmd); +our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window cmd does_i3_live); + +my $tester = Test::Builder->new(); BEGIN { my $window_count = 0; @@ -123,4 +126,12 @@ sub cmd { i3("/tmp/nestedcons")->command(@_)->recv } +sub does_i3_live { + my $tree = i3('/tmp/nestedcons')->get_tree->recv; + my @nodes = @{$tree->{nodes}}; + my $ok = (@nodes > 0); + $tester->ok($ok, 'i3 still lives'); + return $ok; +} + 1 From e2975d80e9f5e6befcb7fa03f76431fe5bc7fb9e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 Jan 2011 01:12:56 +0100 Subject: [PATCH 420/867] implement make clean in testcases/ --- testcases/Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testcases/Makefile b/testcases/Makefile index 6d7898b8..edf5ee0d 100644 --- a/testcases/Makefile +++ b/testcases/Makefile @@ -1,6 +1,9 @@ test: PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/12*.t +clean: + rm -rf testsuite-* latest + all: test testfull: From 773bc5ba6d5597a6354ec35f32d1e70e5f3ae3b5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 Jan 2011 01:18:30 +0100 Subject: [PATCH 421/867] fix typo in testcases/complete-run.pl --- testcases/complete-run.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index bae34ac4..ee21bd5d 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -12,7 +12,7 @@ use TAP::Harness; use TAP::Parser::Aggregator; use File::Basename qw(basename); -my $i3cmd = "export DISPLAY=:0; exec " . abs_path("../i3") . " -V -d all --disable-sighandler -c " . abs_path("../i3.config"); +my $i3cmd = "export DISPLAY=:0; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c " . abs_path("../i3.config"); # 1: get a list of all testcases my $curdir = getcwd(); From dc6241456a817f926fd50fcee67c41ca68e6ed58 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 Jan 2011 01:21:38 +0100 Subject: [PATCH 422/867] testcases: use the does_i3_live function --- testcases/t/26-regress-close.t | 17 ++++++----------- testcases/t/27-regress-floating-parent.t | 4 +--- testcases/t/34-invalid-command.t | 8 ++------ testcases/t/42-regress-move-floating.t | 4 +--- testcases/t/47-regress-floatingmove.t | 4 +--- 5 files changed, 11 insertions(+), 26 deletions(-) diff --git a/testcases/t/26-regress-close.t b/testcases/t/26-regress-close.t index 125caf73..349ffca5 100644 --- a/testcases/t/26-regress-close.t +++ b/testcases/t/26-regress-close.t @@ -7,19 +7,14 @@ use i3test tests => 1; use X11::XCB qw(:all); -my $i3 = i3("/tmp/nestedcons"); - my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +cmd "workspace $tmp"; -$i3->command('open')->recv; -$i3->command('mode toggle')->recv; -$i3->command('kill')->recv; -$i3->command('kill')->recv; +cmd 'open'; +cmd 'mode toggle'; +cmd 'kill'; +cmd 'kill'; - -my $tree = $i3->get_tree->recv; -my @nodes = @{$tree->{nodes}}; -ok(@nodes > 0, 'i3 still lives'); +does_i3_live; diag( "Testing i3, Perl $], $^X" ); diff --git a/testcases/t/27-regress-floating-parent.t b/testcases/t/27-regress-floating-parent.t index 0074af6f..d72fd350 100644 --- a/testcases/t/27-regress-floating-parent.t +++ b/testcases/t/27-regress-floating-parent.t @@ -40,8 +40,6 @@ is(get_focused($tmp), $floating, 'floating window focused'); sleep 1; $i3->command('mode toggle')->recv; -my $tree = $i3->get_tree->recv; -my @nodes = @{$tree->{nodes}}; -ok(@nodes > 0, 'i3 still lives'); +does_i3_live; diag( "Testing i3, Perl $], $^X" ); diff --git a/testcases/t/34-invalid-command.t b/testcases/t/34-invalid-command.t index e1cc2c6f..cfb330a1 100644 --- a/testcases/t/34-invalid-command.t +++ b/testcases/t/34-invalid-command.t @@ -5,12 +5,8 @@ # use i3test tests => 1; -my $i3 = i3("/tmp/nestedcons"); +cmd 'blargh!'; -$i3->command("blargh!")->recv; - -my $tree = $i3->get_tree->recv; -my @nodes = @{$tree->{nodes}}; -ok(@nodes > 0, 'i3 still lives'); +does_i3_live; diag( "Testing i3, Perl $], $^X" ); diff --git a/testcases/t/42-regress-move-floating.t b/testcases/t/42-regress-move-floating.t index 2fc14177..1c4fae13 100644 --- a/testcases/t/42-regress-move-floating.t +++ b/testcases/t/42-regress-move-floating.t @@ -14,6 +14,4 @@ cmd 'open'; cmd 'mode toggle'; cmd "move workspace $otmp"; -my $tree = i3('/tmp/nestedcons')->get_tree->recv; -my @nodes = @{$tree->{nodes}}; -ok(@nodes > 0, 'i3 still lives'); +does_i3_live; diff --git a/testcases/t/47-regress-floatingmove.t b/testcases/t/47-regress-floatingmove.t index ae6fe963..189d2808 100644 --- a/testcases/t/47-regress-floatingmove.t +++ b/testcases/t/47-regress-floatingmove.t @@ -34,6 +34,4 @@ sleep 0.25; cmd 'move before v'; sleep 0.25; -my $tree = i3('/tmp/nestedcons')->get_tree->recv; -my @nodes = @{$tree->{nodes}}; -ok(@nodes > 0, 'i3 still lives'); +does_i3_live; From f465b3c11dbc10b037ea3882a8892d929a25a1de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Thu, 27 Jan 2011 21:09:48 -0200 Subject: [PATCH 423/867] Don't mess with the focus if we're not killing. If we're not killing the mapped window and we're not killing the parent window either in tree_close, then there's no reason to try to change the focus. This fixes focus issues when moving a container around another container (move up, left, bottom, right). --- src/tree.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/tree.c b/src/tree.c index aa79587b..9a2aab96 100644 --- a/src/tree.c +++ b/src/tree.c @@ -177,9 +177,14 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { } if (was_mapped || con == focused) { - DLOG("focusing %p / %s\n", next, next->name); - /* TODO: check if the container (or one of its children) was focused */ - con_focus(next); + if (kill_window || !dont_kill_parent) { + DLOG("focusing %p / %s\n", next, next->name); + /* TODO: check if the container (or one of its children) was focused */ + con_focus(next); + } + else { + DLOG("not focusing because we're not killing anybody"); + } } else { DLOG("not focusing, was not mapped\n"); } From 8be40932f2e69068ce89cf9009af4dd77c0fdf1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Thu, 27 Jan 2011 22:06:47 -0200 Subject: [PATCH 424/867] Fix assertion when moving out of a floating container. --- src/tree.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tree.c b/src/tree.c index 9a2aab96..ed953b34 100644 --- a/src/tree.c +++ b/src/tree.c @@ -448,6 +448,7 @@ void tree_move(char way, orientation_t orientation) { con_detach(focused); con_fix_percent(focused->parent); focused->parent = parent; + fix_percent = true; TAILQ_INSERT_HEAD(&(parent->nodes_head), focused, nodes); TAILQ_INSERT_HEAD(&(parent->focus_head), focused, focused); @@ -526,11 +527,12 @@ void tree_move(char way, orientation_t orientation) { /* fix the percentages in the container we moved to */ if (fix_percent) { int children = con_num_children(focused->parent); - if (children == 1) + if (children == 1) { focused->percent = 1.0; - else + } else { focused->percent = 1.0 / (children - 1); - con_fix_percent(focused->parent); + con_fix_percent(focused->parent); + } } /* We need to call con_focus() to fix the focus stack "above" the container From a88b809135633e62d43546b85988dd67f382b11c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 Jan 2011 13:07:00 +0100 Subject: [PATCH 425/867] =?UTF-8?q?docs/ipc:=20add=20reference=20to=20TheP?= =?UTF-8?q?ub=E2=80=99s=20python=20library?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ipc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/ipc b/docs/ipc index c35e9016..d6f1a253 100644 --- a/docs/ipc +++ b/docs/ipc @@ -299,3 +299,5 @@ Ruby:: http://github.com/badboy/i3-ipc Perl:: http://search.cpan.org/search?query=AnyEvent::I3 +Python:: + http://github.com/thepub/i3ipc From 0a7b3a04b7c830f1a42734872be8b7b32c1d1f08 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 Jan 2011 13:07:00 +0100 Subject: [PATCH 426/867] =?UTF-8?q?docs/ipc:=20add=20reference=20to=20TheP?= =?UTF-8?q?ub=E2=80=99s=20python=20library?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ipc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/ipc b/docs/ipc index 1c31d061..59bb4715 100644 --- a/docs/ipc +++ b/docs/ipc @@ -303,3 +303,5 @@ Ruby:: http://github.com/badboy/i3-ipc Perl:: http://search.cpan.org/search?query=AnyEvent::I3 +Python:: + http://github.com/thepub/i3ipc From 086d1563cb521c3d6e93899af5fdfe35d45103bb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 27 May 2010 20:21:17 +0200 Subject: [PATCH 427/867] Add documentation for external workspace bars --- docs/Makefile | 5 ++- docs/wsbar | 94 +++++++++++++++++++++++++++++++++++++++++++++++++ docs/wsbar.dia | Bin 0 -> 1899 bytes docs/wsbar.png | Bin 0 -> 14339 bytes 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 docs/wsbar create mode 100644 docs/wsbar.dia create mode 100644 docs/wsbar.png diff --git a/docs/Makefile b/docs/Makefile index b17413ca..79e5019a 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,5 +1,5 @@ -all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html +all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html hacking-howto.html: hacking-howto asciidoc -a toc -n $< @@ -16,6 +16,9 @@ ipc.html: ipc multi-monitor.html: multi-monitor asciidoc -a toc -n $< +wsbar.html: wsbar + asciidoc -a toc -n $< + clean: rm -f */*.{aux,log,toc,bm,pdf,dvi} rm -f *.log *.html diff --git a/docs/wsbar b/docs/wsbar new file mode 100644 index 00000000..9e379dd9 --- /dev/null +++ b/docs/wsbar @@ -0,0 +1,94 @@ +External workspace bars +======================= +Michael Stapelberg +May 2010 + +This document describes why the internal workspace bar is minimal and how an +external workspace bar can be used. It explains the concepts using +i3-wsbar+ +as the reference implementation. + +== Internal and external bars + +The internal workspace bar of i3 is meant to be a reasonable default so that +you can use i3 without having too much hassle when setting it up. It is quite +simple and intended to stay this way. So, there is no way to display your own +information in this bar (unlike dwm, wmii, awesome, …). + +We chose not to implement such a mechanism because that would be duplicating +already existing functionality of tools such as dzen2, xmobar and similar. +Instead, you should disable the internal bar and use an external workspace bar +(which communicates with i3 through its IPC interface). + +== dock mode + +You typically want to see the same workspace bar on every workspace on a +specific screen. Also, you don’t want to place the workspace bar somewhere +in your layout by hand. This is where dock mode comes in: When a program sets +the appropriate hint (_NET_WM_WINDOW_TYPE_DOCK), it will be managed in dock +mode by i3. That means it will be placed at the bottom of the screen (while +other edges of the screen are possible in the NetWM standard, this is not yet +implemented in i3), it will not overlap any other window and it will be on +every workspace for the specific screen it was placed on initially. + +== The IPC interface + +In the context of using an external workspace bar, the IPC interface needs to +provide the bar program with the current workspaces and output (as in VGA-1, +LVDS-1, …) configuration. In the other direction, the program has to be able +to switch to specific workspaces. + +By default, the IPC interface is enabled and places its UNIX socket in ++~/.i3/ipc.sock+. + +To learn more about the protocol which is used for IPC, see +docs/ipc+. + +== Output changes (on-the-fly) + +i3 implements the RandR API and can handle changing outputs quite well. So, an +external workspace bar implementation needs to make sure that when you change +the resolution of any of your screens (or enable/disable an output), the bars +will be adjusted properly. + +== i3-wsbar, the reference implementation + +Please keep in mind that +i3-wsbar+ is just a reference implementation. It is +shipped with i3 to have a reasonable default. Thus, +i3-wsbar+ is designed to +work well with dzen2 and there are no plans to make it more generic. + +=== The big picture + +The most common reason to use an external workspace bar is to integrate system +information such as what +i3status+ provides into the workspace bar (to save +screen space). So, we have +i3status+ or a similar program, which only provides +text output (formatted in some way). To display this text nicely on the screen, +there are programs such as dzen2, xmobar and similar. We will stick to dzen2 +from here on. So, we have the output of i3status, which needs to go into dzen2 +somehow. But we also want to display the list of workspaces. +i3-wsbar+ takes +input on stdin, combines it with a formatted workspace list and pipes it to +dzen2. + +Please note that +i3-wsbar+ does not print its output to stdout. Instead, it +launches the dzen2 instances on its own. This is necessary to handle changes +in the available outputs (to place a new dzen2 on a new screen for example). + +image:wsbar.png["Overview",link="wsbar.png"] + +=== Running i3-wsbar + +The most simple usage of i3-wsbar looks like this: +------------------------------- +i3-wsbar -c "dzen2 -x %x -dock" +------------------------------- + +The +%x+ in the command name will be replaced by the X position of the output +for which this workspace bar is running. i3 will automatically place the +workspace bar on the correct output when dzen2 is started in dock mode. The +bar which you will see should look exactly like the internal bar of i3. + +To actually get a benefit, you want to give +i3-wsbar+ some input: +------------------------------------------ +i3status | i3-wsbar -c "dzen2 -x %x -dock" +------------------------------------------ + +It is recommended to place the above command in your i3 configuration file +to start it automatically with i3. diff --git a/docs/wsbar.dia b/docs/wsbar.dia new file mode 100644 index 0000000000000000000000000000000000000000..d117ce075a4400334f757e706e5fb7853049d6d6 GIT binary patch literal 1899 zcmV-x2bB09iwFP!000021MOVfZsRr(ea}}I-j^0gXkJ8#T(7g}w&(>|papijuLf-~ zwpv*-B<9wKetSttyS8O1zQ}Z9)ypnP;mmn;1;BE z@UI9%G0=?${p*WfZ(V>dlrZ;-yF#fXnA|Gai^M{X`V%qzdzQqvk>Af-744>R7$?0u z5svzAZnCRCD8vjlw+Q!oJ6XuhF0VUjAE@G5G?2)hYayZ=N zyl~-p;ga*h1?hMhCn^zv+J>COaVSNUqpIXq9*>hwMW|KmsbQhxrQZZf#XI_cBf@l_ z0pz#8S=^!4J4*upY2)2!Q6uF(@YQ_$U}?H?>5rDA-vwzf31wC9gGilB{NriFmw%m% z!B>wr)P`JI?GoWLNhLjeiJ096zD#$h*=Cy>N%NxFVArgd+wD6s`K{7|ok4`+Q6`1> z538lRSNy;QUDJpd8SLir5>QjG!WkRX6BJ%H$Oa59vO z@ib1NZ7<4H9u8}~1iXk9}Lxfy} zkAn*${iBRGd0{6r90rlj@@5Yas^cv= zPSs;5%aWzWYb09F-B=_^d|xjap|hdA`t74-0aY&j=Y z-Vkt*xgd2JbX@8+gWS}`ef9Aeq_T5Tn{I}%8ITBrS+tPVKWXKv@pOWvDo1dbaolBH zs$XWv`6!f~i3Xh$&|v0|kYmG_eR+^z<{3|nWkkp@(lL_*P)nHb>OP%_q+`M}G2ymK zc2w9JDn!pih4cheNa-cl3}urE3lQkyj0g$Si#~$dKYaN#xD1x^87Xj^0nb5xkTOCD z=GcYA;rJZvx21)zf&5^5$nPXcY`1X|o0=US3~eT{HT7`7H4$OJFEMGBU*4U=ZZx8O za@g9j+)OKbR&&^luV2i4O*!lv$DvLuHgXU_i|g0nLJ5?j*M-g8K5y4_yFt zh5)KU8N2xDCC5)SMcN3Tsu+mc-2+EV%l90E<1y%(I&I`Qj`7%_%N_f5+7*MW&9A~2 zRHfaOQlGv&<)C6`*K)CQon@1jShj(IloXathQ`C@`DPY$xc5RVyYTxPBvU%;evQ`6 zpXVq8YF@<4OwADTfMYkWVnAH&pk`-|wLR;PtR;Ig3lWK#OfQy6Je6tMMHSYgiVB_V z!itw1R@BiGJxSqArVGTgekMDkD31+IRuAHwp|IC6RL4+zs%1w~?b|y>jjL^m^YwiW z775CL7z{>-j(OXO^|wYe9o2rD#--P`5WDY<*Z@Q5HEkM`sV~XoHEkL^S@w*p^|9MDZqM;V+l9AvN!-Sjw*;9= zN6SaGH3;O~GcXjfAu$3Nw%&Hr zCAm+Z*N$A38r2E?4uCZxzBnX?Bk-wXWhj~}}O l+O|EwDmCnqM$eiw%0Fi>3BJDAc#H7$#eco*%P2z58ta?0xnPQBjh4iu)WF1qJ0PL{?G_1qBU+f`Xcf zjR}mrCv}|#-Y`w%Wh7A^9{)0$^I}j?UZ6lEB{bYq_7*(6=b@>WN7_kM5YLlG(e-GbgA0X?o79VolRFx!d`3w%kY6@Ibo4e# zc4@IpA^JBa$ijQALi9~JEi&?4VGPI~H$NCWK`*-yzzjk#v5~U;zjXXiK=6;j-1zNn z(3a-s&p2Z{lf}!QEz_3w1eeSfW~+BOUu6~+hR!cGQ#t)6QUGti9Be#%H<%)l5FQ@R z{XT&S%3ZbsVnmDZRhjS{9dYzB?Sm zG#zah_1_#tse`^SZ9ThmmkQRjft{)6DR10{ajpc#D4G1qhMXQOr5_(Vi|f>cr{7Ka z9xXN=bWOq6dswfc^c=+I>E#gwBw@rNU!t^=oL)weyX{Z6Z*_wx(g%BcR{{e9f}mPu z`aK8x^})4E-Z80jwd$G%18ow4-@X4vr@XZ!*hupgkI7ZA5=dJgk8-(dfHqyuyEIjr z;Q*`uxDg4`h)w9;N$pSJ59<@Z`CyUi_%7@~EYXzt#G|@ONe@i;#Jh>%a|cPW>37#R zt15^{JH1-x^$^ysJXYn@UZZqhaq+uZ_OipAqEdi)g`Ia*r&){9bNV-HTrM30g0Ri< zS%hoV#)(Q>_Iuo5sPK5f_~fKolb0Zu_yvto%k_4U`4oJO9cS#szOiJ2Zkqemy%5r-QN0;iQNx-)JVxvKuU)nwOVQg}Ex@Et0!Op$VI2-;FM?U8{h&3SZ)4EzqI`R2B+KpsWA93oiA{k8v^~zt%S#FVh(^MqXkGPML?pSlxA*e2 z0=D=OnIRFL{x-T`ERPR*?b_nLR~<~#!RULVm*tU|n5dA<6F9d%ATKKnh;{c{)Rf^z zI4xq_P`q`Qa&qgFbOr3HWWx7P&%l~;lvvkkR%GZ<# zA1$Zr-56Q;koO;UvlYL3ZKnB=z7G#q=XAgE_CoA}sjA{77rW_Vluzu>fq~lj(fm%? z{&Tyz%f9r7esc=_Hg>L->HRtZGE&Ep50*}I7p+4p35F(mAL-ZD))ZN2P$niO_QbV# zt#zk;DFN|-aOukO)qDDTO*Vgi^xj>i+t^Ix6S6tqy{v%6zlP|*4u^k;9$v0CUF}NAZ5i-vsk@JNABXK+1HbrPeNowZ(oU)A<)3IX z3kx-MgMsjJ)4A${$?@@V6?OIZ78VxXivZP02H^0a_XvW|kYM4GFC~K3*49b-4(r7q z0F`DJZ(i;o6nZ!vTOBK+*7zB|*XUy<#YAjXQT=BpjDl6c_e5lgUPMIXUmdPG=0t$W z|FJ(`+!?B|`xz zfxPhe*4h48Fx{e)%)4LF#i;05&SM<4Z<2g910%bWTvlN%?^}KDk__&1FhTMB%rmB_ z`tSuPV4y8M=2TQv-g{ltF^k&jNjHEc;ai-6IuMZ-RT`t^_Gi{Erp zgQ*&tn$a=Iec$Ncf9Lk#WleW`PGR85hK}{ry6ONpW$m+N`r4l0qVZl5|L$yk%=WhR z_32i}G!97sOQyEbz_*VbN#T22t@m-@+jYTW!+Jy@7Qq|pjr9ODA0Y?YFeA^9l@*ig zi-VzX)5v8Pqgw{c!PI1pm}#@9CqMJ*z8l@{S~eZ-R+8>bOFT4*D!tuedbDO5*hVo;(Y`FOjn%MZ~Tv2m{|D?>Q9z`|baH_J2mQ zCp%vu;&a~MLgZWk{_dD4TCl4>|ICyl6hi<(kmCMd9Sfj$S-$dW|2?Y!N;T3N9V<-_ zu^P;N4-oo4dWuyl&$}Fn%JNWBLEt}`wxzJL>1dzyZ{Jd z`rLycD1au8v`%gTAShT>2UETA)XYfI@3&DzIbONs>zo{m4$aE`FE`K%2|MU-e~$D7MMXjPfcHL=_q%%jec zOFcv~eL7ErIC5G_8l_DkgqgXN)cTw=ef#Fh_j(j>Zzc$)-F!|DHfD)pm>}EP$Jn$V zcI`R@%DZV!{6;W1R2CWCNu8ZZcB-LssBMiHIb07)!%YHsl&IdAI{&5G=_N!){P4Ak z^l-}AsKunsP>m1Zj#$h-{@50Dhc1K6?u>h==}H>9XU`BJRb$ zjlN_mz~kQfrb|w}Tf&l~Dyv3;96nbA92)%&Xy5REs11m0&cAXm&~AAi^`YTW9{|0* zFdn6Ul8frGH*Ie#gT(+bh=T-^zC_hRTSB{LDZjUBTk;Phuv7sAY;#Q}aTkGo2z`;z z1r;7Ft{r4~A#%$Nh^v8G#^4K_PV96GPuhtvJ)1-tnO1R&1zxyt(!NVlarMG_zZYue1MpjEi7 zH75iiJgkrr-v#N;DZrNl6i6{D=DTAPx>@!cz=4f3r~YFy>h#gNBO18D3~oS>sd6ED zbhfKyCAh$R3atz!Ay4>O0ZB-0|ku! zW+x#>X90m$)O`iE5=Lik`#6x_ei^gM{EyIntS(U!?LQo&NGOUsNnJw-R0bW49xpRh zgffZViluaL`7P(1XX{(`AP3`h67DXl58ka_^V$X)9SQ~N5>^)q+P%l5sRtj>Ce}RW zuC+z-905#4tpf0IZ1C`?`vu}VFT>LVKDMt?)_jlW6JE6$jQBn6UC4=T)C9uY0_-+q{fH>l9KeX4>(ppXxYQLPR z2ndCX8(u{RRd2g>jn-@cYkJ++di^(8W^(kM(3*S%55ey{S+FhzGqTTxrB{|1gH zb1IHE-X*C(^4_yD1)UUbI3PmeT=}VZwOf6joA_9jn$p-_15L?7hSNWL66b$xuLn0f zzTti0-rSYo0KeJ!r65dN_(ZV(le3TJ2OLekc=ZZ%p=9!fpWD}mqz$)g6rl^ybyBkXQN zxHg_wO!na$@03xMbSpUJ@e%!Bcs976#HD9DUCPdb11lb6yt?;f*Qf#9vR|49<6HJ3 zA%Oi!jRY{JhRqqp;8oA4^qkq+x#?y&U*btb>)|-`>gXztdEjh&zQXd}ju)$Ds+v}G zH9mog_pIvj)MZID9{%J2mD_l}Jmqe+fnmge-Do}MAe|_EU&Mr%?&hExWp}PByjpp{ z^a2nqJ7;!HZs@Mh`%SPl=KI`qiThpoEpXvhx6IQ${Te^$&~3m9f0?RgL)5&)Rk-MK zj6(D2oY@66;^+STFHKvo4TxKrp+ZkQS?s}#oTB-MCffdto=Pg%BtO3R@n4pxiSdV? zFBJRc9RZCqMsja(v53S4 z5gizGMauklF&@hFRr|^7mOV7%Ni;ojxCoX}XS4W_g|L3Aw@SrlC?K zHE}EPVZJ{Qqic!#HZN&yGhKOK(g4+NM*OsUy`^*CHV{D^d2apPaa_B_lDcfciHASV z+-~t;yW6+FruJcW+w+Ws0Q;Vh34~cK5h8tr*I9f!+8ZR5)hb$;7||dgFC0t!H0*X~ zN5q+zkMn^(Yu!87;D(lWHOtK6A~!da)Nf^?>>U#t6%}b2ym{oh)5gg1Pt_Pb@elU_ zo1?45nPw{CyDXI52=T}ep|vhnDuEMyh6zoY>6Z9Zx~8^VsS8#zBP}<&$maC~nxkm> zKYupcCu?ap7L1H!pjW6U1h#s8U7uUcSGrs9+uCqgx!0?Pc-*%OPN~GB)9UXEN=nTh zCMSx0PFtssuRD(54(|JD8TC$hyk6_vC)b`^rs2or#i9BEqUPj^#*#qWYd>;P~}@8 z(=?4D1tbwVg?S=}tF7YUsO$~yut<@LN}D-?PikrYP9Q7u3N*Y69(MRkdfYY0f4|;$%%>&m3 zGZW}c-Y$vnjt^{nJ2S;XR+LQ;i(0?x@+Wm#jIP8Fu;OIdpuGc8sgdK)-lte)fLRv1 z?O6aCx&pz%I1|2(N+MF`ek6goz}%?a>qs)YUWOfoF;XmjpiG=H-B9R3OKY%|%x}Iu zi_+&*pC#>~44-nUj^V@mt4i>M`=Sh|qO9<_oi6zU4Dgaq@O=BuVd)kRlbRZ_q^jzg zw6TkCRO)?UMcs7Ju>JFX_1~+sbX7qh}hu&RIaAkKKYEq)hz)w`@rmcE>?jx{JA2M3q&bTyNp^Ll!(zpvg zrZ=mYU^e1*<>?qv*;K%A_0yysObJ5C8;S$!L z;VsK&1tlLB#w5Tm09#RhYuI{gbxJENAfB2km=d-4eAl$wYe=nP3x4ikpa@yUW#E@^ z8Ix7yXQ#dQDT2sIL5PIVrI`lj(x*k=YE7BU95~3zzGX>+iOCpVK6S>5V#tzViB7V< zcQzl&22=Qj(=L=z{3aL=%$Pd@%j7wrhpJNy(zB!cpxp{OGE2a=Ytat!c=Jmc82JJX zJ3M**-nO5LrG9Zh*>>oa8&6X2Ptj9*sjxJ$QqBtv%zK^{lb zna9#}Pum99xS0CE*M)OEyNM*}i-2P-p;ZPkpx0qsic2J{@_^+bZ$LY=0omxtF$~Cr zDMpXX*qvw5-g6Cz4M0lc&z|CUmB=qtuYC{ac6F{{axlBhn!*5CyQgF~ZYq~u-sDSe zKEJNfy9PzGSNRoaOvpHIqLL)ad;)v+!LdG95_FV$hF&i}E_@lVPj7W1FQU64YMg44 z>%2Dr{AfNWc=*yuo;YwEe%MVtQ?=<+*mtgO;Nv$CMu!Xp!`VNk-p(YTMUa$DL@h|K z`I}h3>U-&JDlYGHbfp!Q_a1?&kJ0qBpOt61j^vXOp?G7&3S7sRf(0PagBRg^`74zu#u%;3J@JzY|J;n8k5^@`C`1D$ygY4sOW# zXGg3&C(6s8+Mnla-5e$TYfBmnr9!DwvJ2{OELZu-&7Ix6_uHfu&eyyrcrf1MAZ40S zA5XJ0lWA4UN@$NM&PnjNyzmzVk<_!Hh18$k(<2+hjFon0Oemgb)cyTj{Wj2Ms|0xM z%x`)Thqo>*N1vL4$4$k3vA9a%Vus~aKiDXies6cvR5t|`eTc6F=2A_n>br)>)V^zO+l5yepX^aBp`B2Jo2GMV>o ztd`rXx1jjuW{544g;WSV3i*{pynS501-Q-6%Z57{y3QG}XK>t!Z(F4KkeaHRWZ5D* zCHJyBi_dOfxad7YOk`v=_FMjtvwNZIZF?3HCRS@?oux&&mjn;9L)^dcHuIHrk#$dh zPxi*Qw9~ZlWYoOBJf|x^PHCpklaoprWaW+*aGO5N*T~U&7aw&3i=z1eCHJT_cK13oM$aN#k(FD=fFmO6Ok>M7T&BW8x1`h0w_LQhV_2N{J%02EyNdA+3 z%mJ>3 zP1VYnUpYd{N~|FefnwgKT|Sco?6etT9x`VHy?{&;cxR%Y&tcUCH!!VJ8Jl74Y6BIY zAKpl-ZQ{V-&C5wv3UsNFyMt^~;^z=VtaJ;?F1;*fsNWDxmuUpk%dM}>f)9A7;^Eyu z6(_aL5$Uu|?AM)jLQsnz&;T+YSuI72BwVfo7WqV6aUmMwU^?CYcah^`X`ZkEqSvWRSgL!1C|HlhIXS4Np9Z;ZqeGO)(5 zCGHUHYSfWT5bG$>fEEFh9BMA&)6S6@e0s`CX7UX>zk9X_Sp*Za9jAmtCQ(Lg2AZX1 znV=I#`=@FmV{}Kke@hn}TUfV*P$;t@dT#EU%o-tL_m~&w)<~@v>^Ve0bwqmb1tpHa zJ8s_H2n^F7*~w{bzQjx*BIHwJH91D0Fog1#xC*0y{xaGJ>+P!?B53%SUj)X171&Wx zE-RX;AUXMIG1KM8QZ#$|mu#D_j{U%D+C8q~5+_DLWfEbFl)(s*#&AzBU1;2h!$?h( z@_UC&8UMlv)Q7mkUi5s`thptUARe8Fcw)%nzEs_&yhVGUhsYi_M_ppx6c$A*MqST- zlX(c}jrs*u6IQ>hl_XH7;oQzZ{JYG^!_|J)>p{HW=4ce$4PS3 zLZL&+Lo;HkdtZjJ$V_?5Zh>})AtC?S8EIv#69_ky8P24yxhuDu7N@ubBVS0=BA3GV z`B+Hmo=!uCe@q5q+!_8(p@Y-KjZof~pG7TyGbern=ZNZRNXxJ)&LDIogvKePV@dpJK&6b9S~+p>&WWp8weN2{!4O;^iIfNEHJBW zMR&)id=3|rSdqFAX{&5|84$JyZ!6p8;6f9ZQi(FgcXUuom%LHLG|VY=U9~6n#UQQw z-Vp5qBM#?}vHtgdM8wGg=k^wcns_&xiieD9zUhGkA8$A?V1Ya_vQ3-z7`fxgv`UUL zl$AJjX~<0Px8~18Ip0!7IN&)heiXN8poVZe+ZT_o`a#6v!Gwp99 zra?Y3c1DUOgsC5%)|3_28mP~u%R0j9@GhU#<%hTvOt>RIdv-sFaUXKi9btH88(;G zZ7?q4@%PwaOTG|X)wdAlX%rNS;>TV9FU|%E64E&>^d}f+&u5)S?Cfq9+vu>Z*AFsE zTZ6yTwkeKR6S$`%2YJ?U0~l^z1?b+=*)XKlG~m9B?5Y>>#D}K1_?KX$AJG1~@xHs4 zM-VG8e;WOc+kvA?{98_tmNg&0AW|?0NdP|o@ZgUec@InIJ!l0A<0sg!EFxdLl)L|c zYjXJpswjGaW+W?tzc5lgu30gUl7g6ZRLkV^eqb(`yA%wdyS>B6N;rRdG#!YY7S_FA zm(ILdDX}bs$Z!b5Za4j^hqKM)N>OkN5b(~K9ty=Hcy_8~Rl86yLUO4bkj;qJBr%M5 zkI1e9+;L>XXaoh$^@_@Nsom0-+%3R>sdeZ=FN+D!zRe0pe3Uq-FTV9@AUDc zgRJFKDnOmIS{%bv>-Y6n_tcCWKjAWVoL6Vk*uo>itkui1i17wKKPU#+a18@a@0u4H zx@G(NkQch(`w*Lv#Q{u?(+^nx%8vooJwTDkdBadG!vamUkn+3mqk42-3}UL)+?WbR27|SFPOSozsu3fgmIuVRm89(MjWKV# zfx+FH%$w<4?gqpDXbeWnF5AdKn_g*Sx#q29494m%TeMQvx|f3EqZRA@nf6R$)&X>} zDde>(4GrHLXw6`V`OQ{?yBjV@s$5XUA3>I|49Sf9<1dAJGr39Y?B-o^)Z4ArI~?F> zV~qg=j6f5E*uNEDD*O)AF8Y0+a_41kiz%d6#0{XD-v zkS#tL9+wiNxO}QY48LO$=E`MtWc^9~k|qC+ZVbIHzob&ww0um0YlQhp!(I-d9xm6rMF1~K?}36U-Lk%1SR zlvZ>Sw{!mT>d&J`d#c+|w-Hh5k72r?vX?c#j^d`>{VS3{%Q}qFU$9`Obxn)LO9r7a zq=`Xx!hjKXTKdTYS|BBc<4IV;a;elTVWO@BQ4@TF|6SQ&t1AX0^&bdm`Axti6Or+M zV{A@;9!?UUG_cP#QSem8K!f&BW}A5Kstp+{%>=<6co8u8%>;}#gq8)+(-6pxgP6mX z;L6&_7NCKQE}uJXUu(QYx#hE?F4Y$%HT6#@*!i@d$3y_*f^4H*li*K**cWsphYhMo zYZL-F`%KX>TmBPfvkCYTjJb=kRphvhwL6@6vwx;y7qeU%#uJ_T*c2o)N&2?yYLJdtp!0lg`xZ1`?$Hii9@6o*ZH;L^($COsyRy&Y zM(xf6_GZ)q3^5Fpw2Acd6VO?K&6=%Yny0TBFU7UdmVWgPA!42R{BtlUHl8EhUa$fn z2gHc=UB*Calfs{;0ACi-Q1lupf)OXl*!G-8THj!-ejNI#77L3pHd{pzD_&))Xw^Vx zS;=7T8B%roRpPyWMu!Mw^y4V{B_=d(R{+g2hxY}W*%Yzs5*@05@m3lWh@1qcfir4h zYatBgxt$h?vB>n6u+U$xobWlsT1&6NbMVC`?e&XxDK{YYE@R8dF^b{n+G>)dP&lB( zAm-hAmvQ9~!dW}A`-6bv6b%_F$D-}3%XW2n-fr53{gbYaCB*I?EnLK#9-_7pEi=4u z5~MZRh&e6e$g_Z|@A-WdF{=5f6NnQ)C&(v_ux`bJ>X_KpeBL>JOquIH_C`XsIWwYA zI)33O%Gqe6dEpEu11OATa!YEV9;)H~1EO}>r4DOv{1LLIjoLY~)>sZkD=6tg<5HhW zxy}2>t$9sG6-FRV<>?B*1Tb*sjcW5%2X<)CjuF1h7uwWuelz0mLKrBpg zswGjt$am&~KB2^h{S-zF9Bd4VcF>5&jch4HO73sz5=H^*4q((LGuM8i?iWk0wgTq*S>R`CpWp(vM@vy-sk z?3()6hDqb6Mh7IKi0JI9g!cwuw5LYQIv+HI=Pl5=hP_wlP+PtiBAE&p;bHMyZbSit zp@=<2RW;|_(bLt~TtjMz?HU8b%93(`VoTtx0iE(2LN=$=l=RSMTepnq=34hvKwNg@ z@iV)|O(|?o_U2b)?Q#ac@sX8n9Br?RMO5URH_`)N%CVVrcD?6AzsyK9=WnwxhM!jZ zXJB>4jcgfw#K`B|lM9Ha#x+9;^jmKGis`!(WqC?^D7iTT1^F`M({NRPz?4|$`aBSH zF-(SHss;*bNG)&6)xi338r%OeA0b>`ABe>#EGRhfF+c2PFP@`-K*Hc3(XF@Kwe{sa z*^y(g_5h9G;k9{wxNw$>vl{42L*0``xAOWX(TR^Q2hM;T4%jjE`hPy#{38cN0hJsC zJi!^mS|3zYWdf~sjH;3EE)Gz8T~w^Jp$a4eH5*?I5>SMoU*56nirrbhY+t5%8O7Ema9kJC65WXDivL=$F1%S1ID3RMfh~xM7@S2Mc{% z0(K@xX=1x2g(i}20u=b5-da{AGnJtV`Yo z^`aFd)qlg@6j)5@Xm0i@pK|;;I_Ir?g7{8Y?Qs%n54l zHdJQ@R04gQ181BlOX;FQ_XokX4wfICoVpWH@OR4xn?>~GHt@RRosPGo*9I>pt z0Q%B4PELecd><^9WZa@?=Xs#7(rLw00;{XN?bdpO@K%k&LV?M@wZtRZQlD} z?}`u)MYf`nwTc}152_C78~C*SO;%&XlX|*p2=2Si+gs_I>*;UwIAdk|Gh-{|U_KMc zU#>n%O21rPU|u{E(b$5;x}5_ylT7_|lSLE{Z#-SY=Hv>H_0)&+aig0@z4zPOL0j~S zqUfN}BJHA^f=J2BVDmnA_`%|3Q`YtQ+P!HceUXGz1^j8G$9z#)O~Zp7hiEM(tjJzwIzKn}wt@mQlM)`8@yxQWL5RjN*SL>--*436cZ(alK(1PT@&LcNF3wC<7 z@ss>jO7(crN0-#=DkeV0z~Csc_<9c4k=F(0X^jXIz%0>?Jpq>TSQ3C|e){Kyg-+|L zO0@PXSP)XSK}U|Lg)=QXt})MddyFDQA~#?M>+N=P?k-E>ZWPWrD~#{11EJ)z;6SQrDSmjb5Izd!2JbM>4NXPhIg6xD0xg1??4}l%pRl zN9oy^2`0x6`xKKe%NV1=hi=Z3_-|de$L$4C)_iF_n7~{wGm2{jCVt2UYwEIq7D3hh zjVw8VtZw^%|DvLQA8v$sFCL>~y{!%Wl4t~o`FDb0W>X^FF??d8-)E2iTuT%6XI2HG ztVFjnT-)#?(q)KeyOW~+Zz{~)VyaYnZn55p(PK!gJJs&(&^4#dGhhBU1uzGTjkTlk z!BohBvYd=nf~z}hT=*a^WG^-+@;SZ$6n*yZu~)6_ z^6w^5%+uYT$xz%u&lANj=i8l2k>Ipc%k+T?b){wbESVE(PS%hRAtdHDB)Eu{U zrsDIgEK`9yL4WGp3jek`qx7MMs_LM0F813q9LKg;3^?v$A<54&t z@06V^RNKsGen|YO2!Zl1={F})l{NkH+f1DxlTSD{adJ{S@|enYJ-3J8RAS+ORqk%- zJ8PXHF?5F!D;sQ*Jp3@&d?y2p8!6%&CKv)3nL#X3vpSdUhQ(ymXOH$ol#K} z?1q~BTY=mcT zyKy$f_L4o!;e@a+UX?85aQvvd@@BX4gXeLNx5ZyspDG`C(UCi_pxUg=faPY-&OFnl zjq9a7x?;WVno!($po{nb_)qTZtA{Q$1l_%6WBYitJG1Ms;{CS9*SBk9{n$Ma<92y( z4v0#=EK&}JX$#e1Lm($fHKzy96N^V|fl>+*O9B?GbPTGs)leb;V!W`Kk(yHTmI=x!RK$Hg(F`A_^cttwa{6d$`}62SI&Y|3>2%Bs9G_jW}AR zo@lNs%J3_j5;1~lYVj7gizmHOoLAB6nqy}6_WX^F!b!~&P%AGtt#e@UPVEFhOW3RX z`9eL5{QMgLNw15wNxeq>P#@*&usb)ke8$yeqh=|{Ad>v+_?UcZ=EH}cy7~Fb)4F+m zHvxAAAzqQjPI!ud;|qZHCK|Zy)%^a#qYO!ok5_i^_h%|ce3D-&Ho;CT0KCKd_iAIS z(HQl8ns1sxT-B zL<%v72d`D>7I7c%U&k(A@Ry?oM1>>R%1YLv=4#4s$JJMHdi?8Mu8`yE^}aik?pD@! z=62;~OX87b3D^(=)mdiH#|02SYhq*Wx@}p$v12oyg`pOCtwLHqLdfN%@Qp1kc>k@O zjrHYih4~Q_;FC7AefY<%d3PlF)YbS`B5#*7>(E#1Y=y;tm$XY(DmJqsYG{joW(Hu@ z*qLf;lDS|JD(T$sg^<)+0y<=K=P3{$rwV}JpSfQ)w&N7U0sP0hzQ$9tSp6hmr_br` zhOf0fLQzkw!4wOCpq{cf--H_h!}!%CVy$EYQyjQz{q=5;=1o4(q23k^zdi(%MW+eo z;N1dK+%6OsvZXBBTj;wU9krOcTFi#v_YJOZc7_Rg9+^n5|26({O{qEPVq!Y*=&rE!dixR^&2S#&W)(*6bg@1lN@UmUJhVQbFJi3`bMpX%Zru_U z-G}ub%MrhWb8?zmU|baZ`Al3;exQFiqr?OzlWxCk&*}y{1@l>;RNd_7=TO>zs4bmo z%)JI!sH3Ob`-sIyKqBt@ze!g62EBGTTHbiR$s?fogG>{f(Nb+sr51EH!D7OT0-g|J z?g*wk_Q~$ZfmyIjMNxGC$mkGR-i`J~uGGcr6sbLycT%)9mOL+`hd5%Fj(kLc$p+%W zta&r5C{3bXZk2tUl)_aLYN7EacRr{tkQE*`*N0&S1ps?|A#t3wmjjn^FQoWXx#;g!U^*M`Y`Z}zq`vXFOSSQ;M zR_55cM=1G-&|6FOd+xWbrOhl|&XMi6%im3BN%eM@%ENSbOSfue=oY@f*gqdoL*j|u zpyk=}+uK07#3EPIAq{3b-KKYQ?vW|58k!?7wpBj0mweJv=WkH-Izhh1yvh`E?~zin(!IWko&(hv;W^UtpBg$ zqZQPy5a|9~#b?#X=FE=Q!uz4FvMcB<5PFxk+V|Qh;SlJvZ8w@j#XSp1aBRyx2EN?m h0KsEtZP^3i&yhO~?`4Nw;Hf?oh?J6K={w_q{|!ju)j Date: Sat, 29 Jan 2011 15:10:51 +0100 Subject: [PATCH 428/867] add docs/tree-migrating --- docs/Makefile | 6 +- docs/tree-migrating | 192 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 docs/tree-migrating diff --git a/docs/Makefile b/docs/Makefile index 79e5019a..50808a8c 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,5 +1,5 @@ -all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html +all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html tree-migrating.html hacking-howto.html: hacking-howto asciidoc -a toc -n $< @@ -10,6 +10,10 @@ debugging.html: debugging userguide.html: userguide asciidoc -a toc -n $< +tree-migrating.html: tree-migrating + asciidoc -a toc -n $< + + ipc.html: ipc asciidoc -a toc -n $< diff --git a/docs/tree-migrating b/docs/tree-migrating new file mode 100644 index 00000000..15ea54e4 --- /dev/null +++ b/docs/tree-migrating @@ -0,0 +1,192 @@ +Tree branch: Migrating +====================== +Michael Stapelberg +November 2010 + +== Introduction + +The tree branch (referring to a branch of i3 in the git repository) is the new +version of i3. Due to the very deep changes and heavy refactoring of the source +source, we decided to develop it in a seperate branch (instead of using the +next/master-branch system like before). + +== Current status + +Currently, the code is mostly working. Some of the i3 core developers have been +using the tree branch version for a few weeks now. So, if you are eager to try +out the new features and help us find bugs, give it a try! + +At the same time, a word of warning is appropriate: This version of i3 might +crash unexpectedly, so please be careful with important data (do not work for +two days without saving…). + +== Getting the latest tree branch version + +Check out the latest version: +--------------------------------------------- +$ git clone -b tree git://code.stapelberg.de/i3 +--------------------------------------------- + +Then build and install it (has the same dependencies as the latest stable i3 +version): +----------------------------- +$ cd i3 +$ make +$ sudo cp i3 /usr/bin/i3-tree +----------------------------- + +…and execute +i3-tree+ instead of +i3+ in your Xsession. + +*IMPORTANT:* Please note that configuration file compatibility is not yet done. +So, make sure you use/customize the provided +i3.config+ file. + +== Tree + +The most important change and reason for the name is that i3 stores all +information about the X11 outputs, workspaces and layout of the windows on them +in a tree. The root node is the X11 root window, followed by the X11 outputs, +then workspaces and finally the windows themselve. In previous versions of i3 +we had multiple lists (of outputs, workspaces) and a table for each workspace. +That approach turned out to be complicated to use (snapping), understand and +implement. + +=== The tree consists of Containers + +The building blocks of our tree are so called +Containers+. A +Container+ can +host a window (meaning an X11 window, one that you can actually see and use, +like a browser). Alternatively, it could contain one or more +Containers+. A +simple example is the workspace: When you start i3 with a single monitor, a +single workspace and you open two terminal windows, you will end up with a tree +like this: + +image::tree-layout2.png["layout2",float="right"] +image::tree-shot4.png["shot4",title="Two terminals on standard workspace"] + +=== Orientation and Split Containers + +[[OrientationSplit]] + +It is only natural to use so-called +Split Containers+ in order to build a +layout when using a tree as data structure. In i3, every +Container+ has an +orientation (horizontal, vertical or unspecified). So, in our example with the +workspace, the default orientation of the workspace +Container+ is horizontal +(most monitors are widescreen nowadays). If you change the orientation to +vertical (+Alt+v+ in the default config) and *then* open two terminals, i3 will +configure your windows like this: + +image::tree-shot2.png["shot2",title="Vertical Workspace Orientation"] + +An interesting new feature of the tree branch is the ability to split anything: +Let’s assume you have two terminals on a workspace (with horizontal +orientation), focus is on the right terminal. Now you want to open another +terminal window below the current one. If you would just open a new terminal +window, it would show up to the right due to the horizontal workspace +orientation. Instead, press +Alt+v+ to create a +Vertical Split Container+ (to +open a +Horizontal Split Container+, use +Alt+h+). Now you can open a new +terminal and it will open below the current one: + +image::tree-layout1.png["Layout",float="right"] +image::tree-shot1.png["shot",title="Vertical Split Container"] + +unfloat::[] + +You probably guessed it already: There is no limit on how deep your hierarchy +of splits can be. + +=== Level up + +Let’s stay with our example from above. We have a terminal on the left and two +vertically split terminals on the right, focus is on the bottom right one. When +you open a new terminal, it will open below the current one. + +So, how can you open a new terminal window to the *right* of the current one? +The solution is to use +level up+, which will focus the +Parent Container+ of +the current +Container+. In this case, you would focus the +Vertical Split +Container+ which is *inside* the horizontally oriented workspace. Thus, now new +windows will be opened to the right of the +Vertical Split Container+: + +image::tree-shot3.png["shot3",title="Level Up, then open new terminal"] + +== Commands + +The authoritive reference for commands is +src/cmdparse.y+. You can also find +most commands in +i3.config+. Here comes a short overview over the important +commands: + +=== Manipulating layout + +------------------------------- +layout +------------------------------- + +=== Changing Focus + +-------------------------- +next +prev +-------------------------- + +.Examples: +------------------------- +bindsym Mod1+Left prev h +bindsym Mod1+Right next h +bindsym Mod1+Down next v +bindsym Mod1+Up prev v +------------------------- + +=== Moving + +----------------------------------------- +move +----------------------------------------- + +.Examples: +----------------------------------------- +bindsym Mod1+Shift+Left move before h +bindsym Mod1+Shift+Right move after h +bindsym Mod1+Shift+Down move before v +bindsym Mod1+Shift+Up move after v +----------------------------------------- + +=== Changing workspace + +--------------------------- +workspace +--------------------------- + +.Examples: +--------------------------- +bindsym Mod1+1 workspace 1 +bindsym Mod1+2 workspace 2 +… +--------------------------- + +=== Moving Containers to workspaces + +--------------------- +move workspace +--------------------- + +------------------------------------- +bindsym Mod1+Shift+1 move workspace 1 +bindsym Mod1+Shift+2 move workspace 2 +… +------------------------------------- + +=== Changing border style + +--------------------------- +border +--------------------------- + +=== Changing container mode + +----------------------------- +mode +----------------------------- + +== The rest + +What is not mentioned here explicitly is either unchanged and can be read in +the http://i3.zekjur.net/docs/userguide.html[i3 User’s Guide] or it is not yet +implemented. From ac8fb2399d5673e0f8cde7809ad2583470e45112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Fri, 28 Jan 2011 19:58:41 -0200 Subject: [PATCH 429/867] Don't mess with sizes when moving to other ws. --- src/con.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/con.c b/src/con.c index 50e303ca..a14b95ce 100644 --- a/src/con.c +++ b/src/con.c @@ -507,6 +507,7 @@ void con_move_to_workspace(Con *con, Con *workspace) { /* 6: fix the percentages */ con_fix_percent(parent); + con->percent = 0.0; con_fix_percent(next); /* 7: keep focus on the current workspace */ From 3a634f9ca0a8e201aa73823b62fcf21ff56210a2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 29 Jan 2011 18:06:56 +0100 Subject: [PATCH 430/867] docs/ipc: document that the highest bit is 1 for event replies --- docs/ipc | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/docs/ipc b/docs/ipc index d6f1a253..36a5d2b9 100644 --- a/docs/ipc +++ b/docs/ipc @@ -233,7 +233,8 @@ rect (map):: To get informed when certain things happen in i3, clients can subscribe to events. Events consist of a name (like "workspace") and an event reply type (like I3_IPC_EVENT_WORKSPACE). The events sent by i3 are in the same format -as replies to specific commands. +as replies to specific commands. However, the highest bit of the message type +is set to 1 to indicate that this is an event reply instead of a normal reply. Caveat: As soon as you subscribe to an event, it is not guaranteed any longer that the requests to i3 are processed in order. This means, the following @@ -254,16 +255,38 @@ type: SUBSCRIBE payload: [ "workspace", "focus" ] --------------------------------- + === Available events -workspace:: +The numbers in parenthesis is the event type (keep in mind that you need to +strip the highest bit first). + +workspace (0):: Sent when the user switches to a different workspace, when a new workspace is initialized or when a workspace is removed (because the last client vanished). -output:: +output (1):: Sent when RandR issues a change notification (of either screens, outputs, CRTCs or output properties). +*Example:* +-------------------------------------------------------------------- +# the appropriate 4 bytes read from the socket are stored in $input + +# unpack a 32-bit unsigned integer +my $message_type = unpack("L", $input); + +# check if the highest bit is 1 +my $is_event = (($message_type >> 31) == 1); + +# use the other bits +my $event_type = ($message_type & 0x7F); + +if ($is_event) { + say "Received event of type $event_type"; +} +-------------------------------------------------------------------- + === workspace event This event consists of a single serialized map containing a property From f1aa9d742da2dc19c90506099a46b7c2c50f5398 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 29 Jan 2011 18:06:56 +0100 Subject: [PATCH 431/867] docs/ipc: document that the highest bit is 1 for event replies --- docs/ipc | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/docs/ipc b/docs/ipc index 59bb4715..64d35035 100644 --- a/docs/ipc +++ b/docs/ipc @@ -237,7 +237,8 @@ rect (map):: To get informed when certain things happen in i3, clients can subscribe to events. Events consist of a name (like "workspace") and an event reply type (like I3_IPC_EVENT_WORKSPACE). The events sent by i3 are in the same format -as replies to specific commands. +as replies to specific commands. However, the highest bit of the message type +is set to 1 to indicate that this is an event reply instead of a normal reply. Caveat: As soon as you subscribe to an event, it is not guaranteed any longer that the requests to i3 are processed in order. This means, the following @@ -258,16 +259,38 @@ type: SUBSCRIBE payload: [ "workspace", "focus" ] --------------------------------- + === Available events -workspace:: +The numbers in parenthesis is the event type (keep in mind that you need to +strip the highest bit first). + +workspace (0):: Sent when the user switches to a different workspace, when a new workspace is initialized or when a workspace is removed (because the last client vanished). -output:: +output (1):: Sent when RandR issues a change notification (of either screens, outputs, CRTCs or output properties). +*Example:* +-------------------------------------------------------------------- +# the appropriate 4 bytes read from the socket are stored in $input + +# unpack a 32-bit unsigned integer +my $message_type = unpack("L", $input); + +# check if the highest bit is 1 +my $is_event = (($message_type >> 31) == 1); + +# use the other bits +my $event_type = ($message_type & 0x7F); + +if ($is_event) { + say "Received event of type $event_type"; +} +-------------------------------------------------------------------- + === workspace event This event consists of a single serialized map containing a property From b078957cff0df85c74d2ebd9970bd3d0fd2f8777 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 29 Jan 2011 18:30:43 +0100 Subject: [PATCH 432/867] docs: add tree-*.png (for tree-migrating) --- docs/tree-layout1.png | Bin 0 -> 27856 bytes docs/tree-layout2.png | Bin 0 -> 20101 bytes docs/tree-shot1.png | Bin 0 -> 3665 bytes docs/tree-shot2.png | Bin 0 -> 3383 bytes docs/tree-shot3.png | Bin 0 -> 4001 bytes docs/tree-shot4.png | Bin 0 -> 3050 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/tree-layout1.png create mode 100644 docs/tree-layout2.png create mode 100644 docs/tree-shot1.png create mode 100644 docs/tree-shot2.png create mode 100644 docs/tree-shot3.png create mode 100644 docs/tree-shot4.png diff --git a/docs/tree-layout1.png b/docs/tree-layout1.png new file mode 100644 index 0000000000000000000000000000000000000000..ee69f1ab33f84ba594df749e45fabcaa97a4ce77 GIT binary patch literal 27856 zcmagFbyStl7cWeggi_L7($XL;NH>S>PNloMyHi>kNof@6Zs`VT>4rOefA_xszRy~C zmM5N=*|Ycl#Dpu#OQ4|;pg=)Ep-D-KDMLX)GlD;KBn0pdJ3hT6c!73QmJo%i8YS8T z|3NU4kq`r~;JE$R>I+_w?IblFp`b7fU;d!K36)raHxZqry<>S{Qp6AFq9N=i&b)otN0+to`Ia@X70BsVr(h)I@*B+0_{`c(wH zn7BBux)V-(!*%2F;C^jOL?=WQTioyn{#8rF2J_bdIwM$YLNdvC0!c}>_9ahWU)f_* z%z%6bR`T6jFS3Co?NNbU-ZAY_PpT;nIXIiJoqk)%1S*0&{Es+tin)cT-NJLusNG$x zs1rQ*uWK18hu9+W$)qrA86!Wv38FR_S*SA`*?98oEif=jqbVkzo)ydx?OI=3o(yeX&&vilZw50snJK4)y4x0F(t8O7^%JYtI+uH60>0Z|x393!ZP zvI`xb-r#||lN{m*P88@Uqqe72NObi9y5cyY` ziY82RpcuahMSRTwikMa)86z~I5$o0;ssKv{{ZkuoAIM?hV#2Re{J;S|goYN;fB{E9 z4cyCNh>V1U^b7MfID)8atiwzo(EiK88V_w_Y?J8!0jAJ}CTw^6@D zdNDxf)ns&a$@Tm=7)MH0=Mz@o#^6kscTla#C~xT_FM3sr`8p{115M8D6sa67>!#r( zfH^nEQJ0 zHPd-ck^9dkvSg8n8e89N(y%igLSgjqE-LKC!0>^*`Hs#Ne3}8lYZ^gyWbxyu z+}jBsX)vZj3oT33KGGwH_@|VaUhN^6_Tp9@W121E5d5~-KP^%hByt@u%Y_YK7sO+x%Gs8U@*bHp*J3SL*5o%iUCp6qQrl)PnkIx{Cy zHmSYa@bd5|`zc%SsnOoZlNN)7>Ak(8ppaS!dG9*m8s7o4(}p^xTLMI9da=8r4ra+EDD$AicO=Pu_58ZPtAb}0 zdVuGzlol1&zim?O*KwGr-cD~)g*wwz7JdyXeU2a+h>D1U)VURA%3wG+-bU)%A7e%0 zjcXasq(MXayQEGeZ zKI%|S@%`*%-s%})a&K)UK6-A>f@o9n`zY&$Oz(TC2DadCb{?^#-_5z$A&MlhX;G>U z{P?KCZ)Zg~>h4a=Qh5r6d9*D(7$_gosr)m`;=_}!(yw@|w{=$5N%HP3M-|+%i7wYq zUG+?rKej2;*-7rRT%ePoi=@K(Zd8O=*2N+UNLG72Ptn_IzsHFr3W}&uEx1p`$vb=; z;~;)TOvHD}@YGNoW~r?;;X27}5%K#wlkyLhMNK;q89j~q^gt55u}Ez#$Gt47UBOFg zi3a-#2BDQ7>9pl(9Y#?BE^A-jO(*-2RjkI;cn#kPiltJ}{_d`48n?Hkr!4UiO~u4W z!1s4!643tPW@yP|Fj-wRC}C|Cj)>uBAcK@5Y)QLQ72{x#hwwelJA`oc!9T7OE2$oS z1VUToRCN@;_NA|BE5?=k{ZSv38w`|cFgyL>0Q=BiwF+eo<=?R}S5CQY(c(BC5T3kW z(dYdTxRLoP{$a;<7K*312s&aA?L=&V@EW#i5*K^Bw4TXjs`I1$PL&)i=9|yNid~bJ zoL+>3p%1gK8BH6sMkiF%YVAG-@=<4fA}E~LnYOA zXN&qk62XsB$B?*oanxU=Ok7bOz2Q0-7ZaFtdQuTvw)W2i!9E+dm1^wgV>i0k9Gd)z zX1}fe+^ri2EoT*Ol!w)O1${f#)hHKCg-q4nHXR|B!J%^OOsJHZ{xv(Zn0MCGY4T87 z9)(f4!`nLzhCU%gX`#iEbjp5;72igQ;34vF?8?2GdC^ydi3cRt-feaNAdTV>AjUVl z8%twOy9}d65_Ts^_Y0_vY{UUI;ZoaLA-Q1&y)k%0XL7OUr>T63O+;WPwf>iIM=L}` z^!cByL*-pKaI&b~zmF8zO_DiTweN_752-7Py6>ygoobosd1G^5qC1XF zI?+g{%|)22*>0x``AV-UCx4WIJlRRUV?kpg7ohwVI>atUCo_qT*a0WcbRJf2Y#A1$ zan>j5QE$h#-(%{fjeKC8h+Wipw!Es)zZ95rTU$)8*{p;g+WrXz3#(wXg7|hT)r)lD zly{0XtVp(uNIcy{5gOVM^B8S9_~=pO>2go4_rg}B$q2EUsSQ$9x! zR#N@kqR#oK-LDR375_vljL7rX()Yi?5`j!mN-t<>{vv>-jC({j+Sr} zn=k*ua(g{RlhEDJ`@O#8(R^`3ew+9c`Xq;H$1;(jVhMkk#Gz9RW@BO-U#yUmWUXKl zc|+2NT$U&oFLLL#S9QeNTNKB9mrz+m9iD{R3D+OsaV`RY2 zdQY|Wo+hqX3N@~6qKLEq&dwf-6;J(e-z}n;;l0|o(ygS)*P)G9|NfR}|4hH0GEZh3 z_0e+O$l#uZ`FBW$Bii8AY(wq)_t-=uRJC_0A-kSs=q5*SB~DC*E%xk=5`{Ov2G^?G z;DAq$w;TtG)FuImmaSQ@+XL6+tdoMA&nmg?v~L9;Mw;g5w$UF)A;mRkhZAJxZ>4^f zgw4<2qP(wH_UZJ$%>B{HT)SOOUYAlg&kvYcbi3ss?QY%vUd+ba66eQ?d>yQLV=HHN zK6<%WK;=yO4IQ^)eqB)ySDBnF@NlhT3~k|#>+B+|k^i-fafagByN!-!Yxk{Jun{`1 z$2gkBS#d>LhzUfRvfk6`x>r;)Z4X+?q;u51Z6ZF|yh=yUd2}PL&YllJ%flW>ioAk< zycvto($j;7cP8>&tvJzOEn{CaMz}o|#eA1t@O%jisfUu=aE)a#yh{s|jJbR*{r5+b z7qi>WE4{l-a>nMGw2wKlH8*`mpWPfeu|gVm%Q_IyA-x<@0+*fOI~`5lSRjbYVu zBQX*Ga3iWzkU=;7c-D9=vBl>?F+9g^`Z$P%vvBWppzNUNh}zkdHqWClOKJV+!H`uL zkG5zHHa1TFOpUt$cB7xLl9@rtM$M&!u>T}i`eref;w5F!FGbay0k+kal-VjIT9;7? zi0V$I!gn?$q!p;5Q)io&Qp!$GGyG~F$jCnuXd}M1_Cde=#h<P>{{WA#plHy!yRKGvch$Wx6)>~q>>rpJ`kK`fIdDRYvTH!`g4w-4OPO@uDZS`zfi zG?RP(gb!eEwG>V-X#_Uw=v__Vv4(Z&N?PW!-NzfPcU(1BF8km?*Zn9uTWjXae!y9@ zZarPZIQ=Gk-@@dpRuGl;WEFE|9`xz_F63J(;n!~;Mzv4ITLk`4OCj`pj~q)+Hy(`M zBD5GBHL2u-lGwDRc>H~N7~@-G*osn%`G=S#+9F_?NFS|NtKj5$>znmfdoG);&pK3$ z)Mq`7+GyuDd$g4CyvtQEFrtQ>h|0;O_Sg=`trJl~a$Ca400;Ez{z?$G59mwxl$I6- zpg~wZ(-Q{96X}=B{}2KhIFMw7Uq1T(@WczCXbrk$UyR@!(8l~=@+z!8BpWd}{|K>? zf>T~zj`~~#mI_%jCEiq%(FON)P{Wi)3Y97(cu&1xlpxd+PtHnhj1ep8JsK=!_9rzQ z6U|;$Sy}e-IC~YmhXgq7l=Z8N-ff3GO9BU75#cEgcz@1ad-}m4lQ>x#F6R7 z&dbCTsQ5`Rej3D`ojLG1{S`i#twcr$96h#68Q(u* zJI#d2DVHco^=sL6RnF9udNlQgv3u$)mu4GjJZIAlww!VFi`)|LTa4>vOJz+?0s6 zlDQ=;R`4#2q3TD#8_G`rVg?%G(M1H5m04tjR2@hr{61=09eubxzkNLM9otUTk6AtG z16Q!)QH0F6*zFQn@)DtQ$kpMbFmg4$4w_ja{CO6R=N#igIkf(nqmlftAQgk$l-uQu zI=+Ah2RuAH|M@VxTl@9wKF+X><5e*~L!xD4Up43DrTt%enn7)&7A+C$0edIPUm+PbJyb$)Tq>AWE1 zmMSnT%&D)c=OX70dO+~dp|n(ic?01gM)5{8@AYPHwDD*bua5h6vX19b1Gvu8=O$a4 zod}hV@4MtkGOts}Du}2RhSZA=_=Zwhc5AxPyLZn|HzHY-RaM0}2pS;C+b7@q2PsIV zIKXgnSq66-Dwkr{nU9$r{`p>SF%gie=Mg?zslDmaYRmq9v57iNluVtB2&S1Qayq>v z#E@)=?X9lU8v)O&ttH2??at>%7N5>sNvZ(GA#gJfK0>#*w{S4fZF($Vb|Da3{gk3M z@a|88*VS`FUqu4l4l0|F5CShwR&(!9I&(D`KYn9Le%RJXRg+##Pb!^`eQeNNY&&|`d^V|K~ zOoFnq-hS45op~z;%GKIKENWnA1kYvUo*$j^G z{Co-&6co4T$Lqa?1_nT*ML_oV#aQn^P(?N#nf32&JF&~b9 zbjx!!gei+*f(YlNgcQ-COcl#*=0`H;x3y)xQz-_|9oDxgnGn>y-nDu9rx_w&;#iUt z_`FR}OMIr5(zzWMD@Gw!pw#jFyCG46mo#T@an5ovULP zdsFWi8L84`jWUYm2vSs*H&3T!m~P$)&3rK}CI~U4&HP01=GQvwfJwrb&~p~ZL@=>^ z-BI}LbSx~@uF>;2WI^E|^whvQn5S9e-Oc*={PfuTa5j|w!Ic@LZ!rqPIlo$>HIQF( z$INN{kE0u$a0fK@Fy^I71#@FNyzkFepPy1cqVon7#PCI&ZuS)%v}8Yxv(Bpu3)5B5 zATYq(Oh}NvH~Ni)84mY+?fWdz>GOE_dt_M(X8Em&HFajO@;jsNX-A9FB~!AQT+yw^ zEs>v>TkJs~O@Rcx*mS9VI}Zm39z7Cvr7X#Ah^fhLD_&xnCv>0LqcTtn=2MA1I!%NYbn` zd+&0n;cUdjgo&ZpHa7w239S!LJ@M|xgQ^$D*qT@1eI-K?%wrNq0gFsv<$wTTZ2%_q_)~T}$nmPOZ z1i4E1d0hPaM;|`dIW9(`84&PO~hXR_tZ1^QYRkE+y9zm}Fqg}9r|%Xl)1H#qGn_eS9dT4oGq z@g|ODe^}JKH?oWc>BKXwf)Q(+6*UBnuoo12Yg+%7<l5o%{S?ju{@LJ3sBvzdLD03MS@=+w3djkk`MTh%ZkK z>Uj_{*5Bn;Zjh{7WnpYl2vd)d8%(J)wa{LOPEv7O^GEzvR+it`nBv9{I-mOU_LmiNhf_n;z3Ow%RFdOC4;rw|(vqAF z(D=|`hEJEnSta|wiS&1EucswK2jx3Wz>|x6)r&ow&v_%=JSVTBVz{b-I^_x^`AY^_ zLfyFu=WMA&nu&6C7I14_lSMLn69tItc6eWvAg0v*W*9$qc>YyfAcl4;u{{3VJ-0s_ zpk=q7WnB8q{dj-9d%K(4#E{7)>)Y?c3dPz1 zVZ8-PGNkhPbia9}u2`X`G=Eq`(DM8Sv2QDZ0KND3i3m(OVjYN6Q3^2 zJO@E^Es#V;1@CP@CsEyTNAG=i@!dO6&N~>04i(Rq|NgcB`s<})fcZ-#{L&WhILg~6 zZ&6Q=ZL$l|w1#3*k@!~jtZPe+8SWttV{K)X#$kyG%Aaw696A5R_{Xb8ljzEy*hef# z)}17DYM6hQnjLu`FQ!gC+WBwR0-cT)8uX|Yhc1S4)_bnhI+++7X6!6u4e^GvKcs4Z zX~!v<8UP_JyV~Jx&qLH{fu-sHi0I%*ML*;Z+P^u6CHubjZvPB+#&Q)F8*Pz;ML*1_ zV;}@xrp$(=KXM-jITw8;WInRz%O(QrdpLtLlFN3%ZYSN`VNTaoNkzq&hZ%O&e;G{>-Z`~i05aDfh$_Af#UgO zxx@jg7oY-8MGU}%>0r`3@HjY4i`iJ5|3DtRlS2%uIw+j7d^&l4ip>%5oUGFQfXk#M z))zw@{;2@;14G*jr5!CGcQ^H~H8{VAp;w)XCC}pz{|Z+TEb6tNf$n#E zC7>7;H8wV`h+4%zfwG#&1zkxs9*E8t=6o=F`Ws8ZZd?TMP4p+i%IciTvfokdC)n!7 z$#RtONa11u>FnlbbDdAg^4WZ+`{mV`s0c?UDl)d36WV!U!N069vsc)lN!`|y zSMls*j!gsg5F2P(NmWYZO~=2AEcx6YfdaSb(0pj3@;%N&h^pez(9Y}&qLBG0k-Xro ziB_Y{0BAJgsKQOy!l|TOI0gqb#*Y6`RMWf0XWL{*QXO7|0=Rc|w21%S2o9`1{_7>j zi=)Lkt~X3Vg$*+rDqfBG3bSug4DpI*UZXXYTa~e&;+tm~-t&y|pUZx@uq~ypp0m#M zX|A!fG~Q9XI`J&8Gs~q-y%)WDxm_HTV7L9!k~B6`6xtj&3Jjr#8e;MK2so|y-^Jsd z*QWaYmcMEVs6JFIVTbPn&}sUo#t{4MTXw@=aC|O@l8m^p-_+~qTQP;1)tc)OR9w=0 z^k1hGsX5x)nbT%!y>!NL@+G&2bz^uRTo^%ek|1#pdV0JEsKk}+J~N`MKE;>w+CjWg zN&^Dv`G_H)`sK<>+6<-(RPo;cSHzC()}_vx2-(O@9gb$SSJ1pAeW)?)wpi_GZ-+De zg2p<Ulnw_&U9#K*gz-4=;wkT;1=Iz@}?cm_V?dhEX=@xD-60PPLcG7Oq)c?#9Q=!li{H9&qRozH^o z=A-r8?B5?MM|eFdwI<$rE!*Ls&I^4mL)Q1C7tD>$ z{k-QxA<0EDskK%!^jh^6hMi{bew!^a7h5h+tHL?JW^&t_0r2Qj+%$KvJkVEPA{$q{ z)48Mkvm)aIoZ&W^YzF51Y^P!NR@$*GwEuhtnF3C-ebxD57myifo>#-EBc(sVN zqiNh{d82i;dg_){TgwK(`hP6LAtrOmWXnm%y~SKrw#LvLhpcEVLh5|i@OxG#*tj?x zeB|bsk4_OVN>DWXdOanp$p!sVM&Gr*!c9pUY<#NKC?5^t7{M+T!x;~8;)5>6qAHcI zO;0P1$;*_Ch>AijHhHhBOPdr&h4pbxBq5%f_u%r-y5Xw4T)Tu?imo<+H@5KXD8tJG zV>yBx$3N>ET!p9K&3?iu3Q@EB39%GahAxT3dJ}bL&>>Px12v;VtyPzpI=i=_v;5%I3sUk_I$* zUM5jPW1rEwm{KjgYUoB_$G%Y|BBSqPuJ1&)Cp9R3`zC#Ub3yf`{p9!FH0vc2w_e(t zsIhjvoyn$v&A;^ut%e9r>seL+MWJGZp4?_DG`ri}&+T}KHrBT*r2d2zNaaPG ztX$booDkcTexp#;9%*=MVRJVg{1F*zIMUwWUe??~zj(ZgL*7y(>Opv?!KD(d?N|)< z1DjkIRvyGMeiONVxHSR=_cg8kD(|8%(uY=ev`-u zRstA^3ffio=!=);Bk&y?-=DP{;r)>8QFmBnf&2ZyDD;7zO5&Wd{#PnrsPM8UXU4+keZEVWI_wc&V8KcLYLF4-Ly%TSj+}E)= z3oSpl%eT{vLMe+?<20ym3Uy=x@I)BjQ0+L+`k_<3S z+I!<~RaKzacjVRAJC&UD7`=b#aPlYUTvyy_L1-(|{mvJU{Bc-2bTj7rWPG0`c)NwA z=XEL$at9|Tr;g9wZhOoU4K{u-uK6Fd&1?;&H#%qjLB~{w?Rvg&1ai`zEmwxH>TtGguq5`sE#2=BQABk!^SsP?s-9Edrg0{99D^y%l6W7+p9}yV|dU5mws`>rBlByB5=ZaPF@Kl=O znus&@n9WW-Zz$h(zWerWHlJW8qRH?(`R#0yTGiEUo1=!=HLQ>vBjMHIsH^UKf!dWz zSW{=3-Iy=I6kqwygq4q$eX-%W)Z)lxR7Tqnr$cb4dXSZOI9n-?12nnR3%k-W5&}%5 z(-P`Nl`@g>fF8TeWLmtE-1i2t6*^p!Fh3usr7y3V$T zl(2(ASh0FsSloZm_qnB3DV8<7yV!F&UP>1UM0i2uSm>d^7$681bAEBLS)WOw?7|a0 zXh3bdR3Xbp!hU3B#gZBcmb((mPJM6r?>*2WlwFb7>yi`llGe^r9@Rc;PI`Pgtv?V7 zRb9~C{mKnZtm1&ly=aOjQcL&6+<`;Zy=0DB>Mq!y&oY!Ox%Tf&kQ4oA7*<*Om4Wt( zhwCc=MDL#BO14@}xt_PQ5qekbg^G%bT?f^jqhW6-$DOl5|E!{^DFwJLz}WXhzC8zG z#n%JA1ge=u>`O4SWZCi{k_izoNQS{D!+@;+0jB>z9Jvm;e<8S^?HTS$J5ez|pZ@$n zq+P47sF-~btNl;TUvcptu&xiK+~(+(atk-Nwxup5nf(!ip0C(w4`&<7y{nGGkAo5_ z-Ts{{TdMC^+YwGGydKT*q7;taM~!ZiQuzNp#UO!&U;RYcw2w=4GN?8wl7+F12E zM@yhm($N_<3?=Rb5fn3bAg+WBBqczO3X>URhJlg=;CLu_Fc5L2Wn~+H4c!K1N(=%4 zA$z!~-HNVB1yNRp5#}L!>KT-Jt)oK0NcGE}6f0L_h_~de?Sl#W|UgC6|+sbp@J-s}i2 z2B^e1*KK9qR1K`Xb!9$RA1_D7vndNxQr`Mn!x#p>wM_oGxxQvJ)Raud8Qsf2`$Hb1 z3h+rX;%=6eS5Xk5*MWweLvDzQoFU@qW37u&OyA~@< zR@c;^4oQA*+*@P2$kk}Oc$FArvh5~9BYmSnQz%@O7DUru+fdmQ)s4TFf(*IXQyciD zE?tU7*pGSBb}Q>;1bf2hly8ADy*(mXi<0-I(jxyeC5@(c z=hqjZ5pKH$9OgO`86vyhJxwPU#xoxX!VANP_I~cg@!r&P{t#dy#}S2?sEU^a-Ku2O?pp_BmN0el-*{7k3(0$dyeOR*<^FGg~a9c zCqQhHYV2V1c24cQyG85CK@<9^4T*ato>#s%o5F10=7$b*v0P?v@LRq9xB6cGeVg+| zC%_pC=Kp%dlOY^=0Hguw26~U*M(=y02}Aof zzpD^dEGJcNsT`afEjD3Ae|pjMhEkY}0cQaQ`?Sj;xyD4?^v)(cSVj#i!kogpcs9lO zxh4kd{!88vG(lin0T&7nW4v6pL=e4l5`rioESb3AAGupo;WkhFPtN8Mc-tKI^ z3Er<&5LuF7QMAa3|4PQg`KqQWA4(JXU-u3y-QT((6~vZzsPw8fq4T8DL6}TRs%R?6 z4ioweO4A&Q7FZFbxQ@TW8f+>@`lJ=*2u1GD?)@t{1vxlB>Y?kCcUS*Gbs&*9_d8L$ z-{ZF7Ea$>0;1$sEmVlQxM=fdU7j$u^iuLLCG3e$ZE!!#JTM36n^cgsnaErqUre6I; zt1s;Di$hC{wU$YGCTu;><;YzlpbQuG&yDM)1$oP2_IP&*B%0jlexQuP_(RR<`wA?# zI8(kx0$Bo(bW86Z4FIKGZ#5G!IjIbE42d!bRT9ua%u_nV%W#^_?y4 zHYQ1lF`|GvQVn}yE6H7;RWvYapUzCh*-tJP&pCHOktmpI7;0*__vdsx6{4iPi5#?w zs4{HD{6;w?H9YHkYJ5$moy7)td`+(A-OP#OHKk1_FgdN$+&9R2En zrE13Rg^1dgVntj19Fe%{xf}LthM|6k_nB4?{P!-kqj@L4uvEhcE%ki$5v4xPp51=V zZr=28_{t~FoE{AFk0h0my7WzHc9p3nD0*Ok%GD}s9ZOUpJ-g>KGAK-pj3$7PQ&m;{ zN{6CXJvW-=%9nk`iz5!$TR>6S25&p@O>jiZWpW+WUr3N4BFnH*f50&a9s2|$)tkhR zX8grZ^P0Gat8*V+m0MA_)g?s6ECgy_8Nnq>xi^V}N@}PDrt~v%?q&8}%=NHm>kova z><8b%}VOICZ{rin;KI1#fPG0C0SI994mWzgpaMazbLYLv(8%Y+Nh-HB9vpr z2<~GThg?ip@7Y@&A6a#!l(8s%tcXN4BUTMhGl`t4F%Y_|L5lp0y6ObHEaCOtwF}< z?~S1}DAvUx*Gd}pq?kHuU>htRXoLvkiSL|#uQmf^&D*cn4i`U>O{kk!^;mfssg`ST zJZ36EqIS6~_EyNSLG20&^G~^o6`K6qJCIg9KaRD}NL8Lc+^;^H0j)(cVY(1e^6E~= z`h9~Tp4_KWkT^oni6f@=hHa?le&_am`J@3H7;Trc+7bsz)hPkj5c=R!JM z28O1Nk}sfKP(h89Uee z3Lh95fG#!B?&*r!UGr96`_)&+a8xR(U)v6#?#Yt9{xG9MGD3vz3HEgMX3oGcpJOE15{fIJ8>luq~bA zdCUu{Ne}3VC5x0V4(4KASG`k#E|(M$fify^tpm8v2@W&}L^x`Ri*No1N3ycz^nO9Lo`x0JUKF(E=Vlm@hlZiLSr2{Z^Hlx4*;~ydkgtnZ ze|bE~<*{hY@c>db1cOw*>GQH1ART~N18D4?=P1C%v3T?;#dLCy0}w45j3JCeOYXbb zZKu72Kw1t(B=w3@tJIn=+_vMn(yX5qbqQk4{FYH}NrxX|s1=Xxzr=}ionmB(QiBAh z99&=&bkn^rt`?v*NrQb8t98Tgz%{1!#8OP1bel$2EI6e3Nz8%F{o)Pr0xEA55pTka zY-~yJxoJ_2Yd*2>X$8YU&k$MqwFL@QvVdJGi~cB}eSZSEH=W0x0;nJ4z?2ZL22MM+ z+kJ2e)b~TAOyK03vIG+IgvIm|trX@|iNY=jrk^L*=hvOj*+2;;)=aVONw$u0f7zqK zE2)Y*p)qI$L_$841={!Dq|Y}JFX87|LQ*o- ztb(5U-`0{$ofiv_rg?S`aK5-7e(!rx!15H4`fX;CZDGvrvwf^uYiwpxq>EfAn57cj z0K{|t2hI23SraZqXm>MLQ(3tXxK>(;={=_c=9Vd7zhCjlWXvFP=*K0wJ{gfL9ch6Wf@wfB9;|w8cpJ=^Uz@c!ALy%3C(_Mo z(_nhr2vQ~RlS~vyoCDe9z11|`(?yBUyjfwg6*+w<$}dcdfI`#Kw8ig>C~s z3Nc)><8hIOYmn{oZZ2l|;973TF`O{1`^+~u+Up!WFLAazQ->$*&urmlyf!t&ZJK`Rm+leVQ=f&+F3rchFxu><0~d1MVuF z&zU}y*hQmVOpyV_q7vT50*0%{e?jB1DkPb#BA3+r;?0Y^T2xfDutiyeHwYBRk(6+Xaq=9Sq+aSOBoz1NY0fRHiyViX_J{oLK%FQtFdMbxfhHA9$b7vGVFivcWVv(lEXRPgCL zSfahLq+@`2*vFw5PHs5^`}VG0VJXfzgIEPKY69$L{8xXBL9$=1sxhDr;9^9a%EF}c zEyE1j-pl|DQPZ*uA@IO%0A^yXpECCMy4_*@qXw|12TRfY6Z**p{gzSVKkI9-Xd3{8 zK-^5V<3aUXJo-EhSC63YNZ_VMjTG7S_@5=QvD6I+$n3sPUM((1|7ChK?xn7U25VO~ ztjq(NK9E%(tRSYm$+WU*T`y)7dERa8r`s`KO!QD&tQx~$_{eXy-U1`K>H94!7P_~f zZO8%M%{U8=L10VaIjW!9PoJEZzun3I0b=E$47%ii57d0)7~T-Tw6Ftx7$gPrOuvlc zqxu=OMr68$n$rM(aDzj#sG;4!$`jan(vb;^&f_H!{<9`oVssxhp59rJ%?&y9vTz0HpM;{srl<-hw|;B@$BLY{Cw zcSJ02>{_J6M%paW6lOG?9T9k&=&)b3b6_f%k%x0`)@i&=u!Y&#Kp*I=f4tr~A4C_U zi!ho?ZW+$kXS?0{O2n8{^ZR?=Hx6ey>e$kFBk{6LdY^Vm{D zY;sQzyF%mb#VwkrSU~d?2Y%SJ&*S6S(hNd*^F>>&%pZ;@8qoF%-XF48 zwI1`KcCR(>=4`%L6M-2pl7aw{6VS#)WkXPPrpP97{s0z zml-r7k%0n*9H;32u3}E8J?H#%mMTOZKPc(6OxCwINK;X&LP{GS#2$+FbK4_*V)4K>_4|A&)MW;Wo0SP zrzonO_~iJKLBl#*r8{&_>V;yGqb=eCT~?e=M<2mRIe3X6pfXY6>zBnK*M3U z9WkXw7PzpWRaRaOfJvcpsYhg3&-tLh^z`h@US{CAn{yV?@XV+Ylcj8mlce&k-T1lU ztC#QlaB(g@t?faDB=a?0j-r=&Zt>{WlyUYJHGhJtH0g;jF{SzWO@6a+GiI2qtG^kd zC28&=nHD6tS$31B$PnUDFiMuxE*)yvjr;3ty& z0xwI&4)$#bm4pm`@=ay6>(A8W)!rU-R4#PJ8pca~?g%+qQTCgz?(EI>8AB_gnIw4m z;UL?TGj^QRcyu58@BA{9GWvNqLke=k1Zn(2XW}?T2)tY}+exP7Qr8 z*9x!Cw_@GsaHf4Cpu&=qVtZ=AmbgY%(o-JE>T||zH?q6yLc)6!vyZhAXzq+F$BpkH z8|1_{&>{H@_KcI&#L9L%%Ad0zgu~!^wWN%tRBS(Wx}`r?MCHg+#M7+J_g>d|>$v(S z(&}mbd{xb~8DpKr=&RksvDhT@>;HKH@+Cdsvlq;!RYb_-S|_!%uvJyCRfPl4O_k1; z-cOdoP0?nw-T1}E3TY2Ls&uT6AHSo=Mc^$waB&idmS*DC`MY(Zp6+$+;>T${x9K3} zzUR+R(!n|KL1Fu}8-q~tchmV`pu5g}s90~-@(?rI3r}(gi$~*Rv02>qylYc7+&yi5 z`}drHr1`)g=AbqE*~h?c4uViAtvZOV|0`vmf4p1PYr~d{1%EJclXJAa!%yXtuZWi^ z{*+nJ595RwM%pfoA=^24JeOM+q8$F1lT?F#Kd5e&r=&SE28&Us&6E2Ch20B-r&r=P z7*)S2v_qVJlly3qwD>VC!lunINU3M_!*`$au6={ASos^crPYysU~*%Cn3xkAxB>iq z!Un#fj8d}R_Ut%$F+Uy$<$VLYLI^GKG!b|MyOsj6BWm&`9KC7#kS?9M8(aBqnPGS1~|E;(5&Hr}Y69RB!6V|}yL z%wqWtArUE&j8z-!_Lj!;AIeF%G(V>;QEnyln$#pXoP&(%Z`ueTGw)1zA+FX|W@)p_ zF@}x3c-g6Bx}UtByVIYfa$|kBn;_ zK5Ss$QA#Qk3=90hp+cULT)z$)7?UwNq@0xQEl^a^_#L&=@u86)%nTJDh=m( zrVGiK3p$wd!VRM(r;1VsRw{g=LB+3WC*=0|YV<=_#Qx!PF8fi+QFhsMk)*mX+#7f- zRXC;muLy+SI=_BG+G*(cF4hzF>3xa?_A8|DFa~!KG^BuwvV023lSAg#p)1dm;jC3s z$CH{NW@>3oyN>6>mGr5dEcVrN{xSC)c2oBlY)6QYP>OAHp$#Nuyi)-IK9~l+MCnvOzgg=UUpQVkGxpG9a8%Bb8hPPo7s0kmLWfO?;Ko)V<|_~)l739 zvQp(pcFL$!zvuc2nxBoJMh{N*E1eNNbMas?50{*~u)iyLoj0&K9-={jWtf^rXRufl z#y6_o$aL#`0_WjU_Vq#d-8Q^?+qDj??9@(hIL^0sm-x>M2nbh^1u8E1+tk}hq;zXd za~4@s^y!38aglk1Rll)G>%+0dbtwven3YM^R5p-pUJF zAzIG-DZ#7p_zHSo0nc^s%`8h_66JeJa(a51f&__oDYg={L}C;)@4^k)ltqQ{hHA@A zl2z3*G*aZ~h{)eF%X*Q?^4RcY2JkCIMal9bk1BOB`mc2)X#KF?JSACW z`x!}Gc{Ny!L@_*<pbD0#Z3&`tCP~~rtb?4m!_4+D6%%OqDjE-)=t^` zU3i<2c5LW>+pmCx__RRfm7Q!BDc#jSwYp5H9GUm&L~05)@6)b~XRQ;kwCeyPR1QaGj_zTi;7vM-xO=p}4iSa|x-L;zBwnzPyz5oz)&0+8Zp(&O2WECb8_( z!9X>_YPNie0JBlkhn?QXq7nNj(SFjpjf0vjPa>tJRUdn`|qnoKfPnB z`ezg<61iXPYDtp1RD;ozvmMf5m9DGe!iW7t>iKlK!!D+%6krC;I*gvkjji_ZSd!y9Uvgx1gTZy3Y(4>?vl z;!j+TR%dxTw85lVk$G8|W7Dr~Hxzk!cFtru!71bqp>FJ(Dt1o`#3Sr{H!?C4_-}7p zPLcVQvGX1WVcE*I9J++~i#%@?JOi|&jkQ)hl3xPiRXv8=?O74lb9b)qSsv5lLW(2} z15_5+Fq_P)Mw@Q&AYC2z{5$&UvOlo#a5B9A*^nOSp;=GN&_mes96d2FqlLsTV_pjw zP=s>iHAy=y$3Z?_${q0KL1nSSk0^s~KsgKy1U@o_Bo-ji?_f9p&i8+6i0J35*`e_q&hZsOfd1 zXz&E4%!vjrPNL%|t6jW*#W!S#eAL$-bFskjlk0Suj7sN;aVm=ZN!`qU9T({PNqZ=a zvF%lfdX*E+zK%Q}$XY`_8(Es@F|Le7eJd!)3}`GXEYo*VdBnE3r^_%g#T@*CFP(Nf zUPr&|%^mM9wmACuzz|#Xl<(mo6$G-VXgHf10*xid4Sq`G?DXk zC{Ice=)z7u{#oXNx53aOTV7C5mY`Q@-R6CnOMCeTR-cZD|b$OM*4Bt*9O|I(!=Oyj4_*kM&n7FPC3N{sn zM5nWviy76V>ShVoN!de#f2qS`%s;b1va#&3y5|Hf*f{vu^HFU@Nm`VpV`&4wYi#UE zJ_exUXYc-S=fNLe_>6w_=J|xKgw>ulHwF#L*1ND7(PD(WToR1Y4|!_j3hF8ntOq)$ zJ}g+B7y)V%QL@asT?$gX(Um;5{GqrJR5Is5PW&?Q>_ipG%(@VQr!+OQ8ks+)9+{D? z^KIssA3TDOX`Z0-Au3suacH@+>(GYT*Ts;|pBJkVb@A)=ZB`~O2T_`2aqs8FJ}Jc$ z?EHF0m>%01wjnMf{3fP{H#uKYq`zKaUC(fnC^I>t5CS0&el4SGG zQEk=!&I{k1E}lgG5E_qDD^A*0;XnSyWv}o?RNSxw!%F>Y6UxFI!ff*k<<6Bu2FOZU z#H2x;Lwn4p54OnpkcEc5IT`c*OeDhxOIp}Fs#+(iAcw56`DU%jwvTB`Ac;<`EB9!H zo+tBoo@c9|Nt6mxcp`bF{!|R6zSU8t=+7z{Eu|TB=o5el3xHATS3y`KNMmusDxu=+ z?aoGFT{J&6ami2nKF~m}Y>S(+pYBIBm|$3P7h|X!J%`xPLc5oG@UO@2d^RbqqQ=Q+ z|FK5}zY{(`{eZW@m!4&?r@9?0%GMWV8#BIdf|9}Q93W}VEcs}NY24bt4MAt{SHsW8 zSMyH@s~g^R_Vw4a9S-|}s?ANQQIz{+^!>x6@Tg6$v{>(|@(;A^xrlX#P+U8t2p%wa zoO0)uUx}mDk)!9@#|jE~M=r9ypnt`+S+yJL8I8@|c9bZMb zRMh5r+)eLCt4=19tnd6~QcSQj0a4?19)jQYn0cfu)Nd_6zV-e)zf;DcfWi3b;x)eg z6i$FzelQcS8XgNvy}SaFolpxo#m(Ay5Zl)(B?U$KeCv%@wEK9l0>ui+3N#-V%t+Un z4%*<2Tw=_~T|News=TBLSyC>YLhf6g=jP=v3L$FH3ff<3MhWWXHuLBv<3%z^l_K%o zo(B{e?HpUXA*d1WP=FEq$$yJB$3b!|!`w?@24NlJM4lY+&3 ztRpA~^X#_tXm=J$BdSW*nM;69zLZVN(T$t&B4zXA$toX?L?Hezn=i({n9|~Xs!(N0 zT_Rzo)ny==bw!)g3dAzUYg{ zXU~U6ou1M}zce zSmVr_ezLP>0_V;v8VfJ+rt%+NIQHbySKPZkk!TN7Y9Zp;Y2Uu9st$g$Gdv$I%bcW@ z_G@^2cj>1jA0F}TrWfh&sV`S9adn$uOG0wFgsSyVVU_CfwPRTM#>#qU zsbSVf9Rh-orWQX|mhiw=Ml7Zd6P~BB_K3wfAMS(sGkN>V=~Uu^dx-L?`c6a!hE8Hj z%j#4Wf27K3e}lk^cpR+L7Vne2j~kUaMZ~%rd`O~vY^bN8SFBl?6+_QYG+BCf+}17(i|B!YrpVbaR1Qu*ndDMrLJon29= zi-F>KtNuSb?!rDjMKLF=ViE}EAcT-Uks)fnwy0x|Zug{79EQy{2IuBOX~? zJv{EbYBIViUL^i7Nbh$*8%UfpAr*2@<$Am~vlCMG{A~8d5ItPC5mk47){S^UlrMCd zhJKdju(duDP4-=&J*B`|vi;}gDrcHX5mLrgtejxElMX4@bvx+VO8|SB@li>=`~>B| zpO#JkY~>_`gs+tO`7N}%T++oYPdykyR763~PL{iSE^hT>{8^IUO3X@(q1>pS;4Vv0)yP*RQb>)0$vmivov#_p z=v*x?1GA3#zEmMy?Jm^7Q%J)sT7dmkw-u6>mX9bI?d(QUE>fIi8jdPY535J3Lt&2( z;Ef!YL2O6}f1B_Xg+w+@NS%-@@M8+JF^;QX6)ii0xvq5onT9%i=_CEE~~u|Hs?VtL(}7CqfPV?5>mdJwFc7 zc2~}Ay)Ih9a424n!!+5(c70)dPI*Pf@&3JTQ8SVqLjKUE3Y-@MWTXY*HLur;0Y8B{)n3?V)-WoUvX1LdK2QU1VfzL!agZ0a1&v~ z{9+{d=G_;|F&e{1uh{>(Vg41P*z3xOafl7_&M{uztH^ddxsj?0D{JinX@>5!w?#p2 z{3%inG0i_`2sHS+l8DeLXs`)1-w%3eONKYh#D0!CRQ~pjJ{{?XaBocWV!jvXQR;qop9B^J;sd$Oq{t90>j#rhO;Qyn}Njm2UF z0x_-o50$mZY}_~@Tr#l{;nJDJ(wCX;TAa^6d?_;jiXKp;sX%v7ygxjs?kCdydMkwe z$xGN-WE}b)^0Yhp+S{DxlEvtK&!*qz(kYX!VSA2PPc*-swtndt%+IPhDTh@G9hFF> zJ}_?4PL?cvK-FSX5-GUnD%U87#uRVc>4?}KM8&Al+)t?U!@o(HCz>twa7isvrf!8Z+&DCyE_?0>kAKFB&V(MDb<>r#2u{Va zYqFhSQ{9Uw%`j5NB**k>KgxLd%3iOd&&0e-xgt3z)f)#P%oU-g48hw2ge=tL)BAo1 zRcF4_oXj8rB|3PfJDoiWD!buVuH08rC!kXKv&Hl2J^u2|4&{gT3Q+_H-?-uW&KeIV zJD-k+jwBrWb!Ik!@YpGxs+q-`7Rv)gD{E^L5I`i#`%g z7Ftd{RCG-3Vj^E}t)DOvJr#*8c^ov8GfWo!uZ`NhEmV;tj@2P`!yv)D^Igb=RT4L0-cP18Bf@(2KxjFw?X!~Cu6*eT z9s4u6#1EM-$(B;LQChNTj4niYW}Y))Qpf#*XilqZQhnc)4QA^RcC;iHx=7;LGDYUd zBh>ZgH{w*G-kCDEJ2Ftvw)ia6;4a*hWxk)Th9`=p!w_?{HHaH^<%R-XsCFu+J6L>B zhw%T$Xx1*ptRK2u%S5XeP*}lO!U3(V)whV_Jb?KU^wRN7IDbxbt7ie z)y=`IE>@JdRKnC)ewFm<6r!ZZz_7VnAk)sjw zC-mciNzxKsl?lYc#CY{Q0%0%)Y5VBpLlCF_sA`pBPgnRSQ^}&>bp}hf%cuhyfeF=x z=A%Ws^fZ3FqztoYH!i*m!ldlfJ^LMNfJy4Y-8n85h3@t7Ez*@wU;;P(P7k3Wdmh>| z%ke+ZNEE#2vAgY1Nk+e*S(VH4GN_@bk*ehviaCFR)@ita$3%{v8Sa_^)Cr zJ;DKD75ritR3;4{tzKyDRX%A*;23236jS}2O%Trj|H@~yKIC;-8HlBa0c(x$QymH$@dr(Me*_^sBC{=x&1W35p{INsiq5~t}+-+ zmk0$EsO%6~!dTz%y03>Q-C;%bSv6IVZpZGtxKX~nzeJetAM{jrlY3Ex{z^lby?8T~ zT3j(r{MF*!h^>>U&3iHCpc~}sLojh#hz<3hl_%&%|BMGVf?(Cb;A^>WfH2Muc6Cb8 zUJ3`^go;;%7W8ZGI02DaoHRHh(VDON&H9d_QiXk!v1~Bvq1avGe6u)Dys-Z{BVqFgNTbnuwtNJ>r^DJ#0MF@jg01S4R<{9itd#nEnu|)P2 zTJ;H=-GcNy-kV|(nW|F^mqP_=2yh`0$Oy_qt1s0wClqt!>~4sKZbbT~oZ8+~5~k>2 zab+>a8gJu9?MJR0$<4T0+O_F;uVdJrWZf>5XlS$Vlx-xtfZGaEP#|N0}?6ydFdA;7++R9FH#RQ?)lamuOGqbqN%qVa| zQ@p8F!4MG5van!mus0uxFE{d|-jlw-O9ULst&y}g-67gTf492l)#soPAhF@Z@ zZ)^;XjL@^NVC0PL;Qk);QUHmIg^Za#?(wxLtbTAEKf=sY*g}~ zr77&KVOO4_|1WO^QVz2XmOf&dc$F|1aY%p(aZE&>HK%o7KY)`VIHCK89vEx#Z!>;> z$Ny@)?}aXBbIE))#`x0v+kuBQ+&~2g#5WAWIxoWVnSqhYe&Jgs?j?3Y&HIKvsRONQ zO%X=x=|OM?+Q%5eeqv=hJMvIgzQ!@TTp3%dNo>hv%MlMS9q{DYey?dVym`gwp zI=8`meJPvD2an z$kS-fC}iduGD!DdjlBsEA6?nQH|ee&)gwowOh+hR5Gt6ivX1&F(tW!6y@!Pm-(<|q z$B%^DUq?#%K)6E)yp@pFKb98-LRT= zonotmJ&x7T3Q99L1+DZQW+4ytBh8c=j}@)RUjIaRs8$vj{;jU@#+L>A3!!x_eu+8K zWT>(eF#9d$LDj`8A;cq+B8)K{6gu^}%7-5c z&l>&uHgm1Yjan0lsOC6wpb(j|rOChR3zwmqC*t z$5+g=4HMP8Y?$5+h)^EiiKTUtB@vHe+XTX{Px!0RTI7Q;Y>!ip;+dbXz;Bz{KtbuEfuprEdT18K_cO1q+ep87p}_zCE7e5jWp24M zbq)$CUx1*Va19J;EM)7ultFyOhdnGkerZ2ss>35!FQ}Qw8GbI&3BRmEBaj|sC4QFp+{FZvJlOG9 zm)VQI+uyptTs!7jQ3yZ&`=VY7{p!v%?KI@9=yQO8Q?BbbqdN)j0nKih&F|N^j1?eO zd-hA?jc2=`Y+GU=$PhRK-l5iX0H;z4C%KA~1bt=&hp_v{~3XY}A8wG_m z5bE0kvJ+uWHpl7YCZL#rdyLL!j6Tshq8bD8?BCzHM9tE76NO68E2V5Su%3?uE~kFF z|42(;rp64nNP^gm2q)X|;M+Igs-10UUYTwE9K)B=25PoKGm!m}qYp5p2?Iu)mq5s2 zN?VchHKKnKXg$vVu5>=84GHU04Cpb4LPhGAgi_q@$=4rP#@uOd*R)T`EBkh}ce?zh z+z0eSzM*Clch}fw0JGjp_e1?)h}}v@76>KQ0`~wgmZ<_Y6v)ARs$$;{3wjMqG^kHW zdqIL}7-Y*m018)HDCD?`Ah7H~2SjmZAj4Dfhd6Nr_M>v;S-FIUB?&v_*kvo_LAyl{u=yWZV>K)xaP{xA_GH!9Zvh1hRkd9|W*s=@JlZp#U(B4ln#AP_4+u zknf3Vfov?`l5#Cs)P$W75U`2iep`o%SH@c_u&yD_!<~M*|rJ)fBI7+bo<{ zf@>Gy9w5+L56^+QhX-8fy9)h5a8xExQvLg=pt1h_L4cupVd=QwdKBR+SU@EYzz1?pGF}TJ)V)F)mXh=RMH#gYlABGmwGj1%x& z;l+W_UP6)e@84#g3-*l)ohvU8zhq}~YCxfzN-x^!3zM8yy8`gykWsPYz|Bwe|2tn_ zngksJ5T%0n#om;hA#fYw#ev&Yt9J<8MFmCH+-z)04;PJ{$r1TKuC6?E-o0at4dFCs zk(W_7Q&*2)kh9EkO|hG+Da9ev~4KZcQ8lKRk+=x3_on9muREfKP=|f2Q*#`g;Qe!3>}kcC+B=aT937 z%z+}4p8A_=me=f#ew|f33fO}bwQZ-{ZUV0s19b+U?RF8DiiU-mnY{Qih_WBP0$E5M zMV2iP1Er@n@>qKUjs{+8UMTvyRsIyXD~lJ3fTd`NMk+*J9EXg*Ot%HFee#*e;pR3r zFcdRM$-qn@_X?p9K#{xcc!G1u=xA!1<@g40AgDTl`=W3rF?oCy$lQIg2|n*ae%53S zB9|`^b+om$>HPj~v9q!!_>T+@mbg9MosFfIl$6xu=XbXk!C-%9DsygyAm~33|x~@HiutyvKJhaVGha=p+%|UJ zt02|1wdpIen1iLUgh3_+PMKWP`$}EN{o(E!FYc)AEC!5fv-6Ikc*n!dKA1l*TK{sF zSf}5C5m*!k6DKvjsM!aXt_;lGU(}f(5)J$91(I{DKo7M4@BmJkL9Lup9E8PYDve$g zCV^yJXA+P-odWtH!GE#ZG&G|DAn-MSP?2LO146&%8923#(X?gcwO0tXb~dI{V0O_W_Q#5AC8kmHqdiM9tMt0H;D!W+piTyn=bXw@(*zW(1U;DL@zD@&<6{npmx6 zW&61f94`)+GQfiNy=-0K^1gCJ#UYo6Lh(R}Loxmy_ymk|XC5#Qe8~PDgvR5ts|F_J zMPlK0?U-l=5LFVfFf`aF#>FA1NhP;mtw#=yj-uvjRT?6|leP3rOtl^-@3r_~T9wNt z(_^rvf0IfEnx&=#F$lov1FW2IzZpD1EGQ@lPyqs&JWvbZ==iiO3YYiQg2xfCpZC~G*7`9zY5~Z>ikp>O3FKw3eGXts8O{`RCKmR1 zsiUKVP0AYv=mSc>yN>{=fR}ur2VDn&&mGV?XgN5dfj8lkuQd~~*xbjrQ@k_FAN8ZG zBi2*uZD%+e|AI83z)yI{6C^Y56Ze6CblP`dh?A4E;({EXQ8^bh+|BLn7L?44jQx3G zprDkovH8O_E$FZ;4EN`OQ)lMjFbDPHupQ`9z~{$mbZ>S>vkZ{#OJ1rZ!rd64t$ze% z^mjU=?7Pql1xZPy=H}*#r!d=@3YVkhd!_?yC6=WpN}8HZ&dvtwN0WtEU=jBnUAFQ! zfWv9B?Q5S>vSIf)-ZJtZ?(OaI&3glo2VqEXV<=>;&`5I~of22|p+ zRx)5!?FSCc+nyBsaOrW^cG)bbA|M=kK^%C4bOjBYc(q7LRn=mx`}6c4e94#K5TNBI zytlUA;}X--(IK(6w$=xUd*F;kUTE+H9-G*bXJEw)0KLdVsjnDmLSiC-U;+b?4nfrS z(dP1aXH$d}s-a;}(qXd#O3g5XO;_@+L;LwV@XqG75VFNX`s7`aPok%Lb z%>k1g0kld2A|gsYBQWi)r(uL((rtl^Z$JD&Z4=ye^X^~_xpD_E8uBbU9p2Zt?sc3U zSMP%%{|=WHl9`pK%s{xhOureA2BjzT*$&9mTCVqo&o-!l4h{+m94J88+}t!KM#Umx zFP5OMUg4_wbayhKpsegtc?s??0C`S!!+D`z$UM8D9nsT=6(iS8Y6csrQ zP`JO<_=K44bD^U(Z8x49Fu?fDEA<%YD{4WF0um@iabFO3pLQEVXa=jFj&u;<ru6tf&a94gxeRIoJDOl!AO_(7P~x03)LUpe_WKK#HCk@k>eyW6<~9w)=|(25JEN zoapYead5akg>UCv+%e&M2T%|9NSEZIh}HKk;7Yu|q5vMjivwhp(BCr-?J993r@jcn z!}?j&9^g-0a&l-|Lv37~t-aWtkkU`#3;dwMfxvn^z~wZ6To7Ov zd&})YBqSsiM?+w9rT=76X+kFt4cG3c>N*k!oeDmP+q!kr01(sa!9Xjvb%FiqH z8e_l?j$B5)X){3yFj%h9aYX=dg6I3;x>c|X8~`?F2OuVs0tyS9(xMK)De{8;1M~`K z54T6A#Heu4%@{~8>g?tcfLd;5z6gMoP4*FUkvO4Xt#@3lW5ESi&;)1BYw%NWy>h$P zIjx}JoC?@H=@PMP`&aD$`!gL0IN&0Iu(k%?uEhssKJRreZ()pEKSn z#i~2`I2g(XyE*7S{n1LN7#y|j>+3uGkc9@GZR02IQ)3nv_1{$*8kA1>fr{u2h`vRz zTEWxu@ha}`5;+SCi-Y`9NJWGPn9iRb1Sy3*SWTAt!MGz@{Vw}5Ljqn1)^u0b%s(w) xdqyez-{&ANu$SeS!7^WkKVeb|lRMn`LAze4*ksY#fWIwarlz_$<1s?y>Rph?0* z;1`&0^3swJ2oP)#ZynnGH^4ja4l+8<5D+NF@2?L(MaryzH(_056{TQT5DAbFu*1`* zmw~t7T%@#JBeq^i9KMeMOQ_EKcx^U>|f5P2a zo)AfJe*e~s+d=*l+n5K{G8m0$jL^p?34sN%HFCdEByqKnLSb zc*#)@;XLDv41xPxuQR?WkB$3Iu#mVMoyrnpyefIHs75)YKdQJ!pLu9Oh0%3VijsCN zxXG&~+~z5y)Ji9CmXk?d+qXk`Ng>EzRXZ!Kk)J?Ee{tYiP(y6BF0tO0V6}P1m8hR} z+LcIgZkkguAyrjdm&I5^OUwD!ho=V95uOThH_~j+qw+Q1r&Ad^c$gsQATgwV%d%EX zOk-Qu;5`pwGOOObHyC}bl=`lIr2aSkNV5{17CWL+D^+D>f7HU9jGy^%)|Hfui>?Wv zPeRi4eJg0F5C|3FTv`q06ZltGSE%AfxG<$wg_YdzJJK-1%b*m0uUZxQs-JY}p50`< zsQUG*93?s#eoXVVF1<)9c~y06m3I>AAHhN7j~_-%6WiPQ1A9qFOwm`Hbzxb`hre@( zdqB0II?|nJN1N$zGchsg)F0u_7Usjriy2p{XoHTw>0pD7>CH;s-&j>yl+&Q=1FlwC zIk+T0|9vlDLrV<|3?yg#2y9Z?2*4IuiwSJTbhNkkZ9YWCv2mEhXxl4=BL(`54*D8W!0BI$CjUInl;)`M^<3)7 za2oM3$L^4Ior`Jb&_ybiN55$XJWuES@a&42pSy2!k3Br$SMJGWkp5K%6^Jz6z>!Dy z{8JfEx}~vxIla7&j-z;P>O-f3oWXLYr`Fup7qu*M78Jpn+ZrglF}9 zV>7#5Rn_v2RH^st11bT!+(I5TJNWvX;~xK1hKDAr(lB-H>7Xh%c$knuAhjqdE0wsf zDPkC!jHkEeF1X>2w*QuL)>(xEC$hHr4I!1gKT^+Tap$--fC?&ug)qx_068?C(SF9# z0g}`uo;ajk>J4&arFilvIfHMb{va33TBx9k9#2U)vQ6E)c8d=!o^Qcl;=tra=P1it z>)s{bgFeD+#{XuJ^DX6u|F8yqmQF5++b2pr&xU$X3HEh}{?4oQu) z*>p7;OBq@Jgpnvh@8!S`A zx5Otai?E1eETk-EGt-Rjz;;KC7G)o?|Gc8d-R{q@8GCu~4Iclw&@l7(K4!M%=V?N< zQfp@`@4!cUo+Rvtzhcwhcbj%b|2aqn(tx>~$H-D{7&&<_4?n-!tH34=I+Nj*MSeAZ zDWs7ii@|tJfS`f6gaHxTI`sY|Z{G#$aU41h*+dbDr1*~wCsOAM7i5FMJah3*ZALD1 z9|Mr^s$x0>yGbYZNT_?GO~c98`)VyQl!y(*deD>N6ZjUcj5Hd?TuF4EJqm*JGx)lx zdRbtW*INCEyAW1KRq|XsybTiNH)6#&MVxIaLgrvsp6&7=J#p~heqH`u{T!C{mh(5E zMi4HPhi#eEO5y9;)qsOqGA9A`XC{Y+|YdcXXccKaOM76`(Dl>cM@KoZrYoybX8i|;_j}SKuW+mea zA$W)c(i_~EC~bB0E({3AQOV~Oq>2N(I$&|vn}TH^HuTiTGR-Ty_$Y;HzaErVgXbE& zRB>n9{?6lX=-@#v_n`?T>U9_@ec#5G-gu)LJN}BRY3%gECg9jF{Bc}=vyUOlHc*p8 zV}^c`P{p5StCXjEYrWygI(v$7K1eN zma}`iXjs{PMj~gtq{BVdvy9P)e^;K1y&}Vq89rkqW*HdvZGx*UjC8_}~qxcPrmvfX1E+c5c5*ztw>U^fVg{Ed9GSrG9}4S zn6dvJDC2?9mkgJyz>pHZy74l(;3hp0wS4WJr&Ro;jxv1rGI|c9WbT!^8iejQB+Kz)L*opAW zM0lk5)2)9q2K2ZZLQs;{GcH}zG4ix!R@~ZN{mCt@$Tus&0~icCbIIRs^bOfKmMFr{C|A6lhNY)|K3 z;j8=yYt&O*O62VIC$j&wbTC=Qut9S>-GJ*km*~9F!3s)Y2p*WjNQe8S>9+H-5(IqM z;n^_N`96&rGu7Qmoc#3U|crM`2u5J@9_)5IQJf#g<_zNtMSj1 zk5udVx_+s}k^iox$I3@}a1--X-1DlEPVO&w^!}Mc%5{jfo@m6u$hRV(>yez!BwYVDR-fIuG0k>%quLH7L@H({pCL2^Fs-)o@&T zEQ`;F1@|j?`f63f27|eees=hollv5AhdM429oKT#cjzGcz9rE3V2mruYI^%5fqW)e z4N|mGsLKTDp~J@(Z#?VUNb8eDhQQ=RyN}f1n1vZAw=5@oJuw4TwYZmSeRG<%@vde? zBDbR{)rLR-_(W%CNzZj#AUR zEf^}4%uAeIgk2Rkf_K9RW=GO^elA+8A)>@A=Kd|M6r2w@hI3;yE%yfg{0r(Vb~UAothk< zJp4d=*6ZaO(|Movy&c?f6D=2-dX&xGPB4U0WeP?}d%fqe;0iB~`hq3rVL%;iVmlsM zJaF!ZzMaTc(%I7(P-&n$(q1f9^pT4}^Lm=)vEF%V_GC)83h8q9BmY`Q{NGk+Q-n{l z2x-N8RjUk6R{hFF7oo1J82SDY@QQE$z4sBOf>9%?c94;vubWn-|6CNr^P@l?mf zx`G&W^71oin{%futmjCfX^>bCzT)miw8fxKLe2f1JK}^08V$tlvvbCkkBH~)NVOQu z=1Kd_&L+EAC#($Do5&w^pCvDYeYcg$la|n|PaOw8SN02Xh*Yla5+xM)H=nJzp{gf34wfaI%(X!H$7xZG@Fbor zbD1>czQnn9F6s?@>!RW6wL@ht)$;JY^FDx)&tfnBA(}^9oMbCE<61tQ`LLag$_3sH zu()2%#O5UXNzY*3rZs|IO(TC}z&PVs`NL|b+X#bJaI(gYJJ}ff>JL(;C*;~b?eCcT&i9Q zltilHq{K+Z*g08`iq9SD&*PEceeRM`kBokc>SGqXHcM>5sV<{e0*|h{a`Hr^{se~; zBlDb-l#rhUz)7^?7GkybiU&`liu%k-G;ZwL>5HGJ?TOV>sdc+k6ph(_S!#-$(rl^S zKqf7Y`W)&IG~>;BEsASYf;-3a+ZT#mWe=1Uk!+txwxPZDs}}S?xV9RUYA#boIy3Gl z#0Zmu-`Ti1IHvjpoRN=UJMdA%0YRG`~B(eHaU^G*DL z)AoEi$3@6l8_eT}y|u-X$Y|w2yCCSx?En|lfq4OLo2asfOmMVDWBV9KXzClY1{@rU^F*i@o`|FV*wm0sC3rocQb&?i|&xRx+rJmcrEaM@?2Y z|K7sZ-KZnlmQJmqfR`wb$KPjy>}44>1iH9i3mqRpSj9|f*9M@_ZTfwlKhC!dn@;@+ zJ|2%tkgsh6C4YK^2762XC+14k95tw{>r4cmD z8{-<=Y| z&I1Kd>34GwxzN?OnQ;@KopBz0xG+>ssEhkHYi*Uh-K!v&#`Siv<5AM)Ro@kyWD)9Z z`px6wi8|W$g|Wuk5ADIJ3`rA=rF-Xf1ED_ z!zCswsbh?w2#!0JG3_%=QA!k~q%0AAS)VcSGd^QdijL;;37`s8;(R8Kj}b^ko`vA$ z{ms!?qcssVv?N=&qF+=iC`Gcu(kPp48*G-@V+Gs|yE7Xre5MP)V)gI~)x-Y^M%%>< zWJ66)DF0LiN^)SPNH}Uxd-wimXtlDepfZ|wvdlxpQi;h-9}wxWA}hg>kWEwgMZxYI zllh$$N_pX9;LO6Yz_cptZ4)YU@a*V<11!oco8`~YROnqTQCWCx_@DYx0l9zkKZL07 zezdALUicoSp-G0BEM5^3gs^xuxGZpalIHq+5)g88i2Y4Hj#klX#9H1qT+=jmK4zTJ zyzs+9wNaT|FKGQrJ1@DjZY@9*&0QAq@N=jO%YLN`npFd>N#DRxL3yUix`G@z{}{e4 zHV8*~rODN)kV~>xPJD--+)fDgHbND1LFa4Q{tFf|<`3IX>#VET(e_sXo*CEkpip%L zxk~o?88+@QXkxykf(a7m=cT%vqxq`H`_PiAC37a)FZCX1)|GX8+tE zj&oOvB8TVnl8TLt$@0Vej8nfd*!))XG3I>|t_X!89Q@?l;b%>ruN>$ya9HATmX+eH z!=(x{Na3mC#k|f{HQm85y@Edv<}%ksr?RKu#9L@5G$X;4b9mdF(F(j`luMd-Hu4Cf zEwA3zF#p_2Quw5$D6iW@yvnmsnF5GCyv?W{zK3KzR551jFz@hbyJ6*(S;p|paV*kR z`CF2`eJLYbe$2A#Rp|PsLrf~MVkRV`z4u&1o#XgSK<$Fq!V(t0xz&?sDnUcVp%m%nZ-@{?MSmdTu->#hOF>WEd^@w6;(`S zEBd*d#!_?*D3U#-OucAaxnH6!?Zq?5!ZBThucr@vTzD0r*(v|%bY*OfS2lP~l>W5p z8d5gcpU=pkc_1#$v8*g|+xsw}4NxD-{S_JA^WE#U5^@4U3U0B;!nwI0kNRccQmBIF zickD*n(&Z<1v?(-T9J3(!C}ev7%jIsJ%|5Zeh_!?<)*#cGu3IPJPWHa5QYO#(SCLx z$5f43uQXig03&Bf&KO49I_-L?r9Ao&7w#v&3g80Oav`vYEwJU>N=L+4T&uquLl;G4 z@(PjJn6E#S<()BK5kmm0=>N+pS`sF0c%!9#mR1fZ%4!hVFU6jl7^h|zU}SR*bi<=> zE+YPuX)`nBQDP+rOWU^s^Or|kiDRoDk8fl{vO{Fm@uTU|==J^0n5_H`jpx!v;Z6*x zI*aJoBAX&cKP#%Zso{HQ&JeheJa0B5Qd|7OcG{PkV~*S1K^;U0bQyb;t97zeYNS-{ zRz~>Lp;o3nQ*H)gZaa~uQVMd5B|nEil0_=w1*id{q*-R_R5$D6CWUedogNBeWYDoY z@e0(kWpjW2{Hb-?lhdp*P_(sWs?$aqt8eb`iXGY+&)^6{5qXY(e)e`-Hp3Bqdv@Nw z1i1mDG8B_ zjy;>ZF_ZgNzAK1r`fc$di^KJ8u?`FjSZ03q^%aiYvAL$)>rt39-RSh_{I?yYuBFA{ zv@3JBY(WAzh`9VNw1Anoy%Q&5{&2NN=y_0pgv$y+M-^&pg!@_G>PS0m6<=YF5UlxNXUI6XzvRgnm0u z35Atf>A7!(AQAGSt~Or^vQ$#0IiE8oOp>1ZBlST?^U}kAf<@UcjAH)w%l|`qsVph# zFg==~t!>ogcS9x^$PeNL$_sI3nWZai(r60{n{E1w6XqI!Sd+`#jYir8Zp4Y-4{95;N6Y)6=0BUX7m zU+~?Co$m3j@ZnK=ZkL~-5dB*U;KeqZfaw$U60Zd9vpPr&*{b#064k!&g(!)BnO9phTm>z^%=Uv^#8Z`+9#I9RAA0M7KZ zqB$Wca!0R4RWS`CrM=2oU@KqBB+8z#YxwqvQzUP#()(ZAPS*4S09}6rA(GOsaTcHW_ADZxCXqJW=Ktl%wfC2dADNC0>lL# zh}nF~@zXLke-}A5aH^cg6;1UN(6Kjg)e0HxAtG-NQ%J;u>9R@Gb?4nrv(wZ5tN3c> znbf^T@eaHSyoA)42xc0Iem%{$COX)MfUiO>MxP=M*GpjE*_-oYV+6Q6JOMXG97At5 zpQpP4&WS3y)zm+!>`#D^{EQ-yac)2Bnxkdl5puaR)ZljdbyDzJVUiC_=%PPVU&UCE zQJO(TpI4Fn{lkLt}fx}tRoIskI1VlC%g7TYGk1sWr)5l zhmK`Qvv2R%CYeHo<*&^+*tBRcw*F&NQAY=_Wy6!fWH7p*yqvDG0;zvE(?3Cm(i+BY z-0BRYV24-ddOA-`krG{o&g1E{rR}W!_-9QGxN+`_36PVRI5-wqy3$R1Sw?d6@6ahW zJnDDSO>#7^Ww>`#S+jpVB?4~UiQNfAWX|vG%r<3@IBwU`*n6t**4V4ek1f?5GT*~U zvnPKnPEnw)v&|}zPjh;ExxZequ3S8_G7N$hJilQ^21$h9zTq0}>a< zz@3c4d>kk&yPg*}w@sfP#mUPhlIC{%;d2b@KMtFd0N*?s<0NRNUd|dHB@xFkxTc;QpG~ zBT70wS$Fo7#Q(YUO}TJbLzl=$*LOI0hgb087nI=Lk_m5Ye~OmDJIK&7G1=n0{(Wk8 zQk0a^1GsLpqhE?h$Ng!`CcE#m`-bN+(>n~8e#w!mG3cuArdOO}MKXahoFDX-d4t#4 zdz^Nf*W}N!!+AB*!A8Vk-UX_{;`R9<8lNjt*RCzZ_vO)S>>w+A^=g(Fklm3#zY5r% z;Sc&f?~p_j@E|X`^1VG@w*l~LWoKuH1AX)q`8?3!+bS-t`p;fNL7IIcM|M0%N}+5{ zhOQ0BO^<(DVINwrCk6P9eiqf6O?=XvU@B78eZOwB!ch$k`7~y;(PX;ubhaR1`5Yeh z(m1~Vq5W2e3pyqbULw^a)6#H7(pu7^oYo;sKaan+Ku;Q>1RnEDUAv!m-KPut@JC@Y zZ7-R`?QKV~m`~@60nLJ!aB?u9`~ns*4OZhHz!?6ZW5L&dJI{A5J!X5ODS8gQAD^GW zB2)YeFAg6K%U!UcS9DtYQV*WnjIm!6bJxs-A|xS)9Djm9`-f$D`vqadg%uT%^2}|) zhA&52GIXmUA~tl18t)tdPa|b>Yv+;|gIeKl-4JmR(AEUbJClI2&v4=I5<|6=9`dwH z?0VZn(qlf{H}4#F9ezSAx0uSEz|ztaGclnUBKCSu%F@=>*7aRckc7bmB|T78qv|Pd zKNhNV^jv17#&ZPHf{^*cN0MmNfYv2QJoP%LrO~#Zr=%%Dsb?&|k))*eI-9GvnF{u( z((gzWdEP?lyj{?}Ip53!J_A%tx=pq=E#_#VJ#hSlRDcSRBvd_1rz=pm?PprI0zCx) z!FQ+w&O&>P5!g&Rt#{3`SeI-4`l6lFPJz||iUNC_E>JwSU8|L2a#&364ieZ?CdfEk zjnFs)Ap`0p{dUBa+baLhe~w-t@pKHKRsD4UO`5Pb@FU`qZOeKqIM)m4kIOb?zD`1$ zS}9q`<|f7;M9jm^Y#@7iFa-E_fkP`QDk@}hM!ffpTp_QkGLe@EfMlD?R+`k7Bls*WxM5-sL-5Q^|$Aj0GZ#XYyBH_$f4Ss(B;u+OU6 zLCyg7Ige1K#eXSldpxS-+X}|0Uq1REoszAc_&H-k zxzg9z9c5kJF(5$y=JH-8)0p*J{{|xR9u_C9@a+neR;zrM2bKWkB)n}*V^a9ZhS=}f zkvstoMfe{nfTAOA%A1GTS^8u=dIb#%`KuBZC((^gJl(@w7(n@7?@#O`$}8nCD_v>z4c!Uw*s zx$nfhzmii^otDkg9B*c%Ez}U82qtt=IusXT(bv~ARz~$@atP*-Vz2)<3(&B~i(J!r zh45qD6~}rJv+9LGnoBvtI>{M;<*mMe66I1fnat+PcHfThez{#FbXzjk zbDrRmBu_X2alOuF#@N>FRYLV7ozjlxbEIS*ua4@k0F;A^i%TI-I48}(GXkXF;k0Vo zQU@d+RO7YnLve|l+J_+0k z8-eA}9+!X|>{#`jo-+t2xt>otthl@WP{{cJuIV(GU$*qTd?JH>dAgs1$#N5HLEzOu zunwcDUYq3dy0ZcGym}hAZ+#q$#_J!zF&qy^qp-BGvB3e=)+yxE_dJxshUkTP59xaa zqk03ReGMq3PT)dMy6!jLiS(fiSL=m!<}Ek2wPn1oM3!B91VFrfUyco( zUmw60i$-qEGJ5kZ_lUW2DBfFAlzSWf^-{d^hcR}A#kWs5&EEh(qkM;U;m#1@dU`~66h z=iN6WO`@o;Pb5v~2+gzMZsIn&uBAb+itH;52l-M5le8wf4q%wDBUO^DUB@YRuHUN% zAOhZV_vyUn4Q7in=QQDGtw%v0e)DhIX)%rY`FS>gO@9%(RR@YE9e@oCnlIya_?gF* zR}xsy4k44Ni?kX|9=Qo`JokHHLjPC{`H0;A2Dlu~hSYRFM$+WE2Leax5Ft`LPT~!od3zI%KNB)HLxV!@$xKP zn5)eX>*u~hQ+}`4Zt~XNL+$pjQsfD49+^%Na$JvZIT5YpB|@+GlR&6qHec^rfX9n% z;M_5)G`&P37yi{IX(1jdZC(AK`?Lv0z1DaJP;20O^6CFrPa&{t4R|+*mX^|U=uXD9 zn(9cELr9d~Q$RRDhP1@=orwU;>J4BB5{Yp3F{mRJjpDOSytIL0G7zvM^%Q%^FF;+F ztCgt&Ncv9ymg-E{kb>0dQ#3Su7sl2|l|}T(c>LPFFX`s3jK(Zqab$I0jWWCg6mDp5 zjo*tk^_T2)#rvenW~aRBK=LY*YB71bh=Ff}e$SVkFL$f&CYCg@M{s?8{mEcS+iI`9 zf(BjQ&_cvo$e%XqZ+6MvfYg6qc|dW$vx)b8fxc{>*P>)Zq$qhCjb;3z6h7)_zsmhi z3dT96iVF$?00l05ygIC3yT%6RVsbpkU%?Yntz>J<80XEOV-E0R!TU95fY5&8S^KpW zM)E~PMMcxrR`E#$7z+yU)@TQ*0R^O?r9S{PkAuY;k|b)yZ<{{%wm6{p$_no=8;eg` zyi($@@Oe<*{4MQigsaNS$b`t83-Iea14jZH761 zyEP>7{YO@o?S#Of1=>9*ircTK1d-z+x#2@+c33M8EmL5DYELVUW04>9wj%+o_j`MJ z-ed7A0I&~;ffGPvEi6KKQkT`6pro|3Vyc2s|12(E4^zZB3}9&igG0aGLnKR+BWY`E z>t>~R=5`^hvuEd$kAKjtq(>w>7PmV3$&VE&2&lTC_n@k&^_Q3Bj)veE$N=qe<#eTJ z4KlKp+q&omYp&zGd+dD)fJA*?qe%cy74Fa0f!Hl?+R$VC%oClw*Zujq&4(7WW~8>+ zl0sn9?s@y2*!Q1tuE#zeu*m(zvPr!A=_U}|>Fz2)8RS)OqlQ~y4DU1#U4ESNv1ZBjq$>2^l|6fuLt0vTYpqzN*tZYSDy-4FX! zN@^j^w0(~?>M0^lfQggLF(sJvF0%mDWZ_Ifl{8|LfAGT=wx)3YN0k`!qU5-SZmOAz zme!EH-;=KJ;{k=|-N{I!)!dQ?IA^sDnV)}G!%HQ&X(VCR-WyO|Bt360=T_gpZ+Bi# z?xgiR0}c*gL4By-LdXH)?kM0MGO-xS50mv+yvI;emgk!pM(i05DBO2EU$)2t6t0e& zCLhmv20I#-ag()C+b6|2PJm1Iv-my8zw09yB-~#>ga160D^$mL>;)U(HXc`8YeL6F znl(_+Ab166c(buoM<78jMriU_-7k#Z^=2qCQD%<*Su0{9Ig^yOs4n%i3;%>PiO+99 zl)Ve&bd%UE6u*BGZu76D%|V+>HdD=(=#}iY22CjfN$FY3oGEf-!A9@t|9DUYvF3SP z{ode#aWYfc%7(QUfy^vwd0=vHWDVs9=j#t^y3`UZ%ZDWQA9_vQ$+ z)OX?X-otbjCC(5cPVru07>RzYrK*1W2W_T<9lO&gU%ur*ysD9e)@DkTFuSv$YrhvO zP>O~S(h5kIWk9iq5A8HIHbU5LA%q(5jzcULs{;4kp(EN%#zktS%bnaYa}ZaJ?mY4x z;2YyvJSc$MbL!~T0Ks1oo|$gW-_tcZ`pP%4w=``_0r6cO(n?TL!DW=DErVOcoB0jg8UEiwMPS0S~AR z-vs8jsT(gG;=c$;)xWF^)V2TyVXT)7yxfpP-CcMVq)UP-uI&q z3U@kFMP+^w8Fs8*yT?}uz~LwF3|0S+!~>>7>_GvzfIu1$lYIa&>wE zv;!K_>F2=$Tx-}Hyd~sIcR>P=)2Y6frBP}EgCgP?WY|Slz??oU#C9rJh&}mr(W$U= zL6p5}vgF^_{Bct%Wfj|bwfN5Di5}C}X7+03BN79x4b$&=yyW-s%_Z~y+V}sN^Zf6i zC&0x2-<;=v2R;8c=Lt-O{%7y4CsDUM13R@vj_&)AkTp`M7*YRt?{fve>I}h`YncpL zIx=WdsYxqt*GUqGE|N+OnE=ew2GQpAoqgt>@mwu8&n}`mlhMfr7rTUOyAzM7YEZQt zrT7Xo6gnLjMf{ZUomj6)}8iwzNz8N6Ttx|f~hQ6~?xVF{Ty|Aa!nIB1~`?N-nlr0Kky5S@OY zd7|$+#?8sM76W_4xO`m-7262n){$%TVL2I;!xG0FOd0!Q0&OZ8t0a;N@Dz{78s`iJ zetnA`OFXF%fm#)~rbV{YU>6O(?TsOcwZT|o*m65BF0^$AL5wo}pK*>D!mlFI#J>gQ zfjH+e7k_fl?uat_oaaQ*^|>i(%JF;rhfS zsKXEOu(-*LpjKh7tRl9o3~$XRhBU`v1=v2BX0f9+#~$_(eyDZ1~n_}=9^3z+TrijMK8?@ufNy~|hlVDmaJNMqans?E@}Xu0RZZxrCNUUYGmosP z&*Y9d3LmZ7MM+n>?ZM6$BG&7Vhyevr**DmbWR$tExiq@x@}{meH@|M#_X9x>RH^5+Ig9SYD0NEs)>6i$NX{3dvY-P`xB@Hx-;R1V0*V@V(2Cldv@*pC z;$lU%)*OO1tG&0RMn_AriekS3pLrT{&QUL|aSxsIlOP=%XH=xB7<-AzYB2^9ZpNQk zgUkx{58@`a)uE*smhGXxNKaF*)+!yRgMtwtEycH6kyB665b&2`!^(A&)8O=!ib{pA zz4UQxHRIx2W93)m)O3y#aL|fvV_aP!m0;k~SFq=9-)`-o&{4&=m$)_gNAkOg_XhCC zH03olvVsc>#+eysDVA}Is1}e!Z#_{u1qrqOQhh5Sr;9|C-%$9~WG@pU;V0mVQ{&3it$u+SX&eM2at3ZW z>~A3i6>AKywZXFnNk$=2+U;k8q*+Z9zbs?!>L3w(n@+?Wf03g=MKC+lZFYmBt2^NY zB^mt_71OEvqEF#nr!5hVS`a@YJG3S|AxcLj(=m_Cp_`&AB+jxGHvKjcpc z)oNt(&5UF3j5_qjwe{ubTe}usE3tP&pGjW~o#uS-g<#yaU=-ecYTb7ty$9(y?Wl>F zr;^sKdU0evAEJI3TsC0!W)zrceAY*XDcgvHNdH6_75Q$vc7IATxmII`>70a4bUoT zV5cP=cx=W`Mb^j?PbL4rCVSoua`j+s0@Fx~b1vR1f7+pJ{Ru}{Y2N$f*%Ob&1>Wc` z){$%}GR_I;!)lP-zNxn)#6~rC@Bd`G=6lRX7o6I9rRk=|=f?HgHQM(%y;_R0 zy|vTD4!P2y&TZrA>X6oMrXN^WYOS)z`%Ol%QDi-DS)6=xu0PCz;+aEZKmxs`QjMJ$ zLzcPh;b7*Ley}+An^O=n^pim()P4A6-%CnxAiniN=}%i3Hg6*`im=bi&S1yb6c$v@^I<+$KQ#9amr}%O_cN4zX;E6S0%O2Et#U(*Bz`JM(4A(y6#dvaq*vBBIi?-!~u zF$+~9POxRU9w*%#q8U4ELXZfS&)9Baizj$rFpgdwyMrT`lhZO8b+9e&a3*ff(9CPl zq)r;LB2bwGU)q~*(NdGq7iFEc3+P_Gb@^OBSrPlWVR5+vm=Wc$^Z|>}Q|Q-09#7o` zL6+90<3Qseg}armL?!S-gL{UlVoU&Pz^7qHAALln z7mAStSWQ(;^zG9)e9Q2UA5a*Gc0%Ft)AIBu`|Q7UzK98>p4P8kcuxwoWzTf15Z-2C zeqw{#;{3~RoF6Z{Re@_M3ZiSn@{&eug{;<(+cBDzXFx=Y!W2!XGlh zO1X63lzUL$ylYZZDs^$!So#crKQh=UB^42(P!v2PUE_x|XZx`JaS9S|s38arZL3!V z69WMu*P|`-agbBs9MkM(*y${X@b86TnU&p4ovNd+uS{Ui#&QmpHM-#CGkt|MW4p!? zp|b-Ok6xzqF9oN5DC9w8R23XlYd$j@`%n}TadPe;=gCJr4-XGLkq1a-p|>eGGWX~5 zV;w`S2`L)yzL+GXku%)qD95Mk$72q@{qz@)ml&mg&av>?Iy!yZN}$r(TGmyNpYI7h zGX4{8i{dL4;Mj`GjrcyDjdVXnZB7W-T;-OGTN;-1rzY$zLvzRK{d7+BiSF&l^U9x0^DA@)t$9Ox*)Ch(Dtc@S z@*6|^UN&(DqiKu0q@wK?)8lW2TH4)@Za)3rkE4o=4j$sifOVjDlVIcGeG`}^EiZAWGu zr+3xD^>gx_iAIf0oegGtOc44_wp3{oh{|RCY|9R_KOB=Cy4g-HVmf=ML|l@|Ap7ko z?Y8x=0q1fC2j8j%lN_(C4K>ie+;LlKll@o?=LDCAM62KMY$XY;|El%kUfyH(5PBq7 zYFO`=mi?)Gjhzb2-4}MR?xOs1e(~~zfx+YH5NgT(M~;9w@6tl-Rt-A&4mivG z(#rc`PyA~lH&eOt0?Mujve1>mPHH)rv}AWrYjekb%5TIm?9;A=N!-z&9v8`&;3pK`6N5e<8P?v=X1V9EyFkeLym7t0W-_L2*?yT8=_@& zQ2Mr_2k9A)B6U=QO@{c6F=Loh$fyI2QsXeZNcnr#FP)7A&U_^9TVqI+vE74EIYJOl2BUzm4EmK&tT8cbM@b#AT9&< zxzGItpqagZ9?*_`4K%zKG8AWaC$(@+i+@cTx0(SV%+vryhXMSWi$5r`)54O8 zV{@h+7QF}FbBf06yWmY@$X?@v2l2D65KE_qB=6Vsa&)vQ-}@_F$5)}AU>Pwq(E7M^ z9t(_7@1m@MFONT*3mM^s%mU&*ORV#5z{76H`bqTIpu_zRm-mAr-&P=cv5RaYw8z%1 zD-xczq*?jI>^$>U_y`k96%{6X`mG1rR;PG8`XW-DsYfYc=$Y{mF}DErM%FESSX|8j zBGN#_Y2J%qPSs9y(&LAVd95hmko-4+mx51|7D$A(;W=R_E5GElYmLxo^wLGg1adM+ zy2g892k2;r%UT-)2dtk|bX(Brs45q!-7PjY!bKZhe_>Z&lfzh2!_}mye?rA)U;bS_ zq!lm2Kc1T=zIf%huHJlHU5Vj+>&o#IU6Eh>r?k7Yx~uacA&jBjD1B=@SosUm((ySJ zd@uv*XW>?u_6#Nd)7fduy6*CD(PQX1)<}o7=tQKClrr{78xvsjLAE{(OHX0Ah*F#x zOeF|ZcnhaDScwP0t12k=0BKAaboe%;lf3dD$JHT1)lcvB`l>tdF4qU4>ZBkp7qmz+?ZR=Dj!DQX2ZoyW#7@@RcF zB2Vz%4XUJ4!j@vw;!9sp{Rh)26C%l*P{rj?6BIg0NrM+JIZrK`1V=`{V>F5v!;xPA z_dmguF^I+4IqqZqQSdHXg7<{|XWkbTvA}wA*>e!Sh=9%S`|yq>-^)8z119g0b=YoZtQlbessfLk%=L&Ep5F1WpH1Rz08OMt`-o zjHx-|R3vT8=58wwfu4op>CN|5fh?WuQ2KP_AWApQ@dPSVSM33v@|DiQh;tk=hPQX? zSi63YT39wG3MRGk$Z(@?Y$iQsoblMi8~8)WF@0lXEds^K;e3{DKpP^)YwEPUCH6&x zEN?v&c82TI#m{m@W1UIt^{Fcn8b-2Sc{Ls4N!rkIC;`5o6Dm0*SXs(M1ji1A{T(sT z3bJuB(^TjS@v2uhLNEM$S~w6c>l2cPU{c#GJao)Li8Z8R#!s9SG|+u<`e-r*EujXD z=n8fo1Ep|?gi{cTQ-lk0v&*c=33QSxPRsU6L3B82^~dq3ArD`V|Hd0F3|SjXq6v@} zC$}b(HOiFD@DwPUE7#f9xH>=u$KHyh6rdMJp$6`!PuQX`8@o1TktNyK;8;I(g z;5HHlqOE+k@Doz9f9duyL_LL*d%egoDQH5WP9*ZB(NC;1C+?vcg%88Jdl;-i?H^jK#-RI??) zhmv2{jE}C?+n1~KhX!U>X*inBL;aOH?W)_Yi51(QV?_#C1ep12toy^S=C`?`H=Lni zNm%`@OM9XW+%adr4lWgaejS3QdoeQ-`OQ{@=iMOqCGSkd+{gT43tZ6bfFu}Chcwa4 z`q7GK$?h<23A5K`=4Xmx_L?l4w=xF};X;hkJ~!Ll8rB~koWCj%OVC!7sME3~EqaHI zZ%2c{BZ+$%v!94&vqIHHzOMEDUhn;#)5}@nRd-!0d?MJ$5TJbXH19I>YvtUzwsZzF ziF1H`OvUr|Oeu*gyj7sJNf0VrcO9=um*nx5PU3p+MsrK*?76!*?OTn$e!B*~H#^T@ z&19v?>(z$#iV@>-P-h@HCbuN3-eg|x*BI15eK)+(9Ia;MEybFrL z|0(ck`ThR_7{im%(m_+a5YLvc?*`onM$mAtom zfAkxLa){AcdwM;gyJwjN6nXze`?FE-7{-*Mk-~UhwE>)EV5>hG)zPPk@*!E`wjXTw zYl0+ng+feEMl0DL#8781Bou7Esugv)-h`6;eF`j4i2^Uid~kReSP!m?Dv(hK@TA(l z#%X`v&BJkAuFzZk@`zIFl#V)ttRh!yO$#$0^d~1cLGkZm>wZI2;-oQ>3ML7rDP=Kb zn*7R?U7Qd`62`}|$?NS>jJ9hKiAnU<=orhlIeEWtb1A0HpJn@Ptcx{~Alulize zQo?x~{q?Bzzviaw266izz&VN~=RazLhRjdoVm{9zGfe$q6zMBdr*~NG>OWc1F`1Zm zZawLXPt@v749a%w!)@{D!dO++b6Wp$lFd(weYlHcN-|0}5LH9zb8?e}=zXI8(Ky6o zfDF{WbdmR?8UiTlLckB!c5uT#yv1iP?BSC0@{EpExc%P-3B!OZuC}@M()kRIgxqo8C{a}0 z|Mr!6ww38Cw;oK4&by&bJU!VPZ z2dm*SR(0Fp0%x2o zx#`B`#Bq%4b@8o#@%h)EJe)YO?ZhcQVUFRWfADjZjMzT35Of!J{O~X;)q6Pg#Dxr3 zLK<<;)RA6DVqDg}J_;DVa#<%AoD*^0HInt4x>e{&%%$NQw1&JVx#^bISkSHc{KZGI@sVym z{<)p3U$4@s`umgWNWZ&2s2Lp#86AtkD1@}|!!9oR(k34GuH+*hF7S(gKcrQDWHjK9 zzws8|f6yXV@-T6OAWEE$jX<>A=t?@g{HCOD{U|Gz=ehfvIX-n=jjjE%UsdD_tKj=<1BR+StoY>ftiAg~eED-luD!I}a>{S*4_JNuc7FLr8P8J? zX=)QtZ$yMqyD$X^MXhca8VP9lZRVU17-KOu9x^%}PaT+~X1oT&m4F}=2nmtV3|B%b zW2r+^YQ}4Vp-M;)PHR1(nD7(JFN_pIdOZ08eCNI`tiI$>ZoOe1x89Jn!iKl&oc8;V z^N-&+ma{%GpP$|9P&$5Fa=+1t%DCJ=NsbP*-KpmDM*ms64|wj;JiqDabRaeS?#T~u z{%+#K6}odc}v3t6_f zN^G_vlt=#{1g4WOUjH@&l^nH3@z=Mqecv~uqob@ib{PX5|AWk*II~a1#7Hy3iX+Dv zufc|GbBIaeoZ_%TSl`MgPSYHHgpFe*)0n4T40&U75jWQb!otKp9qtgv99Cz;7K_2r zc?ji#2{7RR3+4`T{4o|msvYD=mv)^$}v_i^2oCtNGS>Z z0rJ*PKJihH#S4;Y!@E?$1zbjMsN2J zU2_EQ>?$)*DIkQz!~vRqjy*D9e8OYH)-o|(vOOsnX4#>`lna_Sx0V^NSvYEtBNqoW zq5^%JiWt-2hy}yUDZ<8WB`URI`hJEus?uE=V97#5-&-AwO_ab)FfqEBGfy!bzdVOh zl6STne)eq4u3^F4!YC`xEHO5bW9<_bo;}Kui=h#h>DyR9X+5)(On|@bCKgz?@R>_4 zqqn!0rAwF6+uKW5R~H=}9cZoh>+NsxUk9qt@ zE74l>%r9Qznddih@(GLit>64GgM&j{bj5%0^u|xkxXQTqHKsV7IZj`p6G?`Mcmls( zAqqo;?!fViNTGp%C>WzLQGuYAT(5Izw}j<%Byn2+#wYNrM3KO<^C)elgStVOkSMI- z`ysZSLs?F`XcJ@NI=){6EF3ovLZ^G;1HxdOAdIk`4wSSo@dS-V9U*OOC)uVJ1~q~( zNb1!t7@0;>goHtjFl?Z74$Dqr!c1JFQ6DC*k0+L%mB;pqC~XsmRT{M_NDnt(KoHVs z_z2}<+f8@O%z71$J;}o00?s@CGaP>S;VfIWj3rB!(ACvNp-{lGtbHY{CTWG3I7qEv z8-z;TOvye!6V-_0Mv_@US_qXxNC$yVVmnNnMt=tgVWn{;ue(s~-0sGhDuI!NiJb%hW&h@%=N_CZLbbP>`+NE?GnGa-Z!`$;^Qv=XIhW<;3<6;c#`x*;YY0WJaq>Lq zR4zUytPz_=l5s-12<0UfTT~~G1BA4Z(!mfV_rO9bYtIV{vz)L@G{(}oKjqT%0?z)} z3OeVM>EAiPLyxWFdp{Ix9XktQ9rB)5qbFB%r*l>_RG~e2pG@_Bax08!g&?G>#0X3( zXE7}b-J}^kn%PCfv~*1~9Z&j4TX;-c+;uClM_XBDIM&JnGCjT1l=4v;lp!YHrI~7{ z?frLiBkd&%?!FM#K28Y}p$TCjlr{CZ0E|RP7o>wuU(%4KKS=}Ml5Q!}&g@KS=tKuB zP#U4yB`kJxUrpBICHHI!CoLz*za<-;Jb(K&QcWoZ zWXO;qBdiP=GGv66Awz}?8DV9}kRd}xSQ#>8$OtP#h71`p!pe{#Lxzm7GGxe*5mtr_ z88T#ql_5ih4AZdNCo}f2(Ogd}l}YYkK{KUE`U$HK?vUyv2@p)yWV2cw0i=fzNd{CY zWm}TMX@u2OSVNFzgUPgGwv&}eKdhj&ZYjtIiM)gm#8CsbeSzAx)nuEy4+ey&WryQ= z9**OnwQdntQ!iVD)w5tJ!P{pO_ScD`m?(nSWZX4dVM|Hzh~?yRIUL8qvaD7*>H|$$ zLI}bzWX+m2tXj2-!NEax?b^l2$OzSH72o$UnPSd5SeC`SdGjcj%jENUT-QyqjJD~c z7DAwu;`ZBbW5HF z@8ga;jzxdoQl-=+VKpr*+qNkb3dC`YWmy!9Me6lBVHjr8oQ2Av zlxitQu~?*7EVc+sv^nGtr1ILfO-D!aaS_*bDVNIxK|mBm2aHTJy#HvnrBVv5HIC!p zd0uM}onm1%3p$R2F$Sd+p6B8FerqJlMzh&RSWOe$wrxDm!}F5QK(!Or2VsiPBrnhN z2*a>NRvBieUP>v^T4PyO^3jQutXjV~j!hiL#Bod%MTBA4`elBC*^Q=hG)K0XR%H&v sXxsjGQ#YHxjI0h0(rOw~?f(P(A3{o5aR~k2r~m)}07*qoM6N<$g73sZdH?_b literal 0 HcmV?d00001 diff --git a/docs/tree-shot1.png b/docs/tree-shot1.png new file mode 100644 index 0000000000000000000000000000000000000000..3bbeae1c583cbbf411c5c2fb0ef823db9c63fff7 GIT binary patch literal 3665 zcmeHKX*e6$8jehQC8(v8nOeuzTHD&HNQ>H4DUG$Gv{OqAT5CxVnIY5=ZBZ3k%UFX% z5w(l-Dp6ZBF-n70#9C|Y;->dL_y7HIfA-Hg=lRZezUMse_nz-PU#im$>yrX!1ONcQ zNn0BWX8?dNleg{#19_uuv_d0q`XSuh_7)fno|ti*1^|R1wiaf$9(k`!jyzOu66<+5 z5?LBuYI;D9qC9Ngs{7}p4$?tUwLy;y)IU;-yOIQ*F2tp z#@Q2HyKf3oX+_>pK|3ZhtI99G)ZgV2uJuQMg+hW~MR)q#gKd9zjZ&M?(E(hKx$=Pp zKxH8i?i##2;MC|pd;1CnqUTi+_`!{mrhg$(itHI^7e67MR8Jcya$R2nv#NWG)I4>4 zbgW2Y<@If1iLe(N6ZjA3FmMuiLwkQQUENrHIn9SPqI!1ZurBnBk}W%9e6)l!G7vx( z&X|4A_5^Lw8*LsxgS5pb!(xL}_zw~^3^*$8D9@T$>3+3!t#+qnH|0V?E!d@^M&y&Y z?A1fhSuOB9zNs+wk6eO$3tP_UGUbwNUJv^GP=M615Q+28_477;IpL6be0b{ml)~ED z?rv_58!dB+VNOIY+qEi+U{!igh$K6ZJ@02zw9}KRgHP{=AAO`VCWR?>IvhVS_B4WrUP@2w zOwI;|LiO}5R}L}MQj^kTFRtn&Zu8>y65g+X0~7C0eyHUxT{}>!Xf;6p^?E^Gr>n5T zz4JD`-gsE3mvi0WluJCcTnYiNjLoHXDyM}NfPMFn!2Ly}ALj2mt`J@>Z`V9{$Xmi9s?rH@y2h2T*is{@A9c>TbYwkq_Xjn6IsSf~arQg@{(4 zQ;P>HT*wEI&v*kN3MkVo0QvFL&lmuZ!rPot$^7p29gzPYLy##(8`q|hKr9x^4t@PY zqBftnt5o_4kZ5i?3KB$mJZXC+;ba^`GMekGKzd;$sey)oY5iptzP8#zH>f zMwikI{fWRMNMg40Urm-FVQ-Y>$K8tw%#i%N9meKOtzR1*g}o3VGX4uIym(FbG&cH2 zC++UW7C(?A6bDSSQazqrb9r9|cE$(yt~T2dna{Ro20b$@2jmaRE~3>0oh*`LM;1$P zBWUX1#z{vlAY^jnFE5L0AQA(feP)vAGOOxq5zN(bFFcH$xk~+y9v<)zI1;in#G&N5 z^-spvu6HRFSv~Rsdf=$}*}_3pu+gGG&4sz2R?y2h$cKm88Fw&unZ1V?AZRtLr!Z@=TU1adCOZV@m58G=G%CBDO z7`r)mS(FbQS$MaT4cojry)%_?ya^SFWXOu|ZekseleNe#Urx1tls&b(!LpCTW96?h zdRiNHg_gAY7oEYAXZge#aSV=X2xn!YDT_^^P^?H|uhLfn)vOI;hFvntBb?2Lrbd@w z-u?9K@R)-(4hx@9!^(=tp9$!j`#c6(x*K7@{70g4ie)`XXD~L)gqo(c3J+Fu#ECui z)Ut#h1V_FfISE>tm%XsY+P%L>GD$gMuEv+3xSn~3$L1V)miM*npBDeBg}vL!I*0YdC*nWGMf@)E3CrYL z4L?>?Y23;JJubdq=~+2M&1KFJ8lZ@f-=+}nVgulhKkkWS+sD|Mlk$n%bI9@o2%|u9 zyN3XKTM>;_cH9eM4eM>A|5$GHyB zW3g_1n;&jvQw-?pS}IS}#TjOJxi=(;1|YS+5u*97SVy~bCO}P3x}HYW^&gvLngpx{ z=XBqa(1HqS>*M!+9t-7v?yD}HvzavxZ#4o1;jr{}P)>`B_s-i2(xs;IeE3jV-9l7P zc3#V<-W6GSf$LArbB%7uL5zKJ#Y}y!09C8^;EGD1eUR{#!WYE-8s8IqAXOz(qN+wp zgs5quBg%&}?rsK1?BhMMJ^@8pkm$U)f_t2StEj1u4iFUd8g0YR$2$&oeU*NiS5f}t z)sJcg8t=pvJ~Z9`-uH0dvHRbN@~vYq^su8f^8=?6_SfZVXM~@LUGQ~q6$rKop*CPUKEpJ%Vn){&t1^&!pfdBvi literal 0 HcmV?d00001 diff --git a/docs/tree-shot2.png b/docs/tree-shot2.png new file mode 100644 index 0000000000000000000000000000000000000000..f00326487a6eaa249d50d2f3438e601585f21c8f GIT binary patch literal 3383 zcmeH~`#03<9>>ksU|d2&$R?6YLyWV#xSMa22#pb$T*BBTA)*jUOv18+3L|$lnD&gz zxD%Sei5Mkm*e+!p%@%{g9)}EbzWobl?eoJ~t6!eySv$f4Kupczxng2+UmfjEo{#lgp6-s+ zZQUnH9C-ylAu%a6;b9$i#0^n&Pvc%mPbx93H#vb}ma>NgNXsmRbgp!lqvY(@>(Bcf zRkODKYkZRVuF>dY!me;j3Ij(dwn>tbACA=Y1vtGkwIO1Y6Yhos#=CMu}Zd^-VCEuEwf;x?V z;w9jY?WdJaydT$qTwuAz3_`)Q>N6`Es(_flQ4g+q=EF~mvp%of)dFnzw1W|J z07nuCg~fMPR#swiz5iUwrDm_m82r+8WUZm3>FK!tD4+9^Tvn@}-&$9B9W#~R{6H;@ zQduw#H_>^g?0G4M4Afs9^Xo5m2)2)_H|zL*Z9l%lsG`X`^~3HIEfZ=CTfcG?V`G@0 z#r!?Th}`H=*q+!)Gst~)L8?9nr>e9zU?f`n9Gz9%4wskilp@4c`PfXS;6vDycfY<} z9GD%7U?{_5gr^#gyLo_N#L(xe9p}QAMA%TcHkH$ z5Rw>!3N zJ4$2sWyIK(r`sOWT(}_bjvn~- z-64}_U}o8{nKWrha~r+WIf!mK@Qc(EP8Kjb9P!uv=x^mw4B<{{vsT^vA$KaR^V$=_ z0!7)4cRxB=)YoIxL;_UU@M$SkFrIfV=d?hsxm8``ZJy=|2ST9pc_sge_zHKWuz17p5zk!p@ zsLGfvrib#VYqRNvx|d z(sA#L7y$l40!}b1!2)o5Sw!{7zrOF)eg|2^WLVmwm& zon8dfUr?5~Lpi(2A5KKqt#97YO;DC!sbdO{<;_d5Gro<+ck}eusq{>ao;{?y_M|SB zw}gY{VYak@8Wj0?&6L&?O+MN?Ek~C_YDaK@APlnhzoLoUDOd01L)*EmX4y^&g6-tz z4C%zFv}9w|GV$knsVxNh|C%S+m@Z-eF#YQ5>-@hHDd@zz=B<5qew4zFyPU}V_e`Q_ zU1#~Y9+(7XfV*?uN{I^ez=JojBr25}FI-)%KX4421X7Z|GSr`*Jp^*s0GvVf{QP{= zXh%m!$xh4A1PZUk+vjp3-s^LqRq&}gX4N=7!5HI^{5K7Vc%5wBjT<(=z)!U8qVe)EI2iA!mcxIVdGpVKxa_1xpfl$#k!D^9Z`)?LL zl?B`V^mwe-ooGd?iKu@ZEbCmWbMc#hUEs`p$hu9;3wpun_1&y!v6eBOt1i`eD<|bs zN74PTL7Comy3ri)+j2!mjnt?HS$;PDX!X8&E(bF6I@u=5igKElc0zL_BWLYu_WoPusrjv$eS922yN^GIVGIg_JC!pHp9KAvfw z6nP#MM5o+xupzBWd=d{Wu;K{_YMi-%xJkUtQ4Kk2Nu))<%Kc>Ya*>LPBkrvJUNwkn zk0Q8)X6R~;Nw`=eKUofw__si-Bx){rgnNHqhngNp#6mon#ntNp@ZVshpIbjO@ptzG xsY5IAdtgu41QL2*hQJJ}C;y3n0XZ^4nI4R-I#1$@2lNvXb3E;0SBoXy`wy4GwW0t3 literal 0 HcmV?d00001 diff --git a/docs/tree-shot3.png b/docs/tree-shot3.png new file mode 100644 index 0000000000000000000000000000000000000000..fe4c11e156584f48ce8c74a11d7a3d41c6249594 GIT binary patch literal 4001 zcmeHKYcv%479R`|Y4i}KkhjVEIf-O2293vHknuPnCeoP^hRCBx&cSGg(x5Qj97p0H zjm*hoG}Oqu@yHBE26;^++-cn}_tRbXe(%eF@3q%nd;QmM{r3Lto$ic)9XfdOAOHY3 z1h=zx1poxH`F@Y6ApeC&E4<(vA)FQ5O;l8LV#aA2066Rgx4z^Sh=bvrkMS z)_%#zBSgYQy4UkigHaBT{CcXxI~(lUypIpxfDDRa|2ncv-T!7WvfekfYea2|k%i|4 z)>8`_Sx4qhaC~JOg@Xvvl?NC9cuk}0(W-tcF(P6w#ocm^YeIXI-XiOWuQZ|yZh;^^ zy(ql(b#+cM?{led*v@^h`xu?XG*K&9#;meoopcT6#D^@2Ig9YiP7}ScrPp4(ekNOm z9>Xnf*Adu3APC|qmUC<&{Jfh8POqt~yeZI}`Fwo(oy1Tb7cw%Ge}GnX;mxGv*1XKq z%E(Nh&|mJw8~&cg<1$A0iN0ympf-&Bu8PT(xW>1`crfq7%bk<#QFL+x;8ggzvk`H! z+|bt&72T<+4KHAQVFmrBkq?TtW}`i~RV%sv+kPudzw=U!H;fwfikQ?ObyAusHpW?T zB-^~YLC8RGL8@R=XKbQ%=-snz;+__?lJ|R7{6e8-#;@xu88L4L30G|%^&ty(7*uk{ zg29D^g&QWc_i0ismLfO<;}XU{wd;H2f;+ZwwTWaoH7E<0jYk$fXG$-4V3Q=~py9TQ zmCwAD#6#BK_D_|q@zF=4Dd#n>*EiQn5wp;tr~%qlg#PSn=aU}pB#Kswf#lj#YcCJZ8Vj3sdo>jz6ia{j5anWu$6$>SAmFIy%N=Tc7G!=ql4KkceT{? zYaA@$S&Cxhj(RKXM1@Ae6`HL-oKMOytLYA%FB5mca42Z{#1{VH>dc8P*5q0SJu0_r zZz=6~lAKw_Dm%S5fZCyBp3e0hxOd{qX5Y$@B;~&e`;v>DdRn3Tb8}^^g!p)tNIE$Y zfF%R4`%=$29rfQ7oqWB<9v%zDx*ES#NtDC&LdHjeJfowdE7`OeUDud_e8SQ~PLOHr zyZD?Ptm~zsJ;y()Tj|w9pT6@3htrB?n(Q-H4dgbTE_)&ML;RFreb%bWg;3N#j|n%oUaocq!Bnimhgrc1;;~lj8vpANYMaRTw{cz{BjH z{A<;bysela?H-Ot$|%t|Y^~v<(?z@@jB*@%TR?e(wHo4y$JBPAAQ`zO&iMVt4351l z_ttM*XId6Et8z`^)`yY&*)(#GZ7pUR6Qy%V0cHb&v|6~nU&;Jtk{JvRtyNp6f6ei7 z=UBMww!bK9WO-zUt>gp-x|kWlkx%xVy~dHPI69RHqkAQYL6o8FY|l&}^yBFl0jC#Q zTF8uwmNk%FGkModJn*JE9QAVOYRDC=-j14<%%M?9}+JIK@8kx zTeD@73|3rsPkR{2GaT}xIRTnBYxVx9rKk6{n|lXkQ+P#dR%g>GhbMs zs@Y>UnUtf$otHvqc(CQ&S$A)ISXD!#YgVMjW4FS352=JV%HnwKkC}2L;tzYqH8Zvo zlRj^ECXea2qe3TmReFb3vZEYsh_798SYb0CHIz`&UNfJw^L&a9_tS{NbUbEp%$}#M z0WZXwVeQU6Pw8uin6knifd+r*;_yB=wYiFL8pMi}7tHiy?%YOwLZ8z>sp2JF zFtgY;Wu27pPVK;~p=u6vH0_h$oTcQOMjchLzUkfaXW)5XtlZ=JR?M~cfFz9NDNma% zYhd=Q_0#4HBDC=b%f5Gn!-obQ&)Z0%1p9NZF61rOsXI#m6+lk_smHJQwWe6A3(9z? zLqbIa{}l80{Bx6HCVKa5V!*wehrvISenRlSv!G*ebg3XT8ihh#^M(XL{gHx>o=-lh zK!6JMssgGtMxojRo7}PI9C%N?bW+i%f;)ZY`FlyMs)bhx**}3$feScmJUGlAHDJ)XDJuqU?1DZC6{MIZ%IV+ju2%w!WKP+Uj z52>`NJq6;!Ayjd_)G&gM^RU-o*=uLN_8~Gt3@}9DMttym1X3!=(k4-?Zx&YED1wuZ z>#Ta&kvvwohV;b1MUWr$BfXst=lkTe+s)@jg_1Vd0f&IGqH^A_V69VkzPMM->`sidp|CT;w|CdfjwZu8Gj^1TDWh+UoM_h&{4e4J zG~@;ItP@x2Z&&UX-J!0#zq^ORZBB?En68bN=*v6H6aT)qJv$Xox`iM5zL#|ead}<* z`%8_i06g+;tg+%v9By!~{ne`*Db^Q`cGO>}hK651Qo)6LkLJ%4t_$(e7I8K~;G?7S z5Tl;`G^5MVWwX8<*7X!Ifk8YmZo8=yb{W)&n&YadP5G9qK36|!9n$T4p9{|u$FE& zeCqkn*BPr&L|}=B(M_W^F`#0{9n;U=Jz*-fo9x!-C6C?RmyyDQS9ahN;hY{GC1m^= zJb!#6jm|90A&`y^GxNt*`16=^r6_$U$l&C79=A84N?6~NSPGuxFJw*ZUwBU5y5EdE zS_@xRo-Pa9qW2p`O0EKcG0KrzznB?nX_y>Fe(nFV;Ab=^Zx#YyJIW2O)jW-vCAG`M zhF!Q6F8Aw6em9oS+%85+OWKP!Uo;-O=u07WmuWhHS(<2jRgzlGQJ1+#yP~=QOWg_I zdO4oNZi=BC$<{z0><(VK-GV-2*gmNfP3$6`)-spKEmWbhzmZiBBc;{nHG-=tpUJ%m z5`C4<|DkD^e`{K4=}OP^?kYO{&r)s2_a_28-ybid4gjkJ4(ZO@@hJows=(_Dh&1;0 z_}rLwh|X(pJ_b~H%AX7m4(f?ocBlwQ^ij?o6aH~z$mAAVNeF^&!T~g_hPcfofZzi_ zDy0IH0s#6P>8SUiTe*M|6ZxI4q4~E0Kp+pOd+ukspV<9hM3Jkjt8*idS^!_+SC=df z;Hu7kS*D>h9mC`WSEgPCca9%0FSh~+8i;=6s5!nNjzbtN`OP86IjWvh%73- z)j z!)nLXKp@az5BIA#K_I1H_Ut|guqQndp_Dy!Al}8p4+4Qqaj(yUK}|h zC&{MbidDweEq)?~yFUFXQHb7w;-c{w#U^*s6P4dtH->3X?Ly2h0}9#jQe9)XGthAb zkqN>RT@hHB1?SA}Ul6l}opBI|)^<1;Efj9j2-TR%bm?~N+12@t0!f(N#}2tPynIHl z!s(b(FwBk!e_|lV;BeJ+e;lj#I3a*_mm3;4($rtHke(SHPPN+MR~(p zQqa@;Oz6Bo0ZV|$BL;j#sa`$t+MN{!!Ph3}KKkO|5Z5C6a#p9wDHh%uhP<4Ib|JRE z_**V>v5>}oN;Pk%BBVvQ0v*9Xb2WGN!x%a~gm0OgQ%W&nQ?HQC^Edr(Sv9#ok%Uft z!Qmz&8ZW-v&~>B@qBI{&zhV`-eXDJycHYBUTEER`J*qZpM!Gshv=LwW@`2nsj>80@ z1gAQr3|G=7ti|QzSw(4y(7j4?tpLwqvJCcnp zIik`M_rvNXa&wta@U!a^sQ&Gv%A`S5x3gx}-D?Ib>Hix`!na|lElin+_CVdb4d4dTCBP=6S5OsB>B-OS=Md|31)cAOj`^Q_O2$QRU>O>s==1uSN=J?Kz&AELp%Oob?qyuqO5Cl8dKTm0DqZJ z*E3k8Ckn#W;_yaDPQnQqd#AzB5^W{=4(~5FiJo0hMnjv>a#tSGN=wl*x^6fHQUc)S zqHjaxaQN+0$Qm1XxK`ljT!Mp4uof~!$8!*)~9A=v0?U%al@!zkvEoYJE@>XW)Z8Vn6J ztnU<@kN@?DjPrD6`|z-M^9HtU&@%#WS4nPr=9rJ_+57`7UY>BMdKefMZlhTvg9Sz( zlAZ?W%>4DrkG)PB$}IWx^6@FiSjlQY^o(2_5qyq<3hI7txmg~lXe>pn*hS&E5ERMbAQU8>Gs4)Ji^3#{s?_f?9PEr40 zg&KL?dQE|6ZwwxnKj$m*st5dxOE}eRGCeghhqKD<(tjA+kYmwuZd(Vr!7g>;sJ#(y zhwn$J;1v=r5{}`aw?{CEzgt)M;TN0kE|azA&lA z4Drt!2<*|h$h~YcXp=RJ35MvXLF!#n$*Nj?U~s_V+r4rwN_hXD8uzkGGduf#_tS>n zI1O!5TcemEpxRo<(&7jJtfmYOIN8qHZ)Sfo`#ZCLP5Yere*&|oIh_0F{j}|%qJd9a jDzz{70L%U_a;2KTE;t}rwMpE)=N;tX>T|Wh1)KFBuBJa2 literal 0 HcmV?d00001 From b78b23de977bdb94a3f1a7e5cad4427d6d7e04d1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 29 Jan 2011 18:30:43 +0100 Subject: [PATCH 433/867] docs: add tree-*.png (for tree-migrating) --- docs/tree-layout1.png | Bin 0 -> 27856 bytes docs/tree-layout2.png | Bin 0 -> 20101 bytes docs/tree-shot1.png | Bin 0 -> 3665 bytes docs/tree-shot2.png | Bin 0 -> 3383 bytes docs/tree-shot3.png | Bin 0 -> 4001 bytes docs/tree-shot4.png | Bin 0 -> 3050 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/tree-layout1.png create mode 100644 docs/tree-layout2.png create mode 100644 docs/tree-shot1.png create mode 100644 docs/tree-shot2.png create mode 100644 docs/tree-shot3.png create mode 100644 docs/tree-shot4.png diff --git a/docs/tree-layout1.png b/docs/tree-layout1.png new file mode 100644 index 0000000000000000000000000000000000000000..ee69f1ab33f84ba594df749e45fabcaa97a4ce77 GIT binary patch literal 27856 zcmagFbyStl7cWeggi_L7($XL;NH>S>PNloMyHi>kNof@6Zs`VT>4rOefA_xszRy~C zmM5N=*|Ycl#Dpu#OQ4|;pg=)Ep-D-KDMLX)GlD;KBn0pdJ3hT6c!73QmJo%i8YS8T z|3NU4kq`r~;JE$R>I+_w?IblFp`b7fU;d!K36)raHxZqry<>S{Qp6AFq9N=i&b)otN0+to`Ia@X70BsVr(h)I@*B+0_{`c(wH zn7BBux)V-(!*%2F;C^jOL?=WQTioyn{#8rF2J_bdIwM$YLNdvC0!c}>_9ahWU)f_* z%z%6bR`T6jFS3Co?NNbU-ZAY_PpT;nIXIiJoqk)%1S*0&{Es+tin)cT-NJLusNG$x zs1rQ*uWK18hu9+W$)qrA86!Wv38FR_S*SA`*?98oEif=jqbVkzo)ydx?OI=3o(yeX&&vilZw50snJK4)y4x0F(t8O7^%JYtI+uH60>0Z|x393!ZP zvI`xb-r#||lN{m*P88@Uqqe72NObi9y5cyY` ziY82RpcuahMSRTwikMa)86z~I5$o0;ssKv{{ZkuoAIM?hV#2Re{J;S|goYN;fB{E9 z4cyCNh>V1U^b7MfID)8atiwzo(EiK88V_w_Y?J8!0jAJ}CTw^6@D zdNDxf)ns&a$@Tm=7)MH0=Mz@o#^6kscTla#C~xT_FM3sr`8p{115M8D6sa67>!#r( zfH^nEQJ0 zHPd-ck^9dkvSg8n8e89N(y%igLSgjqE-LKC!0>^*`Hs#Ne3}8lYZ^gyWbxyu z+}jBsX)vZj3oT33KGGwH_@|VaUhN^6_Tp9@W121E5d5~-KP^%hByt@u%Y_YK7sO+x%Gs8U@*bHp*J3SL*5o%iUCp6qQrl)PnkIx{Cy zHmSYa@bd5|`zc%SsnOoZlNN)7>Ak(8ppaS!dG9*m8s7o4(}p^xTLMI9da=8r4ra+EDD$AicO=Pu_58ZPtAb}0 zdVuGzlol1&zim?O*KwGr-cD~)g*wwz7JdyXeU2a+h>D1U)VURA%3wG+-bU)%A7e%0 zjcXasq(MXayQEGeZ zKI%|S@%`*%-s%})a&K)UK6-A>f@o9n`zY&$Oz(TC2DadCb{?^#-_5z$A&MlhX;G>U z{P?KCZ)Zg~>h4a=Qh5r6d9*D(7$_gosr)m`;=_}!(yw@|w{=$5N%HP3M-|+%i7wYq zUG+?rKej2;*-7rRT%ePoi=@K(Zd8O=*2N+UNLG72Ptn_IzsHFr3W}&uEx1p`$vb=; z;~;)TOvHD}@YGNoW~r?;;X27}5%K#wlkyLhMNK;q89j~q^gt55u}Ez#$Gt47UBOFg zi3a-#2BDQ7>9pl(9Y#?BE^A-jO(*-2RjkI;cn#kPiltJ}{_d`48n?Hkr!4UiO~u4W z!1s4!643tPW@yP|Fj-wRC}C|Cj)>uBAcK@5Y)QLQ72{x#hwwelJA`oc!9T7OE2$oS z1VUToRCN@;_NA|BE5?=k{ZSv38w`|cFgyL>0Q=BiwF+eo<=?R}S5CQY(c(BC5T3kW z(dYdTxRLoP{$a;<7K*312s&aA?L=&V@EW#i5*K^Bw4TXjs`I1$PL&)i=9|yNid~bJ zoL+>3p%1gK8BH6sMkiF%YVAG-@=<4fA}E~LnYOA zXN&qk62XsB$B?*oanxU=Ok7bOz2Q0-7ZaFtdQuTvw)W2i!9E+dm1^wgV>i0k9Gd)z zX1}fe+^ri2EoT*Ol!w)O1${f#)hHKCg-q4nHXR|B!J%^OOsJHZ{xv(Zn0MCGY4T87 z9)(f4!`nLzhCU%gX`#iEbjp5;72igQ;34vF?8?2GdC^ydi3cRt-feaNAdTV>AjUVl z8%twOy9}d65_Ts^_Y0_vY{UUI;ZoaLA-Q1&y)k%0XL7OUr>T63O+;WPwf>iIM=L}` z^!cByL*-pKaI&b~zmF8zO_DiTweN_752-7Py6>ygoobosd1G^5qC1XF zI?+g{%|)22*>0x``AV-UCx4WIJlRRUV?kpg7ohwVI>atUCo_qT*a0WcbRJf2Y#A1$ zan>j5QE$h#-(%{fjeKC8h+Wipw!Es)zZ95rTU$)8*{p;g+WrXz3#(wXg7|hT)r)lD zly{0XtVp(uNIcy{5gOVM^B8S9_~=pO>2go4_rg}B$q2EUsSQ$9x! zR#N@kqR#oK-LDR375_vljL7rX()Yi?5`j!mN-t<>{vv>-jC({j+Sr} zn=k*ua(g{RlhEDJ`@O#8(R^`3ew+9c`Xq;H$1;(jVhMkk#Gz9RW@BO-U#yUmWUXKl zc|+2NT$U&oFLLL#S9QeNTNKB9mrz+m9iD{R3D+OsaV`RY2 zdQY|Wo+hqX3N@~6qKLEq&dwf-6;J(e-z}n;;l0|o(ygS)*P)G9|NfR}|4hH0GEZh3 z_0e+O$l#uZ`FBW$Bii8AY(wq)_t-=uRJC_0A-kSs=q5*SB~DC*E%xk=5`{Ov2G^?G z;DAq$w;TtG)FuImmaSQ@+XL6+tdoMA&nmg?v~L9;Mw;g5w$UF)A;mRkhZAJxZ>4^f zgw4<2qP(wH_UZJ$%>B{HT)SOOUYAlg&kvYcbi3ss?QY%vUd+ba66eQ?d>yQLV=HHN zK6<%WK;=yO4IQ^)eqB)ySDBnF@NlhT3~k|#>+B+|k^i-fafagByN!-!Yxk{Jun{`1 z$2gkBS#d>LhzUfRvfk6`x>r;)Z4X+?q;u51Z6ZF|yh=yUd2}PL&YllJ%flW>ioAk< zycvto($j;7cP8>&tvJzOEn{CaMz}o|#eA1t@O%jisfUu=aE)a#yh{s|jJbR*{r5+b z7qi>WE4{l-a>nMGw2wKlH8*`mpWPfeu|gVm%Q_IyA-x<@0+*fOI~`5lSRjbYVu zBQX*Ga3iWzkU=;7c-D9=vBl>?F+9g^`Z$P%vvBWppzNUNh}zkdHqWClOKJV+!H`uL zkG5zHHa1TFOpUt$cB7xLl9@rtM$M&!u>T}i`eref;w5F!FGbay0k+kal-VjIT9;7? zi0V$I!gn?$q!p;5Q)io&Qp!$GGyG~F$jCnuXd}M1_Cde=#h<P>{{WA#plHy!yRKGvch$Wx6)>~q>>rpJ`kK`fIdDRYvTH!`g4w-4OPO@uDZS`zfi zG?RP(gb!eEwG>V-X#_Uw=v__Vv4(Z&N?PW!-NzfPcU(1BF8km?*Zn9uTWjXae!y9@ zZarPZIQ=Gk-@@dpRuGl;WEFE|9`xz_F63J(;n!~;Mzv4ITLk`4OCj`pj~q)+Hy(`M zBD5GBHL2u-lGwDRc>H~N7~@-G*osn%`G=S#+9F_?NFS|NtKj5$>znmfdoG);&pK3$ z)Mq`7+GyuDd$g4CyvtQEFrtQ>h|0;O_Sg=`trJl~a$Ca400;Ez{z?$G59mwxl$I6- zpg~wZ(-Q{96X}=B{}2KhIFMw7Uq1T(@WczCXbrk$UyR@!(8l~=@+z!8BpWd}{|K>? zf>T~zj`~~#mI_%jCEiq%(FON)P{Wi)3Y97(cu&1xlpxd+PtHnhj1ep8JsK=!_9rzQ z6U|;$Sy}e-IC~YmhXgq7l=Z8N-ff3GO9BU75#cEgcz@1ad-}m4lQ>x#F6R7 z&dbCTsQ5`Rej3D`ojLG1{S`i#twcr$96h#68Q(u* zJI#d2DVHco^=sL6RnF9udNlQgv3u$)mu4GjJZIAlww!VFi`)|LTa4>vOJz+?0s6 zlDQ=;R`4#2q3TD#8_G`rVg?%G(M1H5m04tjR2@hr{61=09eubxzkNLM9otUTk6AtG z16Q!)QH0F6*zFQn@)DtQ$kpMbFmg4$4w_ja{CO6R=N#igIkf(nqmlftAQgk$l-uQu zI=+Ah2RuAH|M@VxTl@9wKF+X><5e*~L!xD4Up43DrTt%enn7)&7A+C$0edIPUm+PbJyb$)Tq>AWE1 zmMSnT%&D)c=OX70dO+~dp|n(ic?01gM)5{8@AYPHwDD*bua5h6vX19b1Gvu8=O$a4 zod}hV@4MtkGOts}Du}2RhSZA=_=Zwhc5AxPyLZn|HzHY-RaM0}2pS;C+b7@q2PsIV zIKXgnSq66-Dwkr{nU9$r{`p>SF%gie=Mg?zslDmaYRmq9v57iNluVtB2&S1Qayq>v z#E@)=?X9lU8v)O&ttH2??at>%7N5>sNvZ(GA#gJfK0>#*w{S4fZF($Vb|Da3{gk3M z@a|88*VS`FUqu4l4l0|F5CShwR&(!9I&(D`KYn9Le%RJXRg+##Pb!^`eQeNNY&&|`d^V|K~ zOoFnq-hS45op~z;%GKIKENWnA1kYvUo*$j^G z{Co-&6co4T$Lqa?1_nT*ML_oV#aQn^P(?N#nf32&JF&~b9 zbjx!!gei+*f(YlNgcQ-COcl#*=0`H;x3y)xQz-_|9oDxgnGn>y-nDu9rx_w&;#iUt z_`FR}OMIr5(zzWMD@Gw!pw#jFyCG46mo#T@an5ovULP zdsFWi8L84`jWUYm2vSs*H&3T!m~P$)&3rK}CI~U4&HP01=GQvwfJwrb&~p~ZL@=>^ z-BI}LbSx~@uF>;2WI^E|^whvQn5S9e-Oc*={PfuTa5j|w!Ic@LZ!rqPIlo$>HIQF( z$INN{kE0u$a0fK@Fy^I71#@FNyzkFepPy1cqVon7#PCI&ZuS)%v}8Yxv(Bpu3)5B5 zATYq(Oh}NvH~Ni)84mY+?fWdz>GOE_dt_M(X8Em&HFajO@;jsNX-A9FB~!AQT+yw^ zEs>v>TkJs~O@Rcx*mS9VI}Zm39z7Cvr7X#Ah^fhLD_&xnCv>0LqcTtn=2MA1I!%NYbn` zd+&0n;cUdjgo&ZpHa7w239S!LJ@M|xgQ^$D*qT@1eI-K?%wrNq0gFsv<$wTTZ2%_q_)~T}$nmPOZ z1i4E1d0hPaM;|`dIW9(`84&PO~hXR_tZ1^QYRkE+y9zm}Fqg}9r|%Xl)1H#qGn_eS9dT4oGq z@g|ODe^}JKH?oWc>BKXwf)Q(+6*UBnuoo12Yg+%7<l5o%{S?ju{@LJ3sBvzdLD03MS@=+w3djkk`MTh%ZkK z>Uj_{*5Bn;Zjh{7WnpYl2vd)d8%(J)wa{LOPEv7O^GEzvR+it`nBv9{I-mOU_LmiNhf_n;z3Ow%RFdOC4;rw|(vqAF z(D=|`hEJEnSta|wiS&1EucswK2jx3Wz>|x6)r&ow&v_%=JSVTBVz{b-I^_x^`AY^_ zLfyFu=WMA&nu&6C7I14_lSMLn69tItc6eWvAg0v*W*9$qc>YyfAcl4;u{{3VJ-0s_ zpk=q7WnB8q{dj-9d%K(4#E{7)>)Y?c3dPz1 zVZ8-PGNkhPbia9}u2`X`G=Eq`(DM8Sv2QDZ0KND3i3m(OVjYN6Q3^2 zJO@E^Es#V;1@CP@CsEyTNAG=i@!dO6&N~>04i(Rq|NgcB`s<})fcZ-#{L&WhILg~6 zZ&6Q=ZL$l|w1#3*k@!~jtZPe+8SWttV{K)X#$kyG%Aaw696A5R_{Xb8ljzEy*hef# z)}17DYM6hQnjLu`FQ!gC+WBwR0-cT)8uX|Yhc1S4)_bnhI+++7X6!6u4e^GvKcs4Z zX~!v<8UP_JyV~Jx&qLH{fu-sHi0I%*ML*;Z+P^u6CHubjZvPB+#&Q)F8*Pz;ML*1_ zV;}@xrp$(=KXM-jITw8;WInRz%O(QrdpLtLlFN3%ZYSN`VNTaoNkzq&hZ%O&e;G{>-Z`~i05aDfh$_Af#UgO zxx@jg7oY-8MGU}%>0r`3@HjY4i`iJ5|3DtRlS2%uIw+j7d^&l4ip>%5oUGFQfXk#M z))zw@{;2@;14G*jr5!CGcQ^H~H8{VAp;w)XCC}pz{|Z+TEb6tNf$n#E zC7>7;H8wV`h+4%zfwG#&1zkxs9*E8t=6o=F`Ws8ZZd?TMP4p+i%IciTvfokdC)n!7 z$#RtONa11u>FnlbbDdAg^4WZ+`{mV`s0c?UDl)d36WV!U!N069vsc)lN!`|y zSMls*j!gsg5F2P(NmWYZO~=2AEcx6YfdaSb(0pj3@;%N&h^pez(9Y}&qLBG0k-Xro ziB_Y{0BAJgsKQOy!l|TOI0gqb#*Y6`RMWf0XWL{*QXO7|0=Rc|w21%S2o9`1{_7>j zi=)Lkt~X3Vg$*+rDqfBG3bSug4DpI*UZXXYTa~e&;+tm~-t&y|pUZx@uq~ypp0m#M zX|A!fG~Q9XI`J&8Gs~q-y%)WDxm_HTV7L9!k~B6`6xtj&3Jjr#8e;MK2so|y-^Jsd z*QWaYmcMEVs6JFIVTbPn&}sUo#t{4MTXw@=aC|O@l8m^p-_+~qTQP;1)tc)OR9w=0 z^k1hGsX5x)nbT%!y>!NL@+G&2bz^uRTo^%ek|1#pdV0JEsKk}+J~N`MKE;>w+CjWg zN&^Dv`G_H)`sK<>+6<-(RPo;cSHzC()}_vx2-(O@9gb$SSJ1pAeW)?)wpi_GZ-+De zg2p<Ulnw_&U9#K*gz-4=;wkT;1=Iz@}?cm_V?dhEX=@xD-60PPLcG7Oq)c?#9Q=!li{H9&qRozH^o z=A-r8?B5?MM|eFdwI<$rE!*Ls&I^4mL)Q1C7tD>$ z{k-QxA<0EDskK%!^jh^6hMi{bew!^a7h5h+tHL?JW^&t_0r2Qj+%$KvJkVEPA{$q{ z)48Mkvm)aIoZ&W^YzF51Y^P!NR@$*GwEuhtnF3C-ebxD57myifo>#-EBc(sVN zqiNh{d82i;dg_){TgwK(`hP6LAtrOmWXnm%y~SKrw#LvLhpcEVLh5|i@OxG#*tj?x zeB|bsk4_OVN>DWXdOanp$p!sVM&Gr*!c9pUY<#NKC?5^t7{M+T!x;~8;)5>6qAHcI zO;0P1$;*_Ch>AijHhHhBOPdr&h4pbxBq5%f_u%r-y5Xw4T)Tu?imo<+H@5KXD8tJG zV>yBx$3N>ET!p9K&3?iu3Q@EB39%GahAxT3dJ}bL&>>Px12v;VtyPzpI=i=_v;5%I3sUk_I$* zUM5jPW1rEwm{KjgYUoB_$G%Y|BBSqPuJ1&)Cp9R3`zC#Ub3yf`{p9!FH0vc2w_e(t zsIhjvoyn$v&A;^ut%e9r>seL+MWJGZp4?_DG`ri}&+T}KHrBT*r2d2zNaaPG ztX$booDkcTexp#;9%*=MVRJVg{1F*zIMUwWUe??~zj(ZgL*7y(>Opv?!KD(d?N|)< z1DjkIRvyGMeiONVxHSR=_cg8kD(|8%(uY=ev`-u zRstA^3ffio=!=);Bk&y?-=DP{;r)>8QFmBnf&2ZyDD;7zO5&Wd{#PnrsPM8UXU4+keZEVWI_wc&V8KcLYLF4-Ly%TSj+}E)= z3oSpl%eT{vLMe+?<20ym3Uy=x@I)BjQ0+L+`k_<3S z+I!<~RaKzacjVRAJC&UD7`=b#aPlYUTvyy_L1-(|{mvJU{Bc-2bTj7rWPG0`c)NwA z=XEL$at9|Tr;g9wZhOoU4K{u-uK6Fd&1?;&H#%qjLB~{w?Rvg&1ai`zEmwxH>TtGguq5`sE#2=BQABk!^SsP?s-9Edrg0{99D^y%l6W7+p9}yV|dU5mws`>rBlByB5=ZaPF@Kl=O znus&@n9WW-Zz$h(zWerWHlJW8qRH?(`R#0yTGiEUo1=!=HLQ>vBjMHIsH^UKf!dWz zSW{=3-Iy=I6kqwygq4q$eX-%W)Z)lxR7Tqnr$cb4dXSZOI9n-?12nnR3%k-W5&}%5 z(-P`Nl`@g>fF8TeWLmtE-1i2t6*^p!Fh3usr7y3V$T zl(2(ASh0FsSloZm_qnB3DV8<7yV!F&UP>1UM0i2uSm>d^7$681bAEBLS)WOw?7|a0 zXh3bdR3Xbp!hU3B#gZBcmb((mPJM6r?>*2WlwFb7>yi`llGe^r9@Rc;PI`Pgtv?V7 zRb9~C{mKnZtm1&ly=aOjQcL&6+<`;Zy=0DB>Mq!y&oY!Ox%Tf&kQ4oA7*<*Om4Wt( zhwCc=MDL#BO14@}xt_PQ5qekbg^G%bT?f^jqhW6-$DOl5|E!{^DFwJLz}WXhzC8zG z#n%JA1ge=u>`O4SWZCi{k_izoNQS{D!+@;+0jB>z9Jvm;e<8S^?HTS$J5ez|pZ@$n zq+P47sF-~btNl;TUvcptu&xiK+~(+(atk-Nwxup5nf(!ip0C(w4`&<7y{nGGkAo5_ z-Ts{{TdMC^+YwGGydKT*q7;taM~!ZiQuzNp#UO!&U;RYcw2w=4GN?8wl7+F12E zM@yhm($N_<3?=Rb5fn3bAg+WBBqczO3X>URhJlg=;CLu_Fc5L2Wn~+H4c!K1N(=%4 zA$z!~-HNVB1yNRp5#}L!>KT-Jt)oK0NcGE}6f0L_h_~de?Sl#W|UgC6|+sbp@J-s}i2 z2B^e1*KK9qR1K`Xb!9$RA1_D7vndNxQr`Mn!x#p>wM_oGxxQvJ)Raud8Qsf2`$Hb1 z3h+rX;%=6eS5Xk5*MWweLvDzQoFU@qW37u&OyA~@< zR@c;^4oQA*+*@P2$kk}Oc$FArvh5~9BYmSnQz%@O7DUru+fdmQ)s4TFf(*IXQyciD zE?tU7*pGSBb}Q>;1bf2hly8ADy*(mXi<0-I(jxyeC5@(c z=hqjZ5pKH$9OgO`86vyhJxwPU#xoxX!VANP_I~cg@!r&P{t#dy#}S2?sEU^a-Ku2O?pp_BmN0el-*{7k3(0$dyeOR*<^FGg~a9c zCqQhHYV2V1c24cQyG85CK@<9^4T*ato>#s%o5F10=7$b*v0P?v@LRq9xB6cGeVg+| zC%_pC=Kp%dlOY^=0Hguw26~U*M(=y02}Aof zzpD^dEGJcNsT`afEjD3Ae|pjMhEkY}0cQaQ`?Sj;xyD4?^v)(cSVj#i!kogpcs9lO zxh4kd{!88vG(lin0T&7nW4v6pL=e4l5`rioESb3AAGupo;WkhFPtN8Mc-tKI^ z3Er<&5LuF7QMAa3|4PQg`KqQWA4(JXU-u3y-QT((6~vZzsPw8fq4T8DL6}TRs%R?6 z4ioweO4A&Q7FZFbxQ@TW8f+>@`lJ=*2u1GD?)@t{1vxlB>Y?kCcUS*Gbs&*9_d8L$ z-{ZF7Ea$>0;1$sEmVlQxM=fdU7j$u^iuLLCG3e$ZE!!#JTM36n^cgsnaErqUre6I; zt1s;Di$hC{wU$YGCTu;><;YzlpbQuG&yDM)1$oP2_IP&*B%0jlexQuP_(RR<`wA?# zI8(kx0$Bo(bW86Z4FIKGZ#5G!IjIbE42d!bRT9ua%u_nV%W#^_?y4 zHYQ1lF`|GvQVn}yE6H7;RWvYapUzCh*-tJP&pCHOktmpI7;0*__vdsx6{4iPi5#?w zs4{HD{6;w?H9YHkYJ5$moy7)td`+(A-OP#OHKk1_FgdN$+&9R2En zrE13Rg^1dgVntj19Fe%{xf}LthM|6k_nB4?{P!-kqj@L4uvEhcE%ki$5v4xPp51=V zZr=28_{t~FoE{AFk0h0my7WzHc9p3nD0*Ok%GD}s9ZOUpJ-g>KGAK-pj3$7PQ&m;{ zN{6CXJvW-=%9nk`iz5!$TR>6S25&p@O>jiZWpW+WUr3N4BFnH*f50&a9s2|$)tkhR zX8grZ^P0Gat8*V+m0MA_)g?s6ECgy_8Nnq>xi^V}N@}PDrt~v%?q&8}%=NHm>kova z><8b%}VOICZ{rin;KI1#fPG0C0SI994mWzgpaMazbLYLv(8%Y+Nh-HB9vpr z2<~GThg?ip@7Y@&A6a#!l(8s%tcXN4BUTMhGl`t4F%Y_|L5lp0y6ObHEaCOtwF}< z?~S1}DAvUx*Gd}pq?kHuU>htRXoLvkiSL|#uQmf^&D*cn4i`U>O{kk!^;mfssg`ST zJZ36EqIS6~_EyNSLG20&^G~^o6`K6qJCIg9KaRD}NL8Lc+^;^H0j)(cVY(1e^6E~= z`h9~Tp4_KWkT^oni6f@=hHa?le&_am`J@3H7;Trc+7bsz)hPkj5c=R!JM z28O1Nk}sfKP(h89Uee z3Lh95fG#!B?&*r!UGr96`_)&+a8xR(U)v6#?#Yt9{xG9MGD3vz3HEgMX3oGcpJOE15{fIJ8>luq~bA zdCUu{Ne}3VC5x0V4(4KASG`k#E|(M$fify^tpm8v2@W&}L^x`Ri*No1N3ycz^nO9Lo`x0JUKF(E=Vlm@hlZiLSr2{Z^Hlx4*;~ydkgtnZ ze|bE~<*{hY@c>db1cOw*>GQH1ART~N18D4?=P1C%v3T?;#dLCy0}w45j3JCeOYXbb zZKu72Kw1t(B=w3@tJIn=+_vMn(yX5qbqQk4{FYH}NrxX|s1=Xxzr=}ionmB(QiBAh z99&=&bkn^rt`?v*NrQb8t98Tgz%{1!#8OP1bel$2EI6e3Nz8%F{o)Pr0xEA55pTka zY-~yJxoJ_2Yd*2>X$8YU&k$MqwFL@QvVdJGi~cB}eSZSEH=W0x0;nJ4z?2ZL22MM+ z+kJ2e)b~TAOyK03vIG+IgvIm|trX@|iNY=jrk^L*=hvOj*+2;;)=aVONw$u0f7zqK zE2)Y*p)qI$L_$841={!Dq|Y}JFX87|LQ*o- ztb(5U-`0{$ofiv_rg?S`aK5-7e(!rx!15H4`fX;CZDGvrvwf^uYiwpxq>EfAn57cj z0K{|t2hI23SraZqXm>MLQ(3tXxK>(;={=_c=9Vd7zhCjlWXvFP=*K0wJ{gfL9ch6Wf@wfB9;|w8cpJ=^Uz@c!ALy%3C(_Mo z(_nhr2vQ~RlS~vyoCDe9z11|`(?yBUyjfwg6*+w<$}dcdfI`#Kw8ig>C~s z3Nc)><8hIOYmn{oZZ2l|;973TF`O{1`^+~u+Up!WFLAazQ->$*&urmlyf!t&ZJK`Rm+leVQ=f&+F3rchFxu><0~d1MVuF z&zU}y*hQmVOpyV_q7vT50*0%{e?jB1DkPb#BA3+r;?0Y^T2xfDutiyeHwYBRk(6+Xaq=9Sq+aSOBoz1NY0fRHiyViX_J{oLK%FQtFdMbxfhHA9$b7vGVFivcWVv(lEXRPgCL zSfahLq+@`2*vFw5PHs5^`}VG0VJXfzgIEPKY69$L{8xXBL9$=1sxhDr;9^9a%EF}c zEyE1j-pl|DQPZ*uA@IO%0A^yXpECCMy4_*@qXw|12TRfY6Z**p{gzSVKkI9-Xd3{8 zK-^5V<3aUXJo-EhSC63YNZ_VMjTG7S_@5=QvD6I+$n3sPUM((1|7ChK?xn7U25VO~ ztjq(NK9E%(tRSYm$+WU*T`y)7dERa8r`s`KO!QD&tQx~$_{eXy-U1`K>H94!7P_~f zZO8%M%{U8=L10VaIjW!9PoJEZzun3I0b=E$47%ii57d0)7~T-Tw6Ftx7$gPrOuvlc zqxu=OMr68$n$rM(aDzj#sG;4!$`jan(vb;^&f_H!{<9`oVssxhp59rJ%?&y9vTz0HpM;{srl<-hw|;B@$BLY{Cw zcSJ02>{_J6M%paW6lOG?9T9k&=&)b3b6_f%k%x0`)@i&=u!Y&#Kp*I=f4tr~A4C_U zi!ho?ZW+$kXS?0{O2n8{^ZR?=Hx6ey>e$kFBk{6LdY^Vm{D zY;sQzyF%mb#VwkrSU~d?2Y%SJ&*S6S(hNd*^F>>&%pZ;@8qoF%-XF48 zwI1`KcCR(>=4`%L6M-2pl7aw{6VS#)WkXPPrpP97{s0z zml-r7k%0n*9H;32u3}E8J?H#%mMTOZKPc(6OxCwINK;X&LP{GS#2$+FbK4_*V)4K>_4|A&)MW;Wo0SP zrzonO_~iJKLBl#*r8{&_>V;yGqb=eCT~?e=M<2mRIe3X6pfXY6>zBnK*M3U z9WkXw7PzpWRaRaOfJvcpsYhg3&-tLh^z`h@US{CAn{yV?@XV+Ylcj8mlce&k-T1lU ztC#QlaB(g@t?faDB=a?0j-r=&Zt>{WlyUYJHGhJtH0g;jF{SzWO@6a+GiI2qtG^kd zC28&=nHD6tS$31B$PnUDFiMuxE*)yvjr;3ty& z0xwI&4)$#bm4pm`@=ay6>(A8W)!rU-R4#PJ8pca~?g%+qQTCgz?(EI>8AB_gnIw4m z;UL?TGj^QRcyu58@BA{9GWvNqLke=k1Zn(2XW}?T2)tY}+exP7Qr8 z*9x!Cw_@GsaHf4Cpu&=qVtZ=AmbgY%(o-JE>T||zH?q6yLc)6!vyZhAXzq+F$BpkH z8|1_{&>{H@_KcI&#L9L%%Ad0zgu~!^wWN%tRBS(Wx}`r?MCHg+#M7+J_g>d|>$v(S z(&}mbd{xb~8DpKr=&RksvDhT@>;HKH@+Cdsvlq;!RYb_-S|_!%uvJyCRfPl4O_k1; z-cOdoP0?nw-T1}E3TY2Ls&uT6AHSo=Mc^$waB&idmS*DC`MY(Zp6+$+;>T${x9K3} zzUR+R(!n|KL1Fu}8-q~tchmV`pu5g}s90~-@(?rI3r}(gi$~*Rv02>qylYc7+&yi5 z`}drHr1`)g=AbqE*~h?c4uViAtvZOV|0`vmf4p1PYr~d{1%EJclXJAa!%yXtuZWi^ z{*+nJ595RwM%pfoA=^24JeOM+q8$F1lT?F#Kd5e&r=&SE28&Us&6E2Ch20B-r&r=P z7*)S2v_qVJlly3qwD>VC!lunINU3M_!*`$au6={ASos^crPYysU~*%Cn3xkAxB>iq z!Un#fj8d}R_Ut%$F+Uy$<$VLYLI^GKG!b|MyOsj6BWm&`9KC7#kS?9M8(aBqnPGS1~|E;(5&Hr}Y69RB!6V|}yL z%wqWtArUE&j8z-!_Lj!;AIeF%G(V>;QEnyln$#pXoP&(%Z`ueTGw)1zA+FX|W@)p_ zF@}x3c-g6Bx}UtByVIYfa$|kBn;_ zK5Ss$QA#Qk3=90hp+cULT)z$)7?UwNq@0xQEl^a^_#L&=@u86)%nTJDh=m( zrVGiK3p$wd!VRM(r;1VsRw{g=LB+3WC*=0|YV<=_#Qx!PF8fi+QFhsMk)*mX+#7f- zRXC;muLy+SI=_BG+G*(cF4hzF>3xa?_A8|DFa~!KG^BuwvV023lSAg#p)1dm;jC3s z$CH{NW@>3oyN>6>mGr5dEcVrN{xSC)c2oBlY)6QYP>OAHp$#Nuyi)-IK9~l+MCnvOzgg=UUpQVkGxpG9a8%Bb8hPPo7s0kmLWfO?;Ko)V<|_~)l739 zvQp(pcFL$!zvuc2nxBoJMh{N*E1eNNbMas?50{*~u)iyLoj0&K9-={jWtf^rXRufl z#y6_o$aL#`0_WjU_Vq#d-8Q^?+qDj??9@(hIL^0sm-x>M2nbh^1u8E1+tk}hq;zXd za~4@s^y!38aglk1Rll)G>%+0dbtwven3YM^R5p-pUJF zAzIG-DZ#7p_zHSo0nc^s%`8h_66JeJa(a51f&__oDYg={L}C;)@4^k)ltqQ{hHA@A zl2z3*G*aZ~h{)eF%X*Q?^4RcY2JkCIMal9bk1BOB`mc2)X#KF?JSACW z`x!}Gc{Ny!L@_*<pbD0#Z3&`tCP~~rtb?4m!_4+D6%%OqDjE-)=t^` zU3i<2c5LW>+pmCx__RRfm7Q!BDc#jSwYp5H9GUm&L~05)@6)b~XRQ;kwCeyPR1QaGj_zTi;7vM-xO=p}4iSa|x-L;zBwnzPyz5oz)&0+8Zp(&O2WECb8_( z!9X>_YPNie0JBlkhn?QXq7nNj(SFjpjf0vjPa>tJRUdn`|qnoKfPnB z`ezg<61iXPYDtp1RD;ozvmMf5m9DGe!iW7t>iKlK!!D+%6krC;I*gvkjji_ZSd!y9Uvgx1gTZy3Y(4>?vl z;!j+TR%dxTw85lVk$G8|W7Dr~Hxzk!cFtru!71bqp>FJ(Dt1o`#3Sr{H!?C4_-}7p zPLcVQvGX1WVcE*I9J++~i#%@?JOi|&jkQ)hl3xPiRXv8=?O74lb9b)qSsv5lLW(2} z15_5+Fq_P)Mw@Q&AYC2z{5$&UvOlo#a5B9A*^nOSp;=GN&_mes96d2FqlLsTV_pjw zP=s>iHAy=y$3Z?_${q0KL1nSSk0^s~KsgKy1U@o_Bo-ji?_f9p&i8+6i0J35*`e_q&hZsOfd1 zXz&E4%!vjrPNL%|t6jW*#W!S#eAL$-bFskjlk0Suj7sN;aVm=ZN!`qU9T({PNqZ=a zvF%lfdX*E+zK%Q}$XY`_8(Es@F|Le7eJd!)3}`GXEYo*VdBnE3r^_%g#T@*CFP(Nf zUPr&|%^mM9wmACuzz|#Xl<(mo6$G-VXgHf10*xid4Sq`G?DXk zC{Ice=)z7u{#oXNx53aOTV7C5mY`Q@-R6CnOMCeTR-cZD|b$OM*4Bt*9O|I(!=Oyj4_*kM&n7FPC3N{sn zM5nWviy76V>ShVoN!de#f2qS`%s;b1va#&3y5|Hf*f{vu^HFU@Nm`VpV`&4wYi#UE zJ_exUXYc-S=fNLe_>6w_=J|xKgw>ulHwF#L*1ND7(PD(WToR1Y4|!_j3hF8ntOq)$ zJ}g+B7y)V%QL@asT?$gX(Um;5{GqrJR5Is5PW&?Q>_ipG%(@VQr!+OQ8ks+)9+{D? z^KIssA3TDOX`Z0-Au3suacH@+>(GYT*Ts;|pBJkVb@A)=ZB`~O2T_`2aqs8FJ}Jc$ z?EHF0m>%01wjnMf{3fP{H#uKYq`zKaUC(fnC^I>t5CS0&el4SGG zQEk=!&I{k1E}lgG5E_qDD^A*0;XnSyWv}o?RNSxw!%F>Y6UxFI!ff*k<<6Bu2FOZU z#H2x;Lwn4p54OnpkcEc5IT`c*OeDhxOIp}Fs#+(iAcw56`DU%jwvTB`Ac;<`EB9!H zo+tBoo@c9|Nt6mxcp`bF{!|R6zSU8t=+7z{Eu|TB=o5el3xHATS3y`KNMmusDxu=+ z?aoGFT{J&6ami2nKF~m}Y>S(+pYBIBm|$3P7h|X!J%`xPLc5oG@UO@2d^RbqqQ=Q+ z|FK5}zY{(`{eZW@m!4&?r@9?0%GMWV8#BIdf|9}Q93W}VEcs}NY24bt4MAt{SHsW8 zSMyH@s~g^R_Vw4a9S-|}s?ANQQIz{+^!>x6@Tg6$v{>(|@(;A^xrlX#P+U8t2p%wa zoO0)uUx}mDk)!9@#|jE~M=r9ypnt`+S+yJL8I8@|c9bZMb zRMh5r+)eLCt4=19tnd6~QcSQj0a4?19)jQYn0cfu)Nd_6zV-e)zf;DcfWi3b;x)eg z6i$FzelQcS8XgNvy}SaFolpxo#m(Ay5Zl)(B?U$KeCv%@wEK9l0>ui+3N#-V%t+Un z4%*<2Tw=_~T|News=TBLSyC>YLhf6g=jP=v3L$FH3ff<3MhWWXHuLBv<3%z^l_K%o zo(B{e?HpUXA*d1WP=FEq$$yJB$3b!|!`w?@24NlJM4lY+&3 ztRpA~^X#_tXm=J$BdSW*nM;69zLZVN(T$t&B4zXA$toX?L?Hezn=i({n9|~Xs!(N0 zT_Rzo)ny==bw!)g3dAzUYg{ zXU~U6ou1M}zce zSmVr_ezLP>0_V;v8VfJ+rt%+NIQHbySKPZkk!TN7Y9Zp;Y2Uu9st$g$Gdv$I%bcW@ z_G@^2cj>1jA0F}TrWfh&sV`S9adn$uOG0wFgsSyVVU_CfwPRTM#>#qU zsbSVf9Rh-orWQX|mhiw=Ml7Zd6P~BB_K3wfAMS(sGkN>V=~Uu^dx-L?`c6a!hE8Hj z%j#4Wf27K3e}lk^cpR+L7Vne2j~kUaMZ~%rd`O~vY^bN8SFBl?6+_QYG+BCf+}17(i|B!YrpVbaR1Qu*ndDMrLJon29= zi-F>KtNuSb?!rDjMKLF=ViE}EAcT-Uks)fnwy0x|Zug{79EQy{2IuBOX~? zJv{EbYBIViUL^i7Nbh$*8%UfpAr*2@<$Am~vlCMG{A~8d5ItPC5mk47){S^UlrMCd zhJKdju(duDP4-=&J*B`|vi;}gDrcHX5mLrgtejxElMX4@bvx+VO8|SB@li>=`~>B| zpO#JkY~>_`gs+tO`7N}%T++oYPdykyR763~PL{iSE^hT>{8^IUO3X@(q1>pS;4Vv0)yP*RQb>)0$vmivov#_p z=v*x?1GA3#zEmMy?Jm^7Q%J)sT7dmkw-u6>mX9bI?d(QUE>fIi8jdPY535J3Lt&2( z;Ef!YL2O6}f1B_Xg+w+@NS%-@@M8+JF^;QX6)ii0xvq5onT9%i=_CEE~~u|Hs?VtL(}7CqfPV?5>mdJwFc7 zc2~}Ay)Ih9a424n!!+5(c70)dPI*Pf@&3JTQ8SVqLjKUE3Y-@MWTXY*HLur;0Y8B{)n3?V)-WoUvX1LdK2QU1VfzL!agZ0a1&v~ z{9+{d=G_;|F&e{1uh{>(Vg41P*z3xOafl7_&M{uztH^ddxsj?0D{JinX@>5!w?#p2 z{3%inG0i_`2sHS+l8DeLXs`)1-w%3eONKYh#D0!CRQ~pjJ{{?XaBocWV!jvXQR;qop9B^J;sd$Oq{t90>j#rhO;Qyn}Njm2UF z0x_-o50$mZY}_~@Tr#l{;nJDJ(wCX;TAa^6d?_;jiXKp;sX%v7ygxjs?kCdydMkwe z$xGN-WE}b)^0Yhp+S{DxlEvtK&!*qz(kYX!VSA2PPc*-swtndt%+IPhDTh@G9hFF> zJ}_?4PL?cvK-FSX5-GUnD%U87#uRVc>4?}KM8&Al+)t?U!@o(HCz>twa7isvrf!8Z+&DCyE_?0>kAKFB&V(MDb<>r#2u{Va zYqFhSQ{9Uw%`j5NB**k>KgxLd%3iOd&&0e-xgt3z)f)#P%oU-g48hw2ge=tL)BAo1 zRcF4_oXj8rB|3PfJDoiWD!buVuH08rC!kXKv&Hl2J^u2|4&{gT3Q+_H-?-uW&KeIV zJD-k+jwBrWb!Ik!@YpGxs+q-`7Rv)gD{E^L5I`i#`%g z7Ftd{RCG-3Vj^E}t)DOvJr#*8c^ov8GfWo!uZ`NhEmV;tj@2P`!yv)D^Igb=RT4L0-cP18Bf@(2KxjFw?X!~Cu6*eT z9s4u6#1EM-$(B;LQChNTj4niYW}Y))Qpf#*XilqZQhnc)4QA^RcC;iHx=7;LGDYUd zBh>ZgH{w*G-kCDEJ2Ftvw)ia6;4a*hWxk)Th9`=p!w_?{HHaH^<%R-XsCFu+J6L>B zhw%T$Xx1*ptRK2u%S5XeP*}lO!U3(V)whV_Jb?KU^wRN7IDbxbt7ie z)y=`IE>@JdRKnC)ewFm<6r!ZZz_7VnAk)sjw zC-mciNzxKsl?lYc#CY{Q0%0%)Y5VBpLlCF_sA`pBPgnRSQ^}&>bp}hf%cuhyfeF=x z=A%Ws^fZ3FqztoYH!i*m!ldlfJ^LMNfJy4Y-8n85h3@t7Ez*@wU;;P(P7k3Wdmh>| z%ke+ZNEE#2vAgY1Nk+e*S(VH4GN_@bk*ehviaCFR)@ita$3%{v8Sa_^)Cr zJ;DKD75ritR3;4{tzKyDRX%A*;23236jS}2O%Trj|H@~yKIC;-8HlBa0c(x$QymH$@dr(Me*_^sBC{=x&1W35p{INsiq5~t}+-+ zmk0$EsO%6~!dTz%y03>Q-C;%bSv6IVZpZGtxKX~nzeJetAM{jrlY3Ex{z^lby?8T~ zT3j(r{MF*!h^>>U&3iHCpc~}sLojh#hz<3hl_%&%|BMGVf?(Cb;A^>WfH2Muc6Cb8 zUJ3`^go;;%7W8ZGI02DaoHRHh(VDON&H9d_QiXk!v1~Bvq1avGe6u)Dys-Z{BVqFgNTbnuwtNJ>r^DJ#0MF@jg01S4R<{9itd#nEnu|)P2 zTJ;H=-GcNy-kV|(nW|F^mqP_=2yh`0$Oy_qt1s0wClqt!>~4sKZbbT~oZ8+~5~k>2 zab+>a8gJu9?MJR0$<4T0+O_F;uVdJrWZf>5XlS$Vlx-xtfZGaEP#|N0}?6ydFdA;7++R9FH#RQ?)lamuOGqbqN%qVa| zQ@p8F!4MG5van!mus0uxFE{d|-jlw-O9ULst&y}g-67gTf492l)#soPAhF@Z@ zZ)^;XjL@^NVC0PL;Qk);QUHmIg^Za#?(wxLtbTAEKf=sY*g}~ zr77&KVOO4_|1WO^QVz2XmOf&dc$F|1aY%p(aZE&>HK%o7KY)`VIHCK89vEx#Z!>;> z$Ny@)?}aXBbIE))#`x0v+kuBQ+&~2g#5WAWIxoWVnSqhYe&Jgs?j?3Y&HIKvsRONQ zO%X=x=|OM?+Q%5eeqv=hJMvIgzQ!@TTp3%dNo>hv%MlMS9q{DYey?dVym`gwp zI=8`meJPvD2an z$kS-fC}iduGD!DdjlBsEA6?nQH|ee&)gwowOh+hR5Gt6ivX1&F(tW!6y@!Pm-(<|q z$B%^DUq?#%K)6E)yp@pFKb98-LRT= zonotmJ&x7T3Q99L1+DZQW+4ytBh8c=j}@)RUjIaRs8$vj{;jU@#+L>A3!!x_eu+8K zWT>(eF#9d$LDj`8A;cq+B8)K{6gu^}%7-5c z&l>&uHgm1Yjan0lsOC6wpb(j|rOChR3zwmqC*t z$5+g=4HMP8Y?$5+h)^EiiKTUtB@vHe+XTX{Px!0RTI7Q;Y>!ip;+dbXz;Bz{KtbuEfuprEdT18K_cO1q+ep87p}_zCE7e5jWp24M zbq)$CUx1*Va19J;EM)7ultFyOhdnGkerZ2ss>35!FQ}Qw8GbI&3BRmEBaj|sC4QFp+{FZvJlOG9 zm)VQI+uyptTs!7jQ3yZ&`=VY7{p!v%?KI@9=yQO8Q?BbbqdN)j0nKih&F|N^j1?eO zd-hA?jc2=`Y+GU=$PhRK-l5iX0H;z4C%KA~1bt=&hp_v{~3XY}A8wG_m z5bE0kvJ+uWHpl7YCZL#rdyLL!j6Tshq8bD8?BCzHM9tE76NO68E2V5Su%3?uE~kFF z|42(;rp64nNP^gm2q)X|;M+Igs-10UUYTwE9K)B=25PoKGm!m}qYp5p2?Iu)mq5s2 zN?VchHKKnKXg$vVu5>=84GHU04Cpb4LPhGAgi_q@$=4rP#@uOd*R)T`EBkh}ce?zh z+z0eSzM*Clch}fw0JGjp_e1?)h}}v@76>KQ0`~wgmZ<_Y6v)ARs$$;{3wjMqG^kHW zdqIL}7-Y*m018)HDCD?`Ah7H~2SjmZAj4Dfhd6Nr_M>v;S-FIUB?&v_*kvo_LAyl{u=yWZV>K)xaP{xA_GH!9Zvh1hRkd9|W*s=@JlZp#U(B4ln#AP_4+u zknf3Vfov?`l5#Cs)P$W75U`2iep`o%SH@c_u&yD_!<~M*|rJ)fBI7+bo<{ zf@>Gy9w5+L56^+QhX-8fy9)h5a8xExQvLg=pt1h_L4cupVd=QwdKBR+SU@EYzz1?pGF}TJ)V)F)mXh=RMH#gYlABGmwGj1%x& z;l+W_UP6)e@84#g3-*l)ohvU8zhq}~YCxfzN-x^!3zM8yy8`gykWsPYz|Bwe|2tn_ zngksJ5T%0n#om;hA#fYw#ev&Yt9J<8MFmCH+-z)04;PJ{$r1TKuC6?E-o0at4dFCs zk(W_7Q&*2)kh9EkO|hG+Da9ev~4KZcQ8lKRk+=x3_on9muREfKP=|f2Q*#`g;Qe!3>}kcC+B=aT937 z%z+}4p8A_=me=f#ew|f33fO}bwQZ-{ZUV0s19b+U?RF8DiiU-mnY{Qih_WBP0$E5M zMV2iP1Er@n@>qKUjs{+8UMTvyRsIyXD~lJ3fTd`NMk+*J9EXg*Ot%HFee#*e;pR3r zFcdRM$-qn@_X?p9K#{xcc!G1u=xA!1<@g40AgDTl`=W3rF?oCy$lQIg2|n*ae%53S zB9|`^b+om$>HPj~v9q!!_>T+@mbg9MosFfIl$6xu=XbXk!C-%9DsygyAm~33|x~@HiutyvKJhaVGha=p+%|UJ zt02|1wdpIen1iLUgh3_+PMKWP`$}EN{o(E!FYc)AEC!5fv-6Ikc*n!dKA1l*TK{sF zSf}5C5m*!k6DKvjsM!aXt_;lGU(}f(5)J$91(I{DKo7M4@BmJkL9Lup9E8PYDve$g zCV^yJXA+P-odWtH!GE#ZG&G|DAn-MSP?2LO146&%8923#(X?gcwO0tXb~dI{V0O_W_Q#5AC8kmHqdiM9tMt0H;D!W+piTyn=bXw@(*zW(1U;DL@zD@&<6{npmx6 zW&61f94`)+GQfiNy=-0K^1gCJ#UYo6Lh(R}Loxmy_ymk|XC5#Qe8~PDgvR5ts|F_J zMPlK0?U-l=5LFVfFf`aF#>FA1NhP;mtw#=yj-uvjRT?6|leP3rOtl^-@3r_~T9wNt z(_^rvf0IfEnx&=#F$lov1FW2IzZpD1EGQ@lPyqs&JWvbZ==iiO3YYiQg2xfCpZC~G*7`9zY5~Z>ikp>O3FKw3eGXts8O{`RCKmR1 zsiUKVP0AYv=mSc>yN>{=fR}ur2VDn&&mGV?XgN5dfj8lkuQd~~*xbjrQ@k_FAN8ZG zBi2*uZD%+e|AI83z)yI{6C^Y56Ze6CblP`dh?A4E;({EXQ8^bh+|BLn7L?44jQx3G zprDkovH8O_E$FZ;4EN`OQ)lMjFbDPHupQ`9z~{$mbZ>S>vkZ{#OJ1rZ!rd64t$ze% z^mjU=?7Pql1xZPy=H}*#r!d=@3YVkhd!_?yC6=WpN}8HZ&dvtwN0WtEU=jBnUAFQ! zfWv9B?Q5S>vSIf)-ZJtZ?(OaI&3glo2VqEXV<=>;&`5I~of22|p+ zRx)5!?FSCc+nyBsaOrW^cG)bbA|M=kK^%C4bOjBYc(q7LRn=mx`}6c4e94#K5TNBI zytlUA;}X--(IK(6w$=xUd*F;kUTE+H9-G*bXJEw)0KLdVsjnDmLSiC-U;+b?4nfrS z(dP1aXH$d}s-a;}(qXd#O3g5XO;_@+L;LwV@XqG75VFNX`s7`aPok%Lb z%>k1g0kld2A|gsYBQWi)r(uL((rtl^Z$JD&Z4=ye^X^~_xpD_E8uBbU9p2Zt?sc3U zSMP%%{|=WHl9`pK%s{xhOureA2BjzT*$&9mTCVqo&o-!l4h{+m94J88+}t!KM#Umx zFP5OMUg4_wbayhKpsegtc?s??0C`S!!+D`z$UM8D9nsT=6(iS8Y6csrQ zP`JO<_=K44bD^U(Z8x49Fu?fDEA<%YD{4WF0um@iabFO3pLQEVXa=jFj&u;<ru6tf&a94gxeRIoJDOl!AO_(7P~x03)LUpe_WKK#HCk@k>eyW6<~9w)=|(25JEN zoapYead5akg>UCv+%e&M2T%|9NSEZIh}HKk;7Yu|q5vMjivwhp(BCr-?J993r@jcn z!}?j&9^g-0a&l-|Lv37~t-aWtkkU`#3;dwMfxvn^z~wZ6To7Ov zd&})YBqSsiM?+w9rT=76X+kFt4cG3c>N*k!oeDmP+q!kr01(sa!9Xjvb%FiqH z8e_l?j$B5)X){3yFj%h9aYX=dg6I3;x>c|X8~`?F2OuVs0tyS9(xMK)De{8;1M~`K z54T6A#Heu4%@{~8>g?tcfLd;5z6gMoP4*FUkvO4Xt#@3lW5ESi&;)1BYw%NWy>h$P zIjx}JoC?@H=@PMP`&aD$`!gL0IN&0Iu(k%?uEhssKJRreZ()pEKSn z#i~2`I2g(XyE*7S{n1LN7#y|j>+3uGkc9@GZR02IQ)3nv_1{$*8kA1>fr{u2h`vRz zTEWxu@ha}`5;+SCi-Y`9NJWGPn9iRb1Sy3*SWTAt!MGz@{Vw}5Ljqn1)^u0b%s(w) xdqyez-{&ANu$SeS!7^WkKVeb|lRMn`LAze4*ksY#fWIwarlz_$<1s?y>Rph?0* z;1`&0^3swJ2oP)#ZynnGH^4ja4l+8<5D+NF@2?L(MaryzH(_056{TQT5DAbFu*1`* zmw~t7T%@#JBeq^i9KMeMOQ_EKcx^U>|f5P2a zo)AfJe*e~s+d=*l+n5K{G8m0$jL^p?34sN%HFCdEByqKnLSb zc*#)@;XLDv41xPxuQR?WkB$3Iu#mVMoyrnpyefIHs75)YKdQJ!pLu9Oh0%3VijsCN zxXG&~+~z5y)Ji9CmXk?d+qXk`Ng>EzRXZ!Kk)J?Ee{tYiP(y6BF0tO0V6}P1m8hR} z+LcIgZkkguAyrjdm&I5^OUwD!ho=V95uOThH_~j+qw+Q1r&Ad^c$gsQATgwV%d%EX zOk-Qu;5`pwGOOObHyC}bl=`lIr2aSkNV5{17CWL+D^+D>f7HU9jGy^%)|Hfui>?Wv zPeRi4eJg0F5C|3FTv`q06ZltGSE%AfxG<$wg_YdzJJK-1%b*m0uUZxQs-JY}p50`< zsQUG*93?s#eoXVVF1<)9c~y06m3I>AAHhN7j~_-%6WiPQ1A9qFOwm`Hbzxb`hre@( zdqB0II?|nJN1N$zGchsg)F0u_7Usjriy2p{XoHTw>0pD7>CH;s-&j>yl+&Q=1FlwC zIk+T0|9vlDLrV<|3?yg#2y9Z?2*4IuiwSJTbhNkkZ9YWCv2mEhXxl4=BL(`54*D8W!0BI$CjUInl;)`M^<3)7 za2oM3$L^4Ior`Jb&_ybiN55$XJWuES@a&42pSy2!k3Br$SMJGWkp5K%6^Jz6z>!Dy z{8JfEx}~vxIla7&j-z;P>O-f3oWXLYr`Fup7qu*M78Jpn+ZrglF}9 zV>7#5Rn_v2RH^st11bT!+(I5TJNWvX;~xK1hKDAr(lB-H>7Xh%c$knuAhjqdE0wsf zDPkC!jHkEeF1X>2w*QuL)>(xEC$hHr4I!1gKT^+Tap$--fC?&ug)qx_068?C(SF9# z0g}`uo;ajk>J4&arFilvIfHMb{va33TBx9k9#2U)vQ6E)c8d=!o^Qcl;=tra=P1it z>)s{bgFeD+#{XuJ^DX6u|F8yqmQF5++b2pr&xU$X3HEh}{?4oQu) z*>p7;OBq@Jgpnvh@8!S`A zx5Otai?E1eETk-EGt-Rjz;;KC7G)o?|Gc8d-R{q@8GCu~4Iclw&@l7(K4!M%=V?N< zQfp@`@4!cUo+Rvtzhcwhcbj%b|2aqn(tx>~$H-D{7&&<_4?n-!tH34=I+Nj*MSeAZ zDWs7ii@|tJfS`f6gaHxTI`sY|Z{G#$aU41h*+dbDr1*~wCsOAM7i5FMJah3*ZALD1 z9|Mr^s$x0>yGbYZNT_?GO~c98`)VyQl!y(*deD>N6ZjUcj5Hd?TuF4EJqm*JGx)lx zdRbtW*INCEyAW1KRq|XsybTiNH)6#&MVxIaLgrvsp6&7=J#p~heqH`u{T!C{mh(5E zMi4HPhi#eEO5y9;)qsOqGA9A`XC{Y+|YdcXXccKaOM76`(Dl>cM@KoZrYoybX8i|;_j}SKuW+mea zA$W)c(i_~EC~bB0E({3AQOV~Oq>2N(I$&|vn}TH^HuTiTGR-Ty_$Y;HzaErVgXbE& zRB>n9{?6lX=-@#v_n`?T>U9_@ec#5G-gu)LJN}BRY3%gECg9jF{Bc}=vyUOlHc*p8 zV}^c`P{p5StCXjEYrWygI(v$7K1eN zma}`iXjs{PMj~gtq{BVdvy9P)e^;K1y&}Vq89rkqW*HdvZGx*UjC8_}~qxcPrmvfX1E+c5c5*ztw>U^fVg{Ed9GSrG9}4S zn6dvJDC2?9mkgJyz>pHZy74l(;3hp0wS4WJr&Ro;jxv1rGI|c9WbT!^8iejQB+Kz)L*opAW zM0lk5)2)9q2K2ZZLQs;{GcH}zG4ix!R@~ZN{mCt@$Tus&0~icCbIIRs^bOfKmMFr{C|A6lhNY)|K3 z;j8=yYt&O*O62VIC$j&wbTC=Qut9S>-GJ*km*~9F!3s)Y2p*WjNQe8S>9+H-5(IqM z;n^_N`96&rGu7Qmoc#3U|crM`2u5J@9_)5IQJf#g<_zNtMSj1 zk5udVx_+s}k^iox$I3@}a1--X-1DlEPVO&w^!}Mc%5{jfo@m6u$hRV(>yez!BwYVDR-fIuG0k>%quLH7L@H({pCL2^Fs-)o@&T zEQ`;F1@|j?`f63f27|eees=hollv5AhdM429oKT#cjzGcz9rE3V2mruYI^%5fqW)e z4N|mGsLKTDp~J@(Z#?VUNb8eDhQQ=RyN}f1n1vZAw=5@oJuw4TwYZmSeRG<%@vde? zBDbR{)rLR-_(W%CNzZj#AUR zEf^}4%uAeIgk2Rkf_K9RW=GO^elA+8A)>@A=Kd|M6r2w@hI3;yE%yfg{0r(Vb~UAothk< zJp4d=*6ZaO(|Movy&c?f6D=2-dX&xGPB4U0WeP?}d%fqe;0iB~`hq3rVL%;iVmlsM zJaF!ZzMaTc(%I7(P-&n$(q1f9^pT4}^Lm=)vEF%V_GC)83h8q9BmY`Q{NGk+Q-n{l z2x-N8RjUk6R{hFF7oo1J82SDY@QQE$z4sBOf>9%?c94;vubWn-|6CNr^P@l?mf zx`G&W^71oin{%futmjCfX^>bCzT)miw8fxKLe2f1JK}^08V$tlvvbCkkBH~)NVOQu z=1Kd_&L+EAC#($Do5&w^pCvDYeYcg$la|n|PaOw8SN02Xh*Yla5+xM)H=nJzp{gf34wfaI%(X!H$7xZG@Fbor zbD1>czQnn9F6s?@>!RW6wL@ht)$;JY^FDx)&tfnBA(}^9oMbCE<61tQ`LLag$_3sH zu()2%#O5UXNzY*3rZs|IO(TC}z&PVs`NL|b+X#bJaI(gYJJ}ff>JL(;C*;~b?eCcT&i9Q zltilHq{K+Z*g08`iq9SD&*PEceeRM`kBokc>SGqXHcM>5sV<{e0*|h{a`Hr^{se~; zBlDb-l#rhUz)7^?7GkybiU&`liu%k-G;ZwL>5HGJ?TOV>sdc+k6ph(_S!#-$(rl^S zKqf7Y`W)&IG~>;BEsASYf;-3a+ZT#mWe=1Uk!+txwxPZDs}}S?xV9RUYA#boIy3Gl z#0Zmu-`Ti1IHvjpoRN=UJMdA%0YRG`~B(eHaU^G*DL z)AoEi$3@6l8_eT}y|u-X$Y|w2yCCSx?En|lfq4OLo2asfOmMVDWBV9KXzClY1{@rU^F*i@o`|FV*wm0sC3rocQb&?i|&xRx+rJmcrEaM@?2Y z|K7sZ-KZnlmQJmqfR`wb$KPjy>}44>1iH9i3mqRpSj9|f*9M@_ZTfwlKhC!dn@;@+ zJ|2%tkgsh6C4YK^2762XC+14k95tw{>r4cmD z8{-<=Y| z&I1Kd>34GwxzN?OnQ;@KopBz0xG+>ssEhkHYi*Uh-K!v&#`Siv<5AM)Ro@kyWD)9Z z`px6wi8|W$g|Wuk5ADIJ3`rA=rF-Xf1ED_ z!zCswsbh?w2#!0JG3_%=QA!k~q%0AAS)VcSGd^QdijL;;37`s8;(R8Kj}b^ko`vA$ z{ms!?qcssVv?N=&qF+=iC`Gcu(kPp48*G-@V+Gs|yE7Xre5MP)V)gI~)x-Y^M%%>< zWJ66)DF0LiN^)SPNH}Uxd-wimXtlDepfZ|wvdlxpQi;h-9}wxWA}hg>kWEwgMZxYI zllh$$N_pX9;LO6Yz_cptZ4)YU@a*V<11!oco8`~YROnqTQCWCx_@DYx0l9zkKZL07 zezdALUicoSp-G0BEM5^3gs^xuxGZpalIHq+5)g88i2Y4Hj#klX#9H1qT+=jmK4zTJ zyzs+9wNaT|FKGQrJ1@DjZY@9*&0QAq@N=jO%YLN`npFd>N#DRxL3yUix`G@z{}{e4 zHV8*~rODN)kV~>xPJD--+)fDgHbND1LFa4Q{tFf|<`3IX>#VET(e_sXo*CEkpip%L zxk~o?88+@QXkxykf(a7m=cT%vqxq`H`_PiAC37a)FZCX1)|GX8+tE zj&oOvB8TVnl8TLt$@0Vej8nfd*!))XG3I>|t_X!89Q@?l;b%>ruN>$ya9HATmX+eH z!=(x{Na3mC#k|f{HQm85y@Edv<}%ksr?RKu#9L@5G$X;4b9mdF(F(j`luMd-Hu4Cf zEwA3zF#p_2Quw5$D6iW@yvnmsnF5GCyv?W{zK3KzR551jFz@hbyJ6*(S;p|paV*kR z`CF2`eJLYbe$2A#Rp|PsLrf~MVkRV`z4u&1o#XgSK<$Fq!V(t0xz&?sDnUcVp%m%nZ-@{?MSmdTu->#hOF>WEd^@w6;(`S zEBd*d#!_?*D3U#-OucAaxnH6!?Zq?5!ZBThucr@vTzD0r*(v|%bY*OfS2lP~l>W5p z8d5gcpU=pkc_1#$v8*g|+xsw}4NxD-{S_JA^WE#U5^@4U3U0B;!nwI0kNRccQmBIF zickD*n(&Z<1v?(-T9J3(!C}ev7%jIsJ%|5Zeh_!?<)*#cGu3IPJPWHa5QYO#(SCLx z$5f43uQXig03&Bf&KO49I_-L?r9Ao&7w#v&3g80Oav`vYEwJU>N=L+4T&uquLl;G4 z@(PjJn6E#S<()BK5kmm0=>N+pS`sF0c%!9#mR1fZ%4!hVFU6jl7^h|zU}SR*bi<=> zE+YPuX)`nBQDP+rOWU^s^Or|kiDRoDk8fl{vO{Fm@uTU|==J^0n5_H`jpx!v;Z6*x zI*aJoBAX&cKP#%Zso{HQ&JeheJa0B5Qd|7OcG{PkV~*S1K^;U0bQyb;t97zeYNS-{ zRz~>Lp;o3nQ*H)gZaa~uQVMd5B|nEil0_=w1*id{q*-R_R5$D6CWUedogNBeWYDoY z@e0(kWpjW2{Hb-?lhdp*P_(sWs?$aqt8eb`iXGY+&)^6{5qXY(e)e`-Hp3Bqdv@Nw z1i1mDG8B_ zjy;>ZF_ZgNzAK1r`fc$di^KJ8u?`FjSZ03q^%aiYvAL$)>rt39-RSh_{I?yYuBFA{ zv@3JBY(WAzh`9VNw1Anoy%Q&5{&2NN=y_0pgv$y+M-^&pg!@_G>PS0m6<=YF5UlxNXUI6XzvRgnm0u z35Atf>A7!(AQAGSt~Or^vQ$#0IiE8oOp>1ZBlST?^U}kAf<@UcjAH)w%l|`qsVph# zFg==~t!>ogcS9x^$PeNL$_sI3nWZai(r60{n{E1w6XqI!Sd+`#jYir8Zp4Y-4{95;N6Y)6=0BUX7m zU+~?Co$m3j@ZnK=ZkL~-5dB*U;KeqZfaw$U60Zd9vpPr&*{b#064k!&g(!)BnO9phTm>z^%=Uv^#8Z`+9#I9RAA0M7KZ zqB$Wca!0R4RWS`CrM=2oU@KqBB+8z#YxwqvQzUP#()(ZAPS*4S09}6rA(GOsaTcHW_ADZxCXqJW=Ktl%wfC2dADNC0>lL# zh}nF~@zXLke-}A5aH^cg6;1UN(6Kjg)e0HxAtG-NQ%J;u>9R@Gb?4nrv(wZ5tN3c> znbf^T@eaHSyoA)42xc0Iem%{$COX)MfUiO>MxP=M*GpjE*_-oYV+6Q6JOMXG97At5 zpQpP4&WS3y)zm+!>`#D^{EQ-yac)2Bnxkdl5puaR)ZljdbyDzJVUiC_=%PPVU&UCE zQJO(TpI4Fn{lkLt}fx}tRoIskI1VlC%g7TYGk1sWr)5l zhmK`Qvv2R%CYeHo<*&^+*tBRcw*F&NQAY=_Wy6!fWH7p*yqvDG0;zvE(?3Cm(i+BY z-0BRYV24-ddOA-`krG{o&g1E{rR}W!_-9QGxN+`_36PVRI5-wqy3$R1Sw?d6@6ahW zJnDDSO>#7^Ww>`#S+jpVB?4~UiQNfAWX|vG%r<3@IBwU`*n6t**4V4ek1f?5GT*~U zvnPKnPEnw)v&|}zPjh;ExxZequ3S8_G7N$hJilQ^21$h9zTq0}>a< zz@3c4d>kk&yPg*}w@sfP#mUPhlIC{%;d2b@KMtFd0N*?s<0NRNUd|dHB@xFkxTc;QpG~ zBT70wS$Fo7#Q(YUO}TJbLzl=$*LOI0hgb087nI=Lk_m5Ye~OmDJIK&7G1=n0{(Wk8 zQk0a^1GsLpqhE?h$Ng!`CcE#m`-bN+(>n~8e#w!mG3cuArdOO}MKXahoFDX-d4t#4 zdz^Nf*W}N!!+AB*!A8Vk-UX_{;`R9<8lNjt*RCzZ_vO)S>>w+A^=g(Fklm3#zY5r% z;Sc&f?~p_j@E|X`^1VG@w*l~LWoKuH1AX)q`8?3!+bS-t`p;fNL7IIcM|M0%N}+5{ zhOQ0BO^<(DVINwrCk6P9eiqf6O?=XvU@B78eZOwB!ch$k`7~y;(PX;ubhaR1`5Yeh z(m1~Vq5W2e3pyqbULw^a)6#H7(pu7^oYo;sKaan+Ku;Q>1RnEDUAv!m-KPut@JC@Y zZ7-R`?QKV~m`~@60nLJ!aB?u9`~ns*4OZhHz!?6ZW5L&dJI{A5J!X5ODS8gQAD^GW zB2)YeFAg6K%U!UcS9DtYQV*WnjIm!6bJxs-A|xS)9Djm9`-f$D`vqadg%uT%^2}|) zhA&52GIXmUA~tl18t)tdPa|b>Yv+;|gIeKl-4JmR(AEUbJClI2&v4=I5<|6=9`dwH z?0VZn(qlf{H}4#F9ezSAx0uSEz|ztaGclnUBKCSu%F@=>*7aRckc7bmB|T78qv|Pd zKNhNV^jv17#&ZPHf{^*cN0MmNfYv2QJoP%LrO~#Zr=%%Dsb?&|k))*eI-9GvnF{u( z((gzWdEP?lyj{?}Ip53!J_A%tx=pq=E#_#VJ#hSlRDcSRBvd_1rz=pm?PprI0zCx) z!FQ+w&O&>P5!g&Rt#{3`SeI-4`l6lFPJz||iUNC_E>JwSU8|L2a#&364ieZ?CdfEk zjnFs)Ap`0p{dUBa+baLhe~w-t@pKHKRsD4UO`5Pb@FU`qZOeKqIM)m4kIOb?zD`1$ zS}9q`<|f7;M9jm^Y#@7iFa-E_fkP`QDk@}hM!ffpTp_QkGLe@EfMlD?R+`k7Bls*WxM5-sL-5Q^|$Aj0GZ#XYyBH_$f4Ss(B;u+OU6 zLCyg7Ige1K#eXSldpxS-+X}|0Uq1REoszAc_&H-k zxzg9z9c5kJF(5$y=JH-8)0p*J{{|xR9u_C9@a+neR;zrM2bKWkB)n}*V^a9ZhS=}f zkvstoMfe{nfTAOA%A1GTS^8u=dIb#%`KuBZC((^gJl(@w7(n@7?@#O`$}8nCD_v>z4c!Uw*s zx$nfhzmii^otDkg9B*c%Ez}U82qtt=IusXT(bv~ARz~$@atP*-Vz2)<3(&B~i(J!r zh45qD6~}rJv+9LGnoBvtI>{M;<*mMe66I1fnat+PcHfThez{#FbXzjk zbDrRmBu_X2alOuF#@N>FRYLV7ozjlxbEIS*ua4@k0F;A^i%TI-I48}(GXkXF;k0Vo zQU@d+RO7YnLve|l+J_+0k z8-eA}9+!X|>{#`jo-+t2xt>otthl@WP{{cJuIV(GU$*qTd?JH>dAgs1$#N5HLEzOu zunwcDUYq3dy0ZcGym}hAZ+#q$#_J!zF&qy^qp-BGvB3e=)+yxE_dJxshUkTP59xaa zqk03ReGMq3PT)dMy6!jLiS(fiSL=m!<}Ek2wPn1oM3!B91VFrfUyco( zUmw60i$-qEGJ5kZ_lUW2DBfFAlzSWf^-{d^hcR}A#kWs5&EEh(qkM;U;m#1@dU`~66h z=iN6WO`@o;Pb5v~2+gzMZsIn&uBAb+itH;52l-M5le8wf4q%wDBUO^DUB@YRuHUN% zAOhZV_vyUn4Q7in=QQDGtw%v0e)DhIX)%rY`FS>gO@9%(RR@YE9e@oCnlIya_?gF* zR}xsy4k44Ni?kX|9=Qo`JokHHLjPC{`H0;A2Dlu~hSYRFM$+WE2Leax5Ft`LPT~!od3zI%KNB)HLxV!@$xKP zn5)eX>*u~hQ+}`4Zt~XNL+$pjQsfD49+^%Na$JvZIT5YpB|@+GlR&6qHec^rfX9n% z;M_5)G`&P37yi{IX(1jdZC(AK`?Lv0z1DaJP;20O^6CFrPa&{t4R|+*mX^|U=uXD9 zn(9cELr9d~Q$RRDhP1@=orwU;>J4BB5{Yp3F{mRJjpDOSytIL0G7zvM^%Q%^FF;+F ztCgt&Ncv9ymg-E{kb>0dQ#3Su7sl2|l|}T(c>LPFFX`s3jK(Zqab$I0jWWCg6mDp5 zjo*tk^_T2)#rvenW~aRBK=LY*YB71bh=Ff}e$SVkFL$f&CYCg@M{s?8{mEcS+iI`9 zf(BjQ&_cvo$e%XqZ+6MvfYg6qc|dW$vx)b8fxc{>*P>)Zq$qhCjb;3z6h7)_zsmhi z3dT96iVF$?00l05ygIC3yT%6RVsbpkU%?Yntz>J<80XEOV-E0R!TU95fY5&8S^KpW zM)E~PMMcxrR`E#$7z+yU)@TQ*0R^O?r9S{PkAuY;k|b)yZ<{{%wm6{p$_no=8;eg` zyi($@@Oe<*{4MQigsaNS$b`t83-Iea14jZH761 zyEP>7{YO@o?S#Of1=>9*ircTK1d-z+x#2@+c33M8EmL5DYELVUW04>9wj%+o_j`MJ z-ed7A0I&~;ffGPvEi6KKQkT`6pro|3Vyc2s|12(E4^zZB3}9&igG0aGLnKR+BWY`E z>t>~R=5`^hvuEd$kAKjtq(>w>7PmV3$&VE&2&lTC_n@k&^_Q3Bj)veE$N=qe<#eTJ z4KlKp+q&omYp&zGd+dD)fJA*?qe%cy74Fa0f!Hl?+R$VC%oClw*Zujq&4(7WW~8>+ zl0sn9?s@y2*!Q1tuE#zeu*m(zvPr!A=_U}|>Fz2)8RS)OqlQ~y4DU1#U4ESNv1ZBjq$>2^l|6fuLt0vTYpqzN*tZYSDy-4FX! zN@^j^w0(~?>M0^lfQggLF(sJvF0%mDWZ_Ifl{8|LfAGT=wx)3YN0k`!qU5-SZmOAz zme!EH-;=KJ;{k=|-N{I!)!dQ?IA^sDnV)}G!%HQ&X(VCR-WyO|Bt360=T_gpZ+Bi# z?xgiR0}c*gL4By-LdXH)?kM0MGO-xS50mv+yvI;emgk!pM(i05DBO2EU$)2t6t0e& zCLhmv20I#-ag()C+b6|2PJm1Iv-my8zw09yB-~#>ga160D^$mL>;)U(HXc`8YeL6F znl(_+Ab166c(buoM<78jMriU_-7k#Z^=2qCQD%<*Su0{9Ig^yOs4n%i3;%>PiO+99 zl)Ve&bd%UE6u*BGZu76D%|V+>HdD=(=#}iY22CjfN$FY3oGEf-!A9@t|9DUYvF3SP z{ode#aWYfc%7(QUfy^vwd0=vHWDVs9=j#t^y3`UZ%ZDWQA9_vQ$+ z)OX?X-otbjCC(5cPVru07>RzYrK*1W2W_T<9lO&gU%ur*ysD9e)@DkTFuSv$YrhvO zP>O~S(h5kIWk9iq5A8HIHbU5LA%q(5jzcULs{;4kp(EN%#zktS%bnaYa}ZaJ?mY4x z;2YyvJSc$MbL!~T0Ks1oo|$gW-_tcZ`pP%4w=``_0r6cO(n?TL!DW=DErVOcoB0jg8UEiwMPS0S~AR z-vs8jsT(gG;=c$;)xWF^)V2TyVXT)7yxfpP-CcMVq)UP-uI&q z3U@kFMP+^w8Fs8*yT?}uz~LwF3|0S+!~>>7>_GvzfIu1$lYIa&>wE zv;!K_>F2=$Tx-}Hyd~sIcR>P=)2Y6frBP}EgCgP?WY|Slz??oU#C9rJh&}mr(W$U= zL6p5}vgF^_{Bct%Wfj|bwfN5Di5}C}X7+03BN79x4b$&=yyW-s%_Z~y+V}sN^Zf6i zC&0x2-<;=v2R;8c=Lt-O{%7y4CsDUM13R@vj_&)AkTp`M7*YRt?{fve>I}h`YncpL zIx=WdsYxqt*GUqGE|N+OnE=ew2GQpAoqgt>@mwu8&n}`mlhMfr7rTUOyAzM7YEZQt zrT7Xo6gnLjMf{ZUomj6)}8iwzNz8N6Ttx|f~hQ6~?xVF{Ty|Aa!nIB1~`?N-nlr0Kky5S@OY zd7|$+#?8sM76W_4xO`m-7262n){$%TVL2I;!xG0FOd0!Q0&OZ8t0a;N@Dz{78s`iJ zetnA`OFXF%fm#)~rbV{YU>6O(?TsOcwZT|o*m65BF0^$AL5wo}pK*>D!mlFI#J>gQ zfjH+e7k_fl?uat_oaaQ*^|>i(%JF;rhfS zsKXEOu(-*LpjKh7tRl9o3~$XRhBU`v1=v2BX0f9+#~$_(eyDZ1~n_}=9^3z+TrijMK8?@ufNy~|hlVDmaJNMqans?E@}Xu0RZZxrCNUUYGmosP z&*Y9d3LmZ7MM+n>?ZM6$BG&7Vhyevr**DmbWR$tExiq@x@}{meH@|M#_X9x>RH^5+Ig9SYD0NEs)>6i$NX{3dvY-P`xB@Hx-;R1V0*V@V(2Cldv@*pC z;$lU%)*OO1tG&0RMn_AriekS3pLrT{&QUL|aSxsIlOP=%XH=xB7<-AzYB2^9ZpNQk zgUkx{58@`a)uE*smhGXxNKaF*)+!yRgMtwtEycH6kyB665b&2`!^(A&)8O=!ib{pA zz4UQxHRIx2W93)m)O3y#aL|fvV_aP!m0;k~SFq=9-)`-o&{4&=m$)_gNAkOg_XhCC zH03olvVsc>#+eysDVA}Is1}e!Z#_{u1qrqOQhh5Sr;9|C-%$9~WG@pU;V0mVQ{&3it$u+SX&eM2at3ZW z>~A3i6>AKywZXFnNk$=2+U;k8q*+Z9zbs?!>L3w(n@+?Wf03g=MKC+lZFYmBt2^NY zB^mt_71OEvqEF#nr!5hVS`a@YJG3S|AxcLj(=m_Cp_`&AB+jxGHvKjcpc z)oNt(&5UF3j5_qjwe{ubTe}usE3tP&pGjW~o#uS-g<#yaU=-ecYTb7ty$9(y?Wl>F zr;^sKdU0evAEJI3TsC0!W)zrceAY*XDcgvHNdH6_75Q$vc7IATxmII`>70a4bUoT zV5cP=cx=W`Mb^j?PbL4rCVSoua`j+s0@Fx~b1vR1f7+pJ{Ru}{Y2N$f*%Ob&1>Wc` z){$%}GR_I;!)lP-zNxn)#6~rC@Bd`G=6lRX7o6I9rRk=|=f?HgHQM(%y;_R0 zy|vTD4!P2y&TZrA>X6oMrXN^WYOS)z`%Ol%QDi-DS)6=xu0PCz;+aEZKmxs`QjMJ$ zLzcPh;b7*Ley}+An^O=n^pim()P4A6-%CnxAiniN=}%i3Hg6*`im=bi&S1yb6c$v@^I<+$KQ#9amr}%O_cN4zX;E6S0%O2Et#U(*Bz`JM(4A(y6#dvaq*vBBIi?-!~u zF$+~9POxRU9w*%#q8U4ELXZfS&)9Baizj$rFpgdwyMrT`lhZO8b+9e&a3*ff(9CPl zq)r;LB2bwGU)q~*(NdGq7iFEc3+P_Gb@^OBSrPlWVR5+vm=Wc$^Z|>}Q|Q-09#7o` zL6+90<3Qseg}armL?!S-gL{UlVoU&Pz^7qHAALln z7mAStSWQ(;^zG9)e9Q2UA5a*Gc0%Ft)AIBu`|Q7UzK98>p4P8kcuxwoWzTf15Z-2C zeqw{#;{3~RoF6Z{Re@_M3ZiSn@{&eug{;<(+cBDzXFx=Y!W2!XGlh zO1X63lzUL$ylYZZDs^$!So#crKQh=UB^42(P!v2PUE_x|XZx`JaS9S|s38arZL3!V z69WMu*P|`-agbBs9MkM(*y${X@b86TnU&p4ovNd+uS{Ui#&QmpHM-#CGkt|MW4p!? zp|b-Ok6xzqF9oN5DC9w8R23XlYd$j@`%n}TadPe;=gCJr4-XGLkq1a-p|>eGGWX~5 zV;w`S2`L)yzL+GXku%)qD95Mk$72q@{qz@)ml&mg&av>?Iy!yZN}$r(TGmyNpYI7h zGX4{8i{dL4;Mj`GjrcyDjdVXnZB7W-T;-OGTN;-1rzY$zLvzRK{d7+BiSF&l^U9x0^DA@)t$9Ox*)Ch(Dtc@S z@*6|^UN&(DqiKu0q@wK?)8lW2TH4)@Za)3rkE4o=4j$sifOVjDlVIcGeG`}^EiZAWGu zr+3xD^>gx_iAIf0oegGtOc44_wp3{oh{|RCY|9R_KOB=Cy4g-HVmf=ML|l@|Ap7ko z?Y8x=0q1fC2j8j%lN_(C4K>ie+;LlKll@o?=LDCAM62KMY$XY;|El%kUfyH(5PBq7 zYFO`=mi?)Gjhzb2-4}MR?xOs1e(~~zfx+YH5NgT(M~;9w@6tl-Rt-A&4mivG z(#rc`PyA~lH&eOt0?Mujve1>mPHH)rv}AWrYjekb%5TIm?9;A=N!-z&9v8`&;3pK`6N5e<8P?v=X1V9EyFkeLym7t0W-_L2*?yT8=_@& zQ2Mr_2k9A)B6U=QO@{c6F=Loh$fyI2QsXeZNcnr#FP)7A&U_^9TVqI+vE74EIYJOl2BUzm4EmK&tT8cbM@b#AT9&< zxzGItpqagZ9?*_`4K%zKG8AWaC$(@+i+@cTx0(SV%+vryhXMSWi$5r`)54O8 zV{@h+7QF}FbBf06yWmY@$X?@v2l2D65KE_qB=6Vsa&)vQ-}@_F$5)}AU>Pwq(E7M^ z9t(_7@1m@MFONT*3mM^s%mU&*ORV#5z{76H`bqTIpu_zRm-mAr-&P=cv5RaYw8z%1 zD-xczq*?jI>^$>U_y`k96%{6X`mG1rR;PG8`XW-DsYfYc=$Y{mF}DErM%FESSX|8j zBGN#_Y2J%qPSs9y(&LAVd95hmko-4+mx51|7D$A(;W=R_E5GElYmLxo^wLGg1adM+ zy2g892k2;r%UT-)2dtk|bX(Brs45q!-7PjY!bKZhe_>Z&lfzh2!_}mye?rA)U;bS_ zq!lm2Kc1T=zIf%huHJlHU5Vj+>&o#IU6Eh>r?k7Yx~uacA&jBjD1B=@SosUm((ySJ zd@uv*XW>?u_6#Nd)7fduy6*CD(PQX1)<}o7=tQKClrr{78xvsjLAE{(OHX0Ah*F#x zOeF|ZcnhaDScwP0t12k=0BKAaboe%;lf3dD$JHT1)lcvB`l>tdF4qU4>ZBkp7qmz+?ZR=Dj!DQX2ZoyW#7@@RcF zB2Vz%4XUJ4!j@vw;!9sp{Rh)26C%l*P{rj?6BIg0NrM+JIZrK`1V=`{V>F5v!;xPA z_dmguF^I+4IqqZqQSdHXg7<{|XWkbTvA}wA*>e!Sh=9%S`|yq>-^)8z119g0b=YoZtQlbessfLk%=L&Ep5F1WpH1Rz08OMt`-o zjHx-|R3vT8=58wwfu4op>CN|5fh?WuQ2KP_AWApQ@dPSVSM33v@|DiQh;tk=hPQX? zSi63YT39wG3MRGk$Z(@?Y$iQsoblMi8~8)WF@0lXEds^K;e3{DKpP^)YwEPUCH6&x zEN?v&c82TI#m{m@W1UIt^{Fcn8b-2Sc{Ls4N!rkIC;`5o6Dm0*SXs(M1ji1A{T(sT z3bJuB(^TjS@v2uhLNEM$S~w6c>l2cPU{c#GJao)Li8Z8R#!s9SG|+u<`e-r*EujXD z=n8fo1Ep|?gi{cTQ-lk0v&*c=33QSxPRsU6L3B82^~dq3ArD`V|Hd0F3|SjXq6v@} zC$}b(HOiFD@DwPUE7#f9xH>=u$KHyh6rdMJp$6`!PuQX`8@o1TktNyK;8;I(g z;5HHlqOE+k@Doz9f9duyL_LL*d%egoDQH5WP9*ZB(NC;1C+?vcg%88Jdl;-i?H^jK#-RI??) zhmv2{jE}C?+n1~KhX!U>X*inBL;aOH?W)_Yi51(QV?_#C1ep12toy^S=C`?`H=Lni zNm%`@OM9XW+%adr4lWgaejS3QdoeQ-`OQ{@=iMOqCGSkd+{gT43tZ6bfFu}Chcwa4 z`q7GK$?h<23A5K`=4Xmx_L?l4w=xF};X;hkJ~!Ll8rB~koWCj%OVC!7sME3~EqaHI zZ%2c{BZ+$%v!94&vqIHHzOMEDUhn;#)5}@nRd-!0d?MJ$5TJbXH19I>YvtUzwsZzF ziF1H`OvUr|Oeu*gyj7sJNf0VrcO9=um*nx5PU3p+MsrK*?76!*?OTn$e!B*~H#^T@ z&19v?>(z$#iV@>-P-h@HCbuN3-eg|x*BI15eK)+(9Ia;MEybFrL z|0(ck`ThR_7{im%(m_+a5YLvc?*`onM$mAtom zfAkxLa){AcdwM;gyJwjN6nXze`?FE-7{-*Mk-~UhwE>)EV5>hG)zPPk@*!E`wjXTw zYl0+ng+feEMl0DL#8781Bou7Esugv)-h`6;eF`j4i2^Uid~kReSP!m?Dv(hK@TA(l z#%X`v&BJkAuFzZk@`zIFl#V)ttRh!yO$#$0^d~1cLGkZm>wZI2;-oQ>3ML7rDP=Kb zn*7R?U7Qd`62`}|$?NS>jJ9hKiAnU<=orhlIeEWtb1A0HpJn@Ptcx{~Alulize zQo?x~{q?Bzzviaw266izz&VN~=RazLhRjdoVm{9zGfe$q6zMBdr*~NG>OWc1F`1Zm zZawLXPt@v749a%w!)@{D!dO++b6Wp$lFd(weYlHcN-|0}5LH9zb8?e}=zXI8(Ky6o zfDF{WbdmR?8UiTlLckB!c5uT#yv1iP?BSC0@{EpExc%P-3B!OZuC}@M()kRIgxqo8C{a}0 z|Mr!6ww38Cw;oK4&by&bJU!VPZ z2dm*SR(0Fp0%x2o zx#`B`#Bq%4b@8o#@%h)EJe)YO?ZhcQVUFRWfADjZjMzT35Of!J{O~X;)q6Pg#Dxr3 zLK<<;)RA6DVqDg}J_;DVa#<%AoD*^0HInt4x>e{&%%$NQw1&JVx#^bISkSHc{KZGI@sVym z{<)p3U$4@s`umgWNWZ&2s2Lp#86AtkD1@}|!!9oR(k34GuH+*hF7S(gKcrQDWHjK9 zzws8|f6yXV@-T6OAWEE$jX<>A=t?@g{HCOD{U|Gz=ehfvIX-n=jjjE%UsdD_tKj=<1BR+StoY>ftiAg~eED-luD!I}a>{S*4_JNuc7FLr8P8J? zX=)QtZ$yMqyD$X^MXhca8VP9lZRVU17-KOu9x^%}PaT+~X1oT&m4F}=2nmtV3|B%b zW2r+^YQ}4Vp-M;)PHR1(nD7(JFN_pIdOZ08eCNI`tiI$>ZoOe1x89Jn!iKl&oc8;V z^N-&+ma{%GpP$|9P&$5Fa=+1t%DCJ=NsbP*-KpmDM*ms64|wj;JiqDabRaeS?#T~u z{%+#K6}odc}v3t6_f zN^G_vlt=#{1g4WOUjH@&l^nH3@z=Mqecv~uqob@ib{PX5|AWk*II~a1#7Hy3iX+Dv zufc|GbBIaeoZ_%TSl`MgPSYHHgpFe*)0n4T40&U75jWQb!otKp9qtgv99Cz;7K_2r zc?ji#2{7RR3+4`T{4o|msvYD=mv)^$}v_i^2oCtNGS>Z z0rJ*PKJihH#S4;Y!@E?$1zbjMsN2J zU2_EQ>?$)*DIkQz!~vRqjy*D9e8OYH)-o|(vOOsnX4#>`lna_Sx0V^NSvYEtBNqoW zq5^%JiWt-2hy}yUDZ<8WB`URI`hJEus?uE=V97#5-&-AwO_ab)FfqEBGfy!bzdVOh zl6STne)eq4u3^F4!YC`xEHO5bW9<_bo;}Kui=h#h>DyR9X+5)(On|@bCKgz?@R>_4 zqqn!0rAwF6+uKW5R~H=}9cZoh>+NsxUk9qt@ zE74l>%r9Qznddih@(GLit>64GgM&j{bj5%0^u|xkxXQTqHKsV7IZj`p6G?`Mcmls( zAqqo;?!fViNTGp%C>WzLQGuYAT(5Izw}j<%Byn2+#wYNrM3KO<^C)elgStVOkSMI- z`ysZSLs?F`XcJ@NI=){6EF3ovLZ^G;1HxdOAdIk`4wSSo@dS-V9U*OOC)uVJ1~q~( zNb1!t7@0;>goHtjFl?Z74$Dqr!c1JFQ6DC*k0+L%mB;pqC~XsmRT{M_NDnt(KoHVs z_z2}<+f8@O%z71$J;}o00?s@CGaP>S;VfIWj3rB!(ACvNp-{lGtbHY{CTWG3I7qEv z8-z;TOvye!6V-_0Mv_@US_qXxNC$yVVmnNnMt=tgVWn{;ue(s~-0sGhDuI!NiJb%hW&h@%=N_CZLbbP>`+NE?GnGa-Z!`$;^Qv=XIhW<;3<6;c#`x*;YY0WJaq>Lq zR4zUytPz_=l5s-12<0UfTT~~G1BA4Z(!mfV_rO9bYtIV{vz)L@G{(}oKjqT%0?z)} z3OeVM>EAiPLyxWFdp{Ix9XktQ9rB)5qbFB%r*l>_RG~e2pG@_Bax08!g&?G>#0X3( zXE7}b-J}^kn%PCfv~*1~9Z&j4TX;-c+;uClM_XBDIM&JnGCjT1l=4v;lp!YHrI~7{ z?frLiBkd&%?!FM#K28Y}p$TCjlr{CZ0E|RP7o>wuU(%4KKS=}Ml5Q!}&g@KS=tKuB zP#U4yB`kJxUrpBICHHI!CoLz*za<-;Jb(K&QcWoZ zWXO;qBdiP=GGv66Awz}?8DV9}kRd}xSQ#>8$OtP#h71`p!pe{#Lxzm7GGxe*5mtr_ z88T#ql_5ih4AZdNCo}f2(Ogd}l}YYkK{KUE`U$HK?vUyv2@p)yWV2cw0i=fzNd{CY zWm}TMX@u2OSVNFzgUPgGwv&}eKdhj&ZYjtIiM)gm#8CsbeSzAx)nuEy4+ey&WryQ= z9**OnwQdntQ!iVD)w5tJ!P{pO_ScD`m?(nSWZX4dVM|Hzh~?yRIUL8qvaD7*>H|$$ zLI}bzWX+m2tXj2-!NEax?b^l2$OzSH72o$UnPSd5SeC`SdGjcj%jENUT-QyqjJD~c z7DAwu;`ZBbW5HF z@8ga;jzxdoQl-=+VKpr*+qNkb3dC`YWmy!9Me6lBVHjr8oQ2Av zlxitQu~?*7EVc+sv^nGtr1ILfO-D!aaS_*bDVNIxK|mBm2aHTJy#HvnrBVv5HIC!p zd0uM}onm1%3p$R2F$Sd+p6B8FerqJlMzh&RSWOe$wrxDm!}F5QK(!Or2VsiPBrnhN z2*a>NRvBieUP>v^T4PyO^3jQutXjV~j!hiL#Bod%MTBA4`elBC*^Q=hG)K0XR%H&v sXxsjGQ#YHxjI0h0(rOw~?f(P(A3{o5aR~k2r~m)}07*qoM6N<$g73sZdH?_b literal 0 HcmV?d00001 diff --git a/docs/tree-shot1.png b/docs/tree-shot1.png new file mode 100644 index 0000000000000000000000000000000000000000..3bbeae1c583cbbf411c5c2fb0ef823db9c63fff7 GIT binary patch literal 3665 zcmeHKX*e6$8jehQC8(v8nOeuzTHD&HNQ>H4DUG$Gv{OqAT5CxVnIY5=ZBZ3k%UFX% z5w(l-Dp6ZBF-n70#9C|Y;->dL_y7HIfA-Hg=lRZezUMse_nz-PU#im$>yrX!1ONcQ zNn0BWX8?dNleg{#19_uuv_d0q`XSuh_7)fno|ti*1^|R1wiaf$9(k`!jyzOu66<+5 z5?LBuYI;D9qC9Ngs{7}p4$?tUwLy;y)IU;-yOIQ*F2tp z#@Q2HyKf3oX+_>pK|3ZhtI99G)ZgV2uJuQMg+hW~MR)q#gKd9zjZ&M?(E(hKx$=Pp zKxH8i?i##2;MC|pd;1CnqUTi+_`!{mrhg$(itHI^7e67MR8Jcya$R2nv#NWG)I4>4 zbgW2Y<@If1iLe(N6ZjA3FmMuiLwkQQUENrHIn9SPqI!1ZurBnBk}W%9e6)l!G7vx( z&X|4A_5^Lw8*LsxgS5pb!(xL}_zw~^3^*$8D9@T$>3+3!t#+qnH|0V?E!d@^M&y&Y z?A1fhSuOB9zNs+wk6eO$3tP_UGUbwNUJv^GP=M615Q+28_477;IpL6be0b{ml)~ED z?rv_58!dB+VNOIY+qEi+U{!igh$K6ZJ@02zw9}KRgHP{=AAO`VCWR?>IvhVS_B4WrUP@2w zOwI;|LiO}5R}L}MQj^kTFRtn&Zu8>y65g+X0~7C0eyHUxT{}>!Xf;6p^?E^Gr>n5T zz4JD`-gsE3mvi0WluJCcTnYiNjLoHXDyM}NfPMFn!2Ly}ALj2mt`J@>Z`V9{$Xmi9s?rH@y2h2T*is{@A9c>TbYwkq_Xjn6IsSf~arQg@{(4 zQ;P>HT*wEI&v*kN3MkVo0QvFL&lmuZ!rPot$^7p29gzPYLy##(8`q|hKr9x^4t@PY zqBftnt5o_4kZ5i?3KB$mJZXC+;ba^`GMekGKzd;$sey)oY5iptzP8#zH>f zMwikI{fWRMNMg40Urm-FVQ-Y>$K8tw%#i%N9meKOtzR1*g}o3VGX4uIym(FbG&cH2 zC++UW7C(?A6bDSSQazqrb9r9|cE$(yt~T2dna{Ro20b$@2jmaRE~3>0oh*`LM;1$P zBWUX1#z{vlAY^jnFE5L0AQA(feP)vAGOOxq5zN(bFFcH$xk~+y9v<)zI1;in#G&N5 z^-spvu6HRFSv~Rsdf=$}*}_3pu+gGG&4sz2R?y2h$cKm88Fw&unZ1V?AZRtLr!Z@=TU1adCOZV@m58G=G%CBDO z7`r)mS(FbQS$MaT4cojry)%_?ya^SFWXOu|ZekseleNe#Urx1tls&b(!LpCTW96?h zdRiNHg_gAY7oEYAXZge#aSV=X2xn!YDT_^^P^?H|uhLfn)vOI;hFvntBb?2Lrbd@w z-u?9K@R)-(4hx@9!^(=tp9$!j`#c6(x*K7@{70g4ie)`XXD~L)gqo(c3J+Fu#ECui z)Ut#h1V_FfISE>tm%XsY+P%L>GD$gMuEv+3xSn~3$L1V)miM*npBDeBg}vL!I*0YdC*nWGMf@)E3CrYL z4L?>?Y23;JJubdq=~+2M&1KFJ8lZ@f-=+}nVgulhKkkWS+sD|Mlk$n%bI9@o2%|u9 zyN3XKTM>;_cH9eM4eM>A|5$GHyB zW3g_1n;&jvQw-?pS}IS}#TjOJxi=(;1|YS+5u*97SVy~bCO}P3x}HYW^&gvLngpx{ z=XBqa(1HqS>*M!+9t-7v?yD}HvzavxZ#4o1;jr{}P)>`B_s-i2(xs;IeE3jV-9l7P zc3#V<-W6GSf$LArbB%7uL5zKJ#Y}y!09C8^;EGD1eUR{#!WYE-8s8IqAXOz(qN+wp zgs5quBg%&}?rsK1?BhMMJ^@8pkm$U)f_t2StEj1u4iFUd8g0YR$2$&oeU*NiS5f}t z)sJcg8t=pvJ~Z9`-uH0dvHRbN@~vYq^su8f^8=?6_SfZVXM~@LUGQ~q6$rKop*CPUKEpJ%Vn){&t1^&!pfdBvi literal 0 HcmV?d00001 diff --git a/docs/tree-shot2.png b/docs/tree-shot2.png new file mode 100644 index 0000000000000000000000000000000000000000..f00326487a6eaa249d50d2f3438e601585f21c8f GIT binary patch literal 3383 zcmeH~`#03<9>>ksU|d2&$R?6YLyWV#xSMa22#pb$T*BBTA)*jUOv18+3L|$lnD&gz zxD%Sei5Mkm*e+!p%@%{g9)}EbzWobl?eoJ~t6!eySv$f4Kupczxng2+UmfjEo{#lgp6-s+ zZQUnH9C-ylAu%a6;b9$i#0^n&Pvc%mPbx93H#vb}ma>NgNXsmRbgp!lqvY(@>(Bcf zRkODKYkZRVuF>dY!me;j3Ij(dwn>tbACA=Y1vtGkwIO1Y6Yhos#=CMu}Zd^-VCEuEwf;x?V z;w9jY?WdJaydT$qTwuAz3_`)Q>N6`Es(_flQ4g+q=EF~mvp%of)dFnzw1W|J z07nuCg~fMPR#swiz5iUwrDm_m82r+8WUZm3>FK!tD4+9^Tvn@}-&$9B9W#~R{6H;@ zQduw#H_>^g?0G4M4Afs9^Xo5m2)2)_H|zL*Z9l%lsG`X`^~3HIEfZ=CTfcG?V`G@0 z#r!?Th}`H=*q+!)Gst~)L8?9nr>e9zU?f`n9Gz9%4wskilp@4c`PfXS;6vDycfY<} z9GD%7U?{_5gr^#gyLo_N#L(xe9p}QAMA%TcHkH$ z5Rw>!3N zJ4$2sWyIK(r`sOWT(}_bjvn~- z-64}_U}o8{nKWrha~r+WIf!mK@Qc(EP8Kjb9P!uv=x^mw4B<{{vsT^vA$KaR^V$=_ z0!7)4cRxB=)YoIxL;_UU@M$SkFrIfV=d?hsxm8``ZJy=|2ST9pc_sge_zHKWuz17p5zk!p@ zsLGfvrib#VYqRNvx|d z(sA#L7y$l40!}b1!2)o5Sw!{7zrOF)eg|2^WLVmwm& zon8dfUr?5~Lpi(2A5KKqt#97YO;DC!sbdO{<;_d5Gro<+ck}eusq{>ao;{?y_M|SB zw}gY{VYak@8Wj0?&6L&?O+MN?Ek~C_YDaK@APlnhzoLoUDOd01L)*EmX4y^&g6-tz z4C%zFv}9w|GV$knsVxNh|C%S+m@Z-eF#YQ5>-@hHDd@zz=B<5qew4zFyPU}V_e`Q_ zU1#~Y9+(7XfV*?uN{I^ez=JojBr25}FI-)%KX4421X7Z|GSr`*Jp^*s0GvVf{QP{= zXh%m!$xh4A1PZUk+vjp3-s^LqRq&}gX4N=7!5HI^{5K7Vc%5wBjT<(=z)!U8qVe)EI2iA!mcxIVdGpVKxa_1xpfl$#k!D^9Z`)?LL zl?B`V^mwe-ooGd?iKu@ZEbCmWbMc#hUEs`p$hu9;3wpun_1&y!v6eBOt1i`eD<|bs zN74PTL7Comy3ri)+j2!mjnt?HS$;PDX!X8&E(bF6I@u=5igKElc0zL_BWLYu_WoPusrjv$eS922yN^GIVGIg_JC!pHp9KAvfw z6nP#MM5o+xupzBWd=d{Wu;K{_YMi-%xJkUtQ4Kk2Nu))<%Kc>Ya*>LPBkrvJUNwkn zk0Q8)X6R~;Nw`=eKUofw__si-Bx){rgnNHqhngNp#6mon#ntNp@ZVshpIbjO@ptzG xsY5IAdtgu41QL2*hQJJ}C;y3n0XZ^4nI4R-I#1$@2lNvXb3E;0SBoXy`wy4GwW0t3 literal 0 HcmV?d00001 diff --git a/docs/tree-shot3.png b/docs/tree-shot3.png new file mode 100644 index 0000000000000000000000000000000000000000..fe4c11e156584f48ce8c74a11d7a3d41c6249594 GIT binary patch literal 4001 zcmeHKYcv%479R`|Y4i}KkhjVEIf-O2293vHknuPnCeoP^hRCBx&cSGg(x5Qj97p0H zjm*hoG}Oqu@yHBE26;^++-cn}_tRbXe(%eF@3q%nd;QmM{r3Lto$ic)9XfdOAOHY3 z1h=zx1poxH`F@Y6ApeC&E4<(vA)FQ5O;l8LV#aA2066Rgx4z^Sh=bvrkMS z)_%#zBSgYQy4UkigHaBT{CcXxI~(lUypIpxfDDRa|2ncv-T!7WvfekfYea2|k%i|4 z)>8`_Sx4qhaC~JOg@Xvvl?NC9cuk}0(W-tcF(P6w#ocm^YeIXI-XiOWuQZ|yZh;^^ zy(ql(b#+cM?{led*v@^h`xu?XG*K&9#;meoopcT6#D^@2Ig9YiP7}ScrPp4(ekNOm z9>Xnf*Adu3APC|qmUC<&{Jfh8POqt~yeZI}`Fwo(oy1Tb7cw%Ge}GnX;mxGv*1XKq z%E(Nh&|mJw8~&cg<1$A0iN0ympf-&Bu8PT(xW>1`crfq7%bk<#QFL+x;8ggzvk`H! z+|bt&72T<+4KHAQVFmrBkq?TtW}`i~RV%sv+kPudzw=U!H;fwfikQ?ObyAusHpW?T zB-^~YLC8RGL8@R=XKbQ%=-snz;+__?lJ|R7{6e8-#;@xu88L4L30G|%^&ty(7*uk{ zg29D^g&QWc_i0ismLfO<;}XU{wd;H2f;+ZwwTWaoH7E<0jYk$fXG$-4V3Q=~py9TQ zmCwAD#6#BK_D_|q@zF=4Dd#n>*EiQn5wp;tr~%qlg#PSn=aU}pB#Kswf#lj#YcCJZ8Vj3sdo>jz6ia{j5anWu$6$>SAmFIy%N=Tc7G!=ql4KkceT{? zYaA@$S&Cxhj(RKXM1@Ae6`HL-oKMOytLYA%FB5mca42Z{#1{VH>dc8P*5q0SJu0_r zZz=6~lAKw_Dm%S5fZCyBp3e0hxOd{qX5Y$@B;~&e`;v>DdRn3Tb8}^^g!p)tNIE$Y zfF%R4`%=$29rfQ7oqWB<9v%zDx*ES#NtDC&LdHjeJfowdE7`OeUDud_e8SQ~PLOHr zyZD?Ptm~zsJ;y()Tj|w9pT6@3htrB?n(Q-H4dgbTE_)&ML;RFreb%bWg;3N#j|n%oUaocq!Bnimhgrc1;;~lj8vpANYMaRTw{cz{BjH z{A<;bysela?H-Ot$|%t|Y^~v<(?z@@jB*@%TR?e(wHo4y$JBPAAQ`zO&iMVt4351l z_ttM*XId6Et8z`^)`yY&*)(#GZ7pUR6Qy%V0cHb&v|6~nU&;Jtk{JvRtyNp6f6ei7 z=UBMww!bK9WO-zUt>gp-x|kWlkx%xVy~dHPI69RHqkAQYL6o8FY|l&}^yBFl0jC#Q zTF8uwmNk%FGkModJn*JE9QAVOYRDC=-j14<%%M?9}+JIK@8kx zTeD@73|3rsPkR{2GaT}xIRTnBYxVx9rKk6{n|lXkQ+P#dR%g>GhbMs zs@Y>UnUtf$otHvqc(CQ&S$A)ISXD!#YgVMjW4FS352=JV%HnwKkC}2L;tzYqH8Zvo zlRj^ECXea2qe3TmReFb3vZEYsh_798SYb0CHIz`&UNfJw^L&a9_tS{NbUbEp%$}#M z0WZXwVeQU6Pw8uin6knifd+r*;_yB=wYiFL8pMi}7tHiy?%YOwLZ8z>sp2JF zFtgY;Wu27pPVK;~p=u6vH0_h$oTcQOMjchLzUkfaXW)5XtlZ=JR?M~cfFz9NDNma% zYhd=Q_0#4HBDC=b%f5Gn!-obQ&)Z0%1p9NZF61rOsXI#m6+lk_smHJQwWe6A3(9z? zLqbIa{}l80{Bx6HCVKa5V!*wehrvISenRlSv!G*ebg3XT8ihh#^M(XL{gHx>o=-lh zK!6JMssgGtMxojRo7}PI9C%N?bW+i%f;)ZY`FlyMs)bhx**}3$feScmJUGlAHDJ)XDJuqU?1DZC6{MIZ%IV+ju2%w!WKP+Uj z52>`NJq6;!Ayjd_)G&gM^RU-o*=uLN_8~Gt3@}9DMttym1X3!=(k4-?Zx&YED1wuZ z>#Ta&kvvwohV;b1MUWr$BfXst=lkTe+s)@jg_1Vd0f&IGqH^A_V69VkzPMM->`sidp|CT;w|CdfjwZu8Gj^1TDWh+UoM_h&{4e4J zG~@;ItP@x2Z&&UX-J!0#zq^ORZBB?En68bN=*v6H6aT)qJv$Xox`iM5zL#|ead}<* z`%8_i06g+;tg+%v9By!~{ne`*Db^Q`cGO>}hK651Qo)6LkLJ%4t_$(e7I8K~;G?7S z5Tl;`G^5MVWwX8<*7X!Ifk8YmZo8=yb{W)&n&YadP5G9qK36|!9n$T4p9{|u$FE& zeCqkn*BPr&L|}=B(M_W^F`#0{9n;U=Jz*-fo9x!-C6C?RmyyDQS9ahN;hY{GC1m^= zJb!#6jm|90A&`y^GxNt*`16=^r6_$U$l&C79=A84N?6~NSPGuxFJw*ZUwBU5y5EdE zS_@xRo-Pa9qW2p`O0EKcG0KrzznB?nX_y>Fe(nFV;Ab=^Zx#YyJIW2O)jW-vCAG`M zhF!Q6F8Aw6em9oS+%85+OWKP!Uo;-O=u07WmuWhHS(<2jRgzlGQJ1+#yP~=QOWg_I zdO4oNZi=BC$<{z0><(VK-GV-2*gmNfP3$6`)-spKEmWbhzmZiBBc;{nHG-=tpUJ%m z5`C4<|DkD^e`{K4=}OP^?kYO{&r)s2_a_28-ybid4gjkJ4(ZO@@hJows=(_Dh&1;0 z_}rLwh|X(pJ_b~H%AX7m4(f?ocBlwQ^ij?o6aH~z$mAAVNeF^&!T~g_hPcfofZzi_ zDy0IH0s#6P>8SUiTe*M|6ZxI4q4~E0Kp+pOd+ukspV<9hM3Jkjt8*idS^!_+SC=df z;Hu7kS*D>h9mC`WSEgPCca9%0FSh~+8i;=6s5!nNjzbtN`OP86IjWvh%73- z)j z!)nLXKp@az5BIA#K_I1H_Ut|guqQndp_Dy!Al}8p4+4Qqaj(yUK}|h zC&{MbidDweEq)?~yFUFXQHb7w;-c{w#U^*s6P4dtH->3X?Ly2h0}9#jQe9)XGthAb zkqN>RT@hHB1?SA}Ul6l}opBI|)^<1;Efj9j2-TR%bm?~N+12@t0!f(N#}2tPynIHl z!s(b(FwBk!e_|lV;BeJ+e;lj#I3a*_mm3;4($rtHke(SHPPN+MR~(p zQqa@;Oz6Bo0ZV|$BL;j#sa`$t+MN{!!Ph3}KKkO|5Z5C6a#p9wDHh%uhP<4Ib|JRE z_**V>v5>}oN;Pk%BBVvQ0v*9Xb2WGN!x%a~gm0OgQ%W&nQ?HQC^Edr(Sv9#ok%Uft z!Qmz&8ZW-v&~>B@qBI{&zhV`-eXDJycHYBUTEER`J*qZpM!Gshv=LwW@`2nsj>80@ z1gAQr3|G=7ti|QzSw(4y(7j4?tpLwqvJCcnp zIik`M_rvNXa&wta@U!a^sQ&Gv%A`S5x3gx}-D?Ib>Hix`!na|lElin+_CVdb4d4dTCBP=6S5OsB>B-OS=Md|31)cAOj`^Q_O2$QRU>O>s==1uSN=J?Kz&AELp%Oob?qyuqO5Cl8dKTm0DqZJ z*E3k8Ckn#W;_yaDPQnQqd#AzB5^W{=4(~5FiJo0hMnjv>a#tSGN=wl*x^6fHQUc)S zqHjaxaQN+0$Qm1XxK`ljT!Mp4uof~!$8!*)~9A=v0?U%al@!zkvEoYJE@>XW)Z8Vn6J ztnU<@kN@?DjPrD6`|z-M^9HtU&@%#WS4nPr=9rJ_+57`7UY>BMdKefMZlhTvg9Sz( zlAZ?W%>4DrkG)PB$}IWx^6@FiSjlQY^o(2_5qyq<3hI7txmg~lXe>pn*hS&E5ERMbAQU8>Gs4)Ji^3#{s?_f?9PEr40 zg&KL?dQE|6ZwwxnKj$m*st5dxOE}eRGCeghhqKD<(tjA+kYmwuZd(Vr!7g>;sJ#(y zhwn$J;1v=r5{}`aw?{CEzgt)M;TN0kE|azA&lA z4Drt!2<*|h$h~YcXp=RJ35MvXLF!#n$*Nj?U~s_V+r4rwN_hXD8uzkGGduf#_tS>n zI1O!5TcemEpxRo<(&7jJtfmYOIN8qHZ)Sfo`#ZCLP5Yere*&|oIh_0F{j}|%qJd9a jDzz{70L%U_a;2KTE;t}rwMpE)=N;tX>T|Wh1)KFBuBJa2 literal 0 HcmV?d00001 From 8f4b9ddaa43ae61932fd702ad7178c2b4e07cfb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sat, 29 Jan 2011 14:32:44 -0200 Subject: [PATCH 434/867] Fix the choice of the right containers for resizing. Also fixes #311, which was an assertion failure that uncovered this problem. Thanks mseed, Merovius. --- src/click.c | 57 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/click.c b/src/click.c index b6a752ef..8617cf0b 100644 --- a/src/click.c +++ b/src/click.c @@ -329,38 +329,48 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x, border_click = %d, clicked_into = %p\n", event->event_x, event->event_y, con, event->event, border_click, clicked_into); DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width); - Con *first = NULL, *second = NULL; + char way = '\0'; + int orientation; if (clicked_into) { DLOG("BORDER top\n"); - if ((first = con_get_next(clicked_into, 'p', VERT)) != NULL) { - /* instead of setting second = clicked_into we get the container - * below the one which con_get_next returned. This way, if - * clicked_into is inside another split-con, we get the correct - * parent to work with. */ - second = TAILQ_NEXT(first, nodes); - resize_graphical_handler(first, second, VERT, event); - } + way = 'p'; + orientation = VERT; } else if (event->event_x >= 0 && event->event_x <= bsr.x && event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) { DLOG("BORDER left\n"); - second = con; - if ((first = con_get_next(con, 'p', HORIZ)) != NULL) - resize_graphical_handler(first, second, HORIZ, event); + way = 'p'; + orientation = HORIZ; } else if (event->event_x >= (con->window_rect.x + con->window_rect.width) && event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) { DLOG("BORDER right\n"); - first = con; - if ((second = con_get_next(con, 'n', HORIZ)) != NULL) - resize_graphical_handler(first, second, HORIZ, event); + way = 'n'; + orientation = HORIZ; } else if (event->event_y >= (con->window_rect.y + con->window_rect.height)) { DLOG("BORDER bottom\n"); + way = 'n'; + orientation = VERT; + } - first = con; - if ((second = con_get_next(con, 'n', VERT)) != NULL) - resize_graphical_handler(first, second, VERT, event); - } else { - /* Set first and second to NULL to trigger a replay of the event */ - first = second = NULL; + /* look for a parent container with the right orientation */ + Con *first = NULL, *second = NULL; + if (way != '\0') { + Con *resize_con = clicked_into ? clicked_into : con; + while (resize_con->type != CT_WORKSPACE && + resize_con->parent->orientation != orientation) + resize_con = resize_con->parent; + if (resize_con->type != CT_WORKSPACE && + resize_con->parent->orientation == orientation) { + first = resize_con; + second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes); + if (second == TAILQ_END(&(first->nodes_head))) { + second = NULL; + } + else if (way == 'p') { + Con *tmp = first; + first = second; + second = tmp; + } + } } if (first == NULL || second == NULL) { @@ -370,6 +380,11 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ xcb_flush(conn); return 0; } + else { + assert(first != second); + assert(first->parent == second->parent); + resize_graphical_handler(first, second, orientation, event); + } DLOG("After resize handler, rendering\n"); tree_render(); From 0238ce3c73b9a28981dc7342515f10b878dedc4a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Feb 2011 15:43:59 +0100 Subject: [PATCH 435/867] fix some compiler warnings --- include/config.h | 2 +- src/cmdparse.y | 10 +++++----- src/load_layout.c | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/config.h b/include/config.h index ccff0fc8..6ab6baee 100644 --- a/include/config.h +++ b/include/config.h @@ -35,7 +35,7 @@ struct context { char *line_copy; const char *filename; - const char *compact_error; + char *compact_error; /* These are the same as in YYLTYPE */ int first_column; diff --git a/src/cmdparse.y b/src/cmdparse.y index cea93d1a..50738f70 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -262,8 +262,8 @@ criteria: { printf("criteria: id = %s\n", $3); /* TODO: correctly parse number */ - current_match.con_id = atoi($3); - printf("id as int = %d\n", current_match.con_id); + current_match.con_id = (Con*)atoi($3); + printf("id as int = %p\n", current_match.con_id); } | TOK_ID '=' STR { @@ -360,8 +360,8 @@ focus: printf("should focus\n"); if (match_is_empty(¤t_match)) { /* TODO: better error message */ - LOG("Error: The foucs command requires you to use some criteria.\n"); - return; + LOG("Error: The focus command requires you to use some criteria.\n"); + break; } /* TODO: warning if the match contains more than one entry. does not @@ -406,7 +406,7 @@ open: { printf("opening new container\n"); Con *con = tree_open_con(NULL); - asprintf(&json_output, "{\"success\":true, \"id\":%d}", (long int)con); + asprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con); } ; diff --git a/src/load_layout.c b/src/load_layout.c index 7602d620..035b87c4 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -197,7 +197,7 @@ void tree_append_json(const char *filename) { stat != yajl_status_insufficient_data) { unsigned char * str = yajl_get_error(hand, 1, (const unsigned char*)buf, n); - fprintf(stderr, (const char *) str); + fprintf(stderr, "%s\n", (const char *) str); yajl_free_error(hand, str); } From dad7c0da00226ccaa97c1ba7a602c152fd65ccbb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Feb 2011 16:08:47 +0100 Subject: [PATCH 436/867] =?UTF-8?q?Don=E2=80=99t=20create=20a=20split=20co?= =?UTF-8?q?ntainer=20if=20no=20other=20cons=20are=20on=20a=20workspace=20(?= =?UTF-8?q?Thanks=20mseed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes #306. --- src/tree.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index ed953b34..165e1516 100644 --- a/src/tree.c +++ b/src/tree.c @@ -396,6 +396,20 @@ void tree_move(char way, orientation_t orientation) { if (TAILQ_EMPTY(&(parent->nodes_head))) break; + /* Check if there are any other cons at all. If not, there is no + * point in creating a new split con and changing workspace + * orientation. Instead, the operation is a no-op. */ + Con *child; + bool other_container = false; + TAILQ_FOREACH(child, &(parent->nodes_head), nodes) + if (child != focused) + other_container = true; + + if (!other_container) { + DLOG("No other container found, we are not creating this split container.\n"); + return; + } + /* 1: create a new split container */ Con *new = con_new(NULL); new->parent = parent; @@ -410,7 +424,6 @@ void tree_move(char way, orientation_t orientation) { /* 3: move the existing cons of this workspace below the new con */ DLOG("Moving cons\n"); - Con *child; while (!TAILQ_EMPTY(&(parent->nodes_head))) { child = TAILQ_FIRST(&(parent->nodes_head)); con_detach(child); From 9b01b1a7a64f70eaa344192b5e9fa76272414e8b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Feb 2011 16:42:59 +0100 Subject: [PATCH 437/867] Bugfix: When the container which was just closed is focused, we *do* need to focus another one (Thanks mseed) --- src/tree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index 165e1516..58c50370 100644 --- a/src/tree.c +++ b/src/tree.c @@ -177,7 +177,7 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { } if (was_mapped || con == focused) { - if (kill_window || !dont_kill_parent) { + if (kill_window || !dont_kill_parent || con == focused) { DLOG("focusing %p / %s\n", next, next->name); /* TODO: check if the container (or one of its children) was focused */ con_focus(next); From 97ab44b3d8f822fafcb5f121bcfd655dabec30ac Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Feb 2011 16:59:02 +0100 Subject: [PATCH 438/867] disable workspace-level move operations (not yet implemented) (Thanks mseed) --- src/con.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/con.c b/src/con.c index a14b95ce..5953c8c6 100644 --- a/src/con.c +++ b/src/con.c @@ -474,6 +474,11 @@ void con_toggle_fullscreen(Con *con) { * */ void con_move_to_workspace(Con *con, Con *workspace) { + if (con->type == CT_WORKSPACE) { + DLOG("Moving workspaces is not yet implemented.\n"); + return; + } + if (con_is_floating(con)) { DLOG("Using FLOATINGCON instead\n"); con = con->parent; From d855bea21544ef860b7f59110cc76159013c9ef9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Feb 2011 17:17:50 +0100 Subject: [PATCH 439/867] Bugfix: Correctly focus con when moving to another workspace (Thanks mseed) This fixes #310. --- src/con.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/con.c b/src/con.c index 5953c8c6..c04d281e 100644 --- a/src/con.c +++ b/src/con.c @@ -515,10 +515,14 @@ void con_move_to_workspace(Con *con, Con *workspace) { con->percent = 0.0; con_fix_percent(next); - /* 7: keep focus on the current workspace */ + /* 7: focus the con on the target workspace (the X focus is only updated by + * calling tree_render(), so for the "real" focus this is a no-op) */ + con_focus(con); + + /* 8: keep focus on the current workspace */ con_focus(focus_next); - /* 8: check if the parent container is empty now and close it */ + /* 9: check if the parent container is empty now and close it */ if (parent->type != CT_WORKSPACE && TAILQ_EMPTY(&(parent->nodes_head))) { DLOG("Closing empty parent container\n"); From 4d0106b00ff9782d3171f6f17120884c5b730527 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Feb 2011 17:53:15 +0100 Subject: [PATCH 440/867] =?UTF-8?q?bugfix:=20don=E2=80=99t=20crash=20when?= =?UTF-8?q?=20moving=20windows=20out=20of=20a=20floating=20con=20(Thanks?= =?UTF-8?q?=20mseed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tree.c | 80 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/src/tree.c b/src/tree.c index 58c50370..f402aa7d 100644 --- a/src/tree.c +++ b/src/tree.c @@ -378,10 +378,12 @@ void tree_next(char way, orientation_t orientation) { */ void tree_move(char way, orientation_t orientation) { /* 1: get the first parent with the same orientation */ - Con *parent = focused->parent; + Con *con = focused; + Con *parent = con->parent; Con *old_parent = parent; - if (focused->type == CT_WORKSPACE) + if (con->type == CT_WORKSPACE) return; + DLOG("con = %p / %s\n", con, con->name); bool level_changed = false; while (con_orientation(parent) != orientation) { DLOG("need to go one level further up\n"); @@ -389,7 +391,7 @@ void tree_move(char way, orientation_t orientation) { * and the orientation still does not match. In this case, we split the * workspace to have the same look & feel as in older i3 releases. */ if (parent->type == CT_WORKSPACE) { - DLOG("Arrived at workspace\n"); + DLOG("Arrived at workspace (%p / %s)\n", parent, parent->name); /* In case of moving a window out of a floating con, there might be * not a single tiling container. Makes no sense to split then, so * just use the workspace as target */ @@ -402,7 +404,7 @@ void tree_move(char way, orientation_t orientation) { Con *child; bool other_container = false; TAILQ_FOREACH(child, &(parent->nodes_head), nodes) - if (child != focused) + if (child != con) other_container = true; if (!other_container) { @@ -450,33 +452,40 @@ void tree_move(char way, orientation_t orientation) { parent = parent->parent; level_changed = true; } - Con *current = TAILQ_FIRST(&(parent->focus_head)); - assert(current != TAILQ_END(&(parent->focus_head))); - /* If we have no tiling cons (when moving a window out of a floating con to * an otherwise empty workspace for example), we just attach the window to * the workspace. */ bool fix_percent = false; if (TAILQ_EMPTY(&(parent->nodes_head))) { - con_detach(focused); - con_fix_percent(focused->parent); - focused->parent = parent; + con_detach(con); + con_fix_percent(con->parent); + con->parent = parent; fix_percent = true; - TAILQ_INSERT_HEAD(&(parent->nodes_head), focused, nodes); - TAILQ_INSERT_HEAD(&(parent->focus_head), focused, focused); + TAILQ_INSERT_HEAD(&(parent->nodes_head), con, nodes); + TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused); } else { + Con *current = NULL, *loop; + /* Get the first tiling container in focus stack */ + TAILQ_FOREACH(loop, &(parent->focus_head), focused) { + if (loop->type == CT_FLOATING_CON) + continue; + current = loop; + break; + } + assert(current != TAILQ_END(&(parent->focus_head))); + /* 2: chose next (or previous) */ Con *next = current; if (way == 'n') { LOG("i would insert it after %p / %s\n", next, next->name); /* Have a look at the next container: If there is no next container or - * if it is a leaf node, we move the focused one left to it. However, + * if it is a leaf node, we move the con one left to it. However, * for split containers, we descend into it. */ next = TAILQ_NEXT(next, nodes); if (next == TAILQ_END(&(next->parent->nodes_head))) { - if (focused == current) + if (con == current) return; next = current; } else { @@ -488,23 +497,25 @@ void tree_move(char way, orientation_t orientation) { } } - con_detach(focused); - if (focused->parent != next->parent) { - con_fix_percent(focused->parent); - focused->parent = next->parent; + con_detach(con); + if (con->parent != next->parent) { + con_fix_percent(con->parent); + con->parent = next->parent; fix_percent = true; } - TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, focused, nodes); - TAILQ_INSERT_HEAD(&(next->parent->focus_head), focused, focused); + TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, con, nodes); + TAILQ_INSERT_HEAD(&(next->parent->focus_head), con, focused); /* TODO: don’t influence focus handling? */ } else { LOG("i would insert it before %p / %s\n", current, current->name); bool gone_down = false; next = TAILQ_PREV(next, nodes_head, nodes); if (next == TAILQ_END(&(next->parent->nodes_head))) { - if (focused == current) + if (con == current) { + DLOG("Cannot move, no other container in that direction\n"); return; + } next = current; } else { if (level_changed && con_is_leaf(next)) { @@ -518,10 +529,13 @@ void tree_move(char way, orientation_t orientation) { } } - con_detach(focused); - if (focused->parent != next->parent) { - con_fix_percent(focused->parent); - focused->parent = next->parent; + DLOG("detaching con = %p / %s, next = %p / %s\n", + con, con->name, next, next->name); + con_detach(con); + if (con->parent != next->parent) { + DLOG("different parents. new parent = %p / %s\n", next->parent, next->parent->name); + con_fix_percent(con->parent); + con->parent = next->parent; fix_percent = true; } @@ -530,28 +544,28 @@ void tree_move(char way, orientation_t orientation) { * This is to keep the user experience clear, since the before/after * only signifies the direction of the movement on top-level */ if (gone_down) - TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, focused, nodes); - else TAILQ_INSERT_BEFORE(next, focused, nodes); - TAILQ_INSERT_HEAD(&(next->parent->focus_head), focused, focused); + TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, con, nodes); + else TAILQ_INSERT_BEFORE(next, con, nodes); + TAILQ_INSERT_HEAD(&(next->parent->focus_head), con, focused); /* TODO: don’t influence focus handling? */ } } /* fix the percentages in the container we moved to */ if (fix_percent) { - int children = con_num_children(focused->parent); + int children = con_num_children(con->parent); if (children == 1) { - focused->percent = 1.0; + con->percent = 1.0; } else { - focused->percent = 1.0 / (children - 1); - con_fix_percent(focused->parent); + con->percent = 1.0 / (children - 1); + con_fix_percent(con->parent); } } /* We need to call con_focus() to fix the focus stack "above" the container * we just inserted the focused container into (otherwise, the parent * container(s) would still point to the old container(s)). */ - con_focus(focused); + con_focus(con); if (con_num_children(old_parent) == 0) { DLOG("Old container empty after moving. Let's close it\n"); From ed7bee72bdfa80589e24671886dcc4c0458bcbeb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Feb 2011 18:18:07 +0100 Subject: [PATCH 441/867] tests: extend testcase to check for the last commit --- testcases/t/47-regress-floatingmove.t | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/testcases/t/47-regress-floatingmove.t b/testcases/t/47-regress-floatingmove.t index 189d2808..946276db 100644 --- a/testcases/t/47-regress-floatingmove.t +++ b/testcases/t/47-regress-floatingmove.t @@ -6,7 +6,7 @@ # use X11::XCB qw(:all); use Time::HiRes qw(sleep); -use i3test tests => 2; +use i3test tests => 3; BEGIN { use_ok('X11::XCB::Window'); @@ -21,6 +21,8 @@ my $left = open_standard_window($x); sleep 0.25; my $mid = open_standard_window($x); sleep 0.25; +my $right = open_standard_window($x); +sleep 0.25; # go to workspace level cmd 'level up'; @@ -35,3 +37,10 @@ cmd 'move before v'; sleep 0.25; does_i3_live; + +# move another con outside +cmd '[id="' . $mid->id . '"] focus'; +cmd 'move before v'; +sleep 0.25; + +does_i3_live; From 44ab15abf05deda00cd452d01589faed40055d45 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Feb 2011 18:18:25 +0100 Subject: [PATCH 442/867] tests: add testcase for the focus when moving floating con to other ws problem --- testcases/t/48-regress-floatingmovews.t | 39 +++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 testcases/t/48-regress-floatingmovews.t diff --git a/testcases/t/48-regress-floatingmovews.t b/testcases/t/48-regress-floatingmovews.t new file mode 100644 index 00000000..deebecd9 --- /dev/null +++ b/testcases/t/48-regress-floatingmovews.t @@ -0,0 +1,39 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Regression test for correct focus behaviour when moving a floating con to +# another workspace. +# +use X11::XCB qw(:all); +use Time::HiRes qw(sleep); +use i3test tests => 2; + +BEGIN { + use_ok('X11::XCB::Window'); +} + +my $x = X11::XCB::Connection->new; + +my $tmp = get_unused_workspace; +cmd "workspace $tmp"; + +# open a tiling window on the first workspace +open_standard_window($x); +sleep 0.25; +my $first = get_focused($tmp); + +# on a different ws, open a floating window +my $otmp = get_unused_workspace; +cmd "workspace $otmp"; +open_standard_window($x); +sleep 0.25; +my $float = get_focused($otmp); +cmd 'mode toggle'; +sleep 0.25; + +# move the floating con to first workspace +cmd "move workspace $tmp"; +sleep 0.25; + +# switch to the first ws and check focus +is(get_focused($tmp), $float, 'floating client correctly focused'); From 07381ccb7b4a8f5eb28c7ead86aed54f8e45d615 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 2 Feb 2011 17:56:29 +0100 Subject: [PATCH 443/867] fix a rendering problem for split cons inside tabbed cons (Thanks julien) This fixes #280. --- src/x.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/x.c b/src/x.c index f99dbcfc..0e310a52 100644 --- a/src/x.c +++ b/src/x.c @@ -233,12 +233,14 @@ void x_window_kill(xcb_window_t window) { void x_draw_decoration(Con *con) { /* This code needs to run for: * • leaf containers - * • non-leaf containers which are in a stacking container + * • non-leaf containers which are in a stacked/tabbed container * * It does not need to run for: * • floating containers (they don’t have a decoration) */ - if ((!con_is_leaf(con) && con->parent->layout != L_STACKED) || + if ((!con_is_leaf(con) && + con->parent->layout != L_STACKED && + con->parent->layout != L_TABBED) || con->type == CT_FLOATING_CON) return; DLOG("decoration should be rendered for con %p\n", con); From 9cf48f17bbd1ada665d11891e5f388b6ce9e2136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sat, 5 Feb 2011 14:23:02 -0200 Subject: [PATCH 444/867] Fix libxcursor -> xcb cursors fallback. --- src/xcursor.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/xcursor.c b/src/xcursor.c index cd80aa68..ee77d0c1 100644 --- a/src/xcursor.c +++ b/src/xcursor.c @@ -17,17 +17,17 @@ static const int xcb_cursors[XCURSOR_CURSOR_MAX] = { XCB_CURSOR_SB_V_DOUBLE_ARROW }; -static Cursor load_cursor(const char *name, int font) { +static Cursor load_cursor(const char *name) { Cursor c = XcursorLibraryLoadCursor(xlibdpy, name); if (c == None) - c = XCreateFontCursor(xlibdpy, font); + xcursor_supported = false; return c; } void xcursor_load_cursors() { - cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr", XC_left_ptr); - cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow", XC_sb_h_double_arrow); - cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow", XC_sb_v_double_arrow); + cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr"); + cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow"); + cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow"); } Cursor xcursor_get_cursor(enum xcursor_cursor_t c) { From 305eac0e71fb8e2d6944ec9d5726b9361b13a002 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Feb 2011 16:33:42 +0100 Subject: [PATCH 445/867] Bugfix: Correctly check asprintf() return value --- src/window.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/window.c b/src/window.c index 6555be37..82c881a4 100644 --- a/src/window.c +++ b/src/window.c @@ -51,12 +51,21 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop) { (char*)xcb_get_property_value(prop)) == -1) { perror("asprintf()"); DLOG("Could not get window name\n"); + return; } /* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */ + int len; + char *ucs2_name = convert_utf8_to_ucs2(new_name, &len); + if (ucs2_name == NULL) { + LOG("Could not convert _NET_WM_NAME to UCS-2, ignoring new hint\n"); + FREE(new_name); + return; + } FREE(win->name_x); FREE(win->name_json); win->name_json = new_name; - win->name_x = convert_utf8_to_ucs2(win->name_json, &win->name_len); + win->name_x = ucs2_name; + win->name_len = len; LOG("_NET_WM_NAME changed to \"%s\"\n", win->name_json); win->uses_net_wm_name = true; From b800555161bdc5d14c3d28108d857bb70b4c828d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Feb 2011 16:35:48 +0100 Subject: [PATCH 446/867] Bugfix: check ->layout, not ->type for L_STACKED --- src/x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/x.c b/src/x.c index 0e310a52..d35c8c6a 100644 --- a/src/x.c +++ b/src/x.c @@ -337,7 +337,7 @@ void x_draw_decoration(Con *con) { int indent_level = 0, indent_mult = 0; Con *il_parent = con->parent; - if (il_parent->type != L_STACKED) { + if (il_parent->layout != L_STACKED) { while (1) { DLOG("il_parent = %p, layout = %d\n", il_parent, il_parent->layout); if (il_parent->layout == L_STACKED) From a5e075c154aadb77790b1d76395a2bffd24f745d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Feb 2011 16:43:41 +0100 Subject: [PATCH 447/867] Automatically close empty parent cons when making their last child floating (Thanks mseed) This fixes #313 --- src/floating.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/floating.c b/src/floating.c index c912dc71..25afa832 100644 --- a/src/floating.c +++ b/src/floating.c @@ -78,6 +78,12 @@ void floating_enable(Con *con, bool automatic) { * otherwise. */ nc->parent = con_get_workspace(con); + /* check if the parent container is empty and close it if so */ + if ((con->parent->type == CT_CON || con->parent->type == CT_FLOATING_CON) && con_num_children(con->parent) == 0) { + DLOG("Old container empty after setting this child to floating, closing\n"); + tree_close(con->parent, false, false); + } + char *name; asprintf(&name, "[i3 con] floatingcon around %p", con); x_set_name(nc, name); From 28dd226259d9d5afc535b7839f8b953900ef0b0f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Feb 2011 18:08:36 +0100 Subject: [PATCH 448/867] refactor code for removing children from a con MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let’s see how this callback stuff will work out. If it doesn’t work out well, we will remove it. --- include/data.h | 3 +++ include/util.h | 2 ++ src/con.c | 26 ++++++++++++++++++-------- src/tree.c | 22 ++++++++-------------- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/include/data.h b/include/data.h index b3974a3a..5c4a8852 100644 --- a/include/data.h +++ b/include/data.h @@ -358,6 +358,9 @@ struct Con { TAILQ_ENTRY(Con) focused; TAILQ_ENTRY(Con) all_cons; TAILQ_ENTRY(Con) floating_windows; + + /** callbacks */ + void(*on_remove_child)(Con *); }; #endif diff --git a/include/util.h b/include/util.h index 91b533a1..064a4792 100644 --- a/include/util.h +++ b/include/util.h @@ -34,6 +34,8 @@ } \ while (0) +#define CALL(obj, member, ...) obj->member(obj, ## __VA_ARGS__) + int min(int a, int b); int max(int a, int b); bool rect_contains(Rect rect, uint32_t x, uint32_t y); diff --git a/src/con.c b/src/con.c index c04d281e..65b97443 100644 --- a/src/con.c +++ b/src/con.c @@ -24,6 +24,8 @@ char *colors[] = { "#aa00aa" }; +static void con_on_remove_child(Con *con); + /* * Create a new container (and attach it to the given parent, if not NULL). * This function initializes the data structures and creates the appropriate @@ -32,6 +34,7 @@ char *colors[] = { */ Con *con_new(Con *parent) { Con *new = scalloc(sizeof(Con)); + new->on_remove_child = con_on_remove_child; TAILQ_INSERT_TAIL(&all_cons, new, all_cons); new->type = CT_CON; new->border_style = config.default_border; @@ -522,14 +525,7 @@ void con_move_to_workspace(Con *con, Con *workspace) { /* 8: keep focus on the current workspace */ con_focus(focus_next); - /* 9: check if the parent container is empty now and close it */ - if (parent->type != CT_WORKSPACE && - TAILQ_EMPTY(&(parent->nodes_head))) { - DLOG("Closing empty parent container\n"); - /* TODO: check if this container would swallow any other client and - * don’t close it automatically. */ - tree_close(parent, false, false); - } + CALL(parent, on_remove_child); } /* @@ -744,3 +740,17 @@ void con_set_layout(Con *con, int layout) { con->layout = layout; } + +static void con_on_remove_child(Con *con) { + /* Nothing to do for workspaces */ + if (con->type == CT_WORKSPACE) + return; + + /* TODO: check if this container would swallow any other client and + * don’t close it automatically. */ + DLOG("on_remove_child\n"); + if (con_num_children(con) == 0) { + DLOG("Container empty, closing\n"); + tree_close(con, false, false); + } +} diff --git a/src/tree.c b/src/tree.c index f402aa7d..77da3ac3 100644 --- a/src/tree.c +++ b/src/tree.c @@ -190,14 +190,8 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { } /* check if the parent container is empty now and close it */ - if (!dont_kill_parent && - parent->type != CT_WORKSPACE && - TAILQ_EMPTY(&(parent->nodes_head))) { - DLOG("Closing empty parent container\n"); - /* TODO: check if this container would swallow any other client and - * don’t close it automatically. */ - tree_close(parent, false, false); - } + if (!dont_kill_parent) + CALL(parent, on_remove_child); } /* @@ -504,6 +498,8 @@ void tree_move(char way, orientation_t orientation) { fix_percent = true; } + CALL(con->parent, on_remove_child); + TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, con, nodes); TAILQ_INSERT_HEAD(&(next->parent->focus_head), con, focused); /* TODO: don’t influence focus handling? */ @@ -567,13 +563,11 @@ void tree_move(char way, orientation_t orientation) { * container(s) would still point to the old container(s)). */ con_focus(con); - if (con_num_children(old_parent) == 0) { - DLOG("Old container empty after moving. Let's close it\n"); - tree_close(old_parent, false, false); - } else if (level_changed) { - /* fix the percentages in the container we moved from */ + /* fix the percentages in the container we moved from */ + if (level_changed) con_fix_percent(old_parent); - } + + CALL(old_parent, on_remove_child); tree_flatten(croot); } From 26a416e016314bdeb299cebccae3e8617b697d98 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Feb 2011 23:05:20 +0100 Subject: [PATCH 449/867] refactor tree_move() into src/move.c, change config (!), change testcase Due to lots of cases which were added and added to tree_move(), the function was not really easy to understand. For this refactoring, I wrote tree_move() from scratch, thinking about (hopefully) all cases. The testsuite still passes. The move command also has different parameters now. Instead of the hard to understand 'before v' stuff, we use 'move [left|right|up|down]'. --- Makefile | 2 +- i3.config | 8 +- include/all.h | 1 + include/con.h | 3 + include/move.h | 15 +++ include/tree.h | 7 -- include/workspace.h | 2 + src/cmdparse.l | 2 - src/cmdparse.y | 15 +-- src/con.c | 53 ++++++++++- src/move.c | 174 +++++++++++++++++++++++++++++++++++ src/tree.c | 207 ------------------------------------------ src/workspace.c | 33 +++++++ testcases/t/24-move.t | 30 +++--- 14 files changed, 302 insertions(+), 250 deletions(-) create mode 100644 include/move.h create mode 100644 src/move.c diff --git a/Makefile b/Makefile index 89647f6c..25da180e 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c -FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c +FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c src/move.c FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) diff --git a/i3.config b/i3.config index 185c2462..786ee6a6 100644 --- a/i3.config +++ b/i3.config @@ -66,10 +66,10 @@ bindsym Mod1+Down next v bindsym Mod1+Up prev v # Move -bindsym Mod1+Shift+n move before h -bindsym Mod1+Shift+r move before v -bindsym Mod1+Shift+t move after v -bindsym Mod1+Shift+d move after h +bindsym Mod1+Shift+n move left +bindsym Mod1+Shift+r move down +bindsym Mod1+Shift+t move up +bindsym Mod1+Shift+d move right # alternatively, you can use the cursor keys: bindsym Mod1+Shift+Left move before h diff --git a/include/all.h b/include/all.h index 3cc28940..dd2b9365 100644 --- a/include/all.h +++ b/include/all.h @@ -54,5 +54,6 @@ #include "xcursor.h" #include "resize.h" #include "sighandler.h" +#include "move.h" #endif diff --git a/include/con.h b/include/con.h index de537218..d5dc74e7 100644 --- a/include/con.h +++ b/include/con.h @@ -42,6 +42,9 @@ Con *con_get_output(Con *con); */ Con *con_get_workspace(Con *con); + +Con *con_parent_with_orientation(Con *con, orientation_t orientation); + /** * Returns the first fullscreen node below this node. * diff --git a/include/move.h b/include/move.h new file mode 100644 index 00000000..d0c97014 --- /dev/null +++ b/include/move.h @@ -0,0 +1,15 @@ +/* + * vim:ts=4:sw=4:expandtab + */ + +#ifndef _MOVE_H +#define _MOVE_H + +/** + * Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT, + * TOK_UP, TOK_DOWN from cmdparse.l) + * + */ +void tree_move(int direction); + +#endif diff --git a/include/tree.h b/include/tree.h index c93d4c22..40d9a541 100644 --- a/include/tree.h +++ b/include/tree.h @@ -65,13 +65,6 @@ void tree_close_con(); */ void tree_next(char way, orientation_t orientation); -/** - * Moves the current container in the given way (next/previous) and given - * orientation (horizontal/vertical). - * - */ -void tree_move(char way, orientation_t orientation); - /** * Closes the given container including all children * diff --git a/include/workspace.h b/include/workspace.h index f65f1494..b902f490 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -99,4 +99,6 @@ void workspace_map_clients(xcb_connection_t *conn, Workspace *ws); */ void workspace_update_urgent_flag(Con *ws); +void ws_force_orientation(Con *ws, orientation_t orientation); + #endif diff --git a/src/cmdparse.l b/src/cmdparse.l index d8320578..ebd466af 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -112,8 +112,6 @@ up { return TOK_UP; } down { return TOK_DOWN; } left { return TOK_LEFT; } right { return TOK_RIGHT; } -before { return TOK_BEFORE; } -after { return TOK_AFTER; } resize { return TOK_RESIZE; } shrink { return TOK_SHRINK; } grow { return TOK_GROW; } diff --git a/src/cmdparse.y b/src/cmdparse.y index 50738f70..d71773fe 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -134,8 +134,6 @@ char *parse_cmd(const char *new) { %token TOK_DOWN "down" %token TOK_LEFT "left" %token TOK_RIGHT "right" -%token TOK_AFTER "after" -%token TOK_BEFORE "before" %token TOK_RESTORE "restore" %token TOK_MARK "mark" %token TOK_RESIZE "resize" @@ -527,12 +525,10 @@ level_direction: ; move: - TOK_MOVE WHITESPACE before_after WHITESPACE direction + TOK_MOVE WHITESPACE direction { - printf("moving: %s and %c\n", ($3 == TOK_BEFORE ? "before" : "after"), $5); - /* TODO: change API for the next call, we need to convert in both directions while ideally - * we should not need any of both */ - tree_move(($3 == TOK_BEFORE ? 'p' : 'n'), ($5 == 'v' ? VERT : HORIZ)); + printf("moving in direction %d\n", $3); + tree_move($3); } | TOK_MOVE WHITESPACE TOK_WORKSPACE WHITESPACE STR { @@ -555,11 +551,6 @@ move: } ; -before_after: - TOK_BEFORE { $$ = TOK_BEFORE; } - | TOK_AFTER { $$ = TOK_AFTER; } - ; - restore: TOK_RESTORE WHITESPACE STR { diff --git a/src/con.c b/src/con.c index 65b97443..d36a0da4 100644 --- a/src/con.c +++ b/src/con.c @@ -230,6 +230,24 @@ Con *con_get_workspace(Con *con) { return result; } +Con *con_parent_with_orientation(Con *con, orientation_t orientation) { + DLOG("Searching for parent of Con %p with orientation %d\n", con, orientation); + Con *parent = con->parent; + if (parent->type == CT_FLOATING_CON) + return NULL; + while (con_orientation(parent) != orientation) { + DLOG("Need to go one level further up\n"); + parent = parent->parent; + /* Abort when we reach a floating con */ + if (parent && parent->type == CT_FLOATING_CON) + parent = NULL; + if (parent == NULL) + break; + } + DLOG("Result: %p\n", parent); + return parent; +} + /* * helper data structure for the breadth-first-search in * con_get_fullscreen_con() @@ -742,15 +760,44 @@ void con_set_layout(Con *con, int layout) { } static void con_on_remove_child(Con *con) { + DLOG("on_remove_child\n"); + /* Nothing to do for workspaces */ - if (con->type == CT_WORKSPACE) + if (con->type == CT_WORKSPACE || con->type == CT_OUTPUT || con->type == CT_ROOT) { + DLOG("not handling, type = %d\n", con->type); return; + } /* TODO: check if this container would swallow any other client and * don’t close it automatically. */ - DLOG("on_remove_child\n"); - if (con_num_children(con) == 0) { + int children = con_num_children(con); + if (children == 0) { DLOG("Container empty, closing\n"); tree_close(con, false, false); + return; + } + + /* If we did not close the container, check if we have only a single child left */ + if (children == 1) { + Con *child = TAILQ_FIRST(&(con->nodes_head)); + Con *parent = con->parent; + DLOG("Container has only one child, replacing con %p with child %p\n", con, child); + + /* TODO: refactor it into con_swap */ + TAILQ_REPLACE(&(parent->nodes_head), con, child, nodes); + TAILQ_REPLACE(&(parent->focus_head), con, child, focused); + if (focused == con) + focused = child; + child->parent = parent; + child->percent = 0.0; + con_fix_percent(parent); + + con->parent = NULL; + x_con_kill(con); + free(con->name); + TAILQ_REMOVE(&all_cons, con, all_cons); + free(con); + + return; } } diff --git a/src/move.c b/src/move.c new file mode 100644 index 00000000..2700f205 --- /dev/null +++ b/src/move.c @@ -0,0 +1,174 @@ +/* + * vim:ts=4:sw=4:expandtab + */ + +#include "all.h" +#include "cmdparse.tab.h" + +typedef enum { BEFORE, AFTER } position_t; + +/* + * This function detaches 'con' from its parent and inserts it either before or + * after 'target'. + * + */ +static void insert_con_into(Con *con, Con *target, position_t position) { + Con *parent = target->parent; + /* We need to preserve the old con->parent. While it might still be used to + * insert the entry before/after it, we call the on_remove_child callback + * afterwards which might then close the con if it is empty. */ + Con *old_parent = con->parent; + + con_detach(con); + con_fix_percent(con->parent); + + con->parent = parent; + + if (position == BEFORE) { + TAILQ_INSERT_BEFORE(target, con, nodes); + TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused); + } else if (position == AFTER) { + TAILQ_INSERT_AFTER(&(parent->nodes_head), target, con, nodes); + TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused); + } + + /* Pretend the con was just opened with regards to size percent values. + * Since the con is moved to a completely different con, the old value + * does not make sense anyways. */ + con->percent = 0.0; + con_fix_percent(parent); + + CALL(old_parent, on_remove_child); +} + +/* + * This function detaches 'con' from its parent and inserts it at the given + * workspace. + * + */ +static void attach_to_workspace(Con *con, Con *ws) { + con_detach(con); + con_fix_percent(con->parent); + + CALL(con->parent, on_remove_child); + + con->parent = ws; + + TAILQ_INSERT_TAIL(&(ws->nodes_head), con, nodes); + TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused); + + /* Pretend the con was just opened with regards to size percent values. + * Since the con is moved to a completely different con, the old value + * does not make sense anyways. */ + con->percent = 0.0; + con_fix_percent(ws); +} + +/* + * Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT, + * TOK_UP, TOK_DOWN from cmdparse.l) + * + */ +void tree_move(int direction) { + DLOG("Moving in direction %d\n", direction); + /* 1: get the first parent with the same orientation */ + Con *con = focused; + + if (con->type == CT_WORKSPACE) { + DLOG("Not moving workspace\n"); + return; + } + + if (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1) { + DLOG("This is the only con on this workspace, not doing anything\n"); + return; + } + + orientation_t o = (direction == TOK_LEFT || direction == TOK_RIGHT ? HORIZ : VERT); + + Con *same_orientation = con_parent_with_orientation(con, o); + /* There is no parent container with the same orientation */ + if (!same_orientation) { + if (con_is_floating(con)) { + /* this is a floating con, we just disable floating */ + floating_disable(con, true); + return; + } + if (con_inside_floating(con)) { + /* 'con' should be moved out of a floating container */ + DLOG("Inside floating, moving to workspace\n"); + attach_to_workspace(con, con_get_workspace(con)); + goto end; + } + DLOG("Force-changing orientation\n"); + ws_force_orientation(con_get_workspace(con), o); + same_orientation = con_parent_with_orientation(con, o); + } + + /* easy case: the move is within this container */ + if (same_orientation == con->parent) { + DLOG("We are in the same container\n"); + Con *swap; + /* TODO: TAILQ_SWAP? */ + if (direction == TOK_LEFT || direction == TOK_UP) { + if (!(swap = TAILQ_PREV(con, nodes_head, nodes))) + return; + + if (!con_is_leaf(swap)) { + insert_con_into(con, con_descend_focused(swap), AFTER); + goto end; + } + + /* the container right of the current one is a normal one. */ + con_detach(con); + TAILQ_INSERT_BEFORE(swap, con, nodes); + TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused); + } else { + if (!(swap = TAILQ_NEXT(con, nodes))) + return; + + if (!con_is_leaf(swap)) { + insert_con_into(con, con_descend_focused(swap), AFTER); + goto end; + } + + con_detach(con); + TAILQ_INSERT_AFTER(&(swap->parent->nodes_head), swap, con, nodes); + TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused); + } + DLOG("Swapped.\n"); + return; + } + + /* this time, we have to move to another container */ + /* This is the container *above* 'con' which is inside 'same_orientation' */ + Con *above = con; + while (above->parent != same_orientation) + above = above->parent; + + DLOG("above = %p\n", above); + Con *next; + position_t position; + if (direction == TOK_UP || direction == TOK_LEFT) { + position = BEFORE; + next = TAILQ_PREV(above, nodes_head, nodes); + } else if (direction == TOK_DOWN || direction == TOK_RIGHT) { + position = AFTER; + next = TAILQ_NEXT(above, nodes); + } + + /* special case: there is a split container in the direction we are moving + * to, so descend and append */ + if (next && !con_is_leaf(next)) + insert_con_into(con, con_descend_focused(next), AFTER); + else + insert_con_into(con, above, position); + +end: + /* We need to call con_focus() to fix the focus stack "above" the container + * we just inserted the focused container into (otherwise, the parent + * container(s) would still point to the old container(s)). */ + con_focus(con); + + tree_flatten(croot); +} diff --git a/src/tree.c b/src/tree.c index 77da3ac3..f2b1d90e 100644 --- a/src/tree.c +++ b/src/tree.c @@ -365,213 +365,6 @@ void tree_next(char way, orientation_t orientation) { con_focus(con_descend_focused(next)); } -/* - * Moves the current container in the given way (next/previous) and given - * orientation (horizontal/vertical). - * - */ -void tree_move(char way, orientation_t orientation) { - /* 1: get the first parent with the same orientation */ - Con *con = focused; - Con *parent = con->parent; - Con *old_parent = parent; - if (con->type == CT_WORKSPACE) - return; - DLOG("con = %p / %s\n", con, con->name); - bool level_changed = false; - while (con_orientation(parent) != orientation) { - DLOG("need to go one level further up\n"); - /* If the current parent is an output, we are at a workspace - * and the orientation still does not match. In this case, we split the - * workspace to have the same look & feel as in older i3 releases. */ - if (parent->type == CT_WORKSPACE) { - DLOG("Arrived at workspace (%p / %s)\n", parent, parent->name); - /* In case of moving a window out of a floating con, there might be - * not a single tiling container. Makes no sense to split then, so - * just use the workspace as target */ - if (TAILQ_EMPTY(&(parent->nodes_head))) - break; - - /* Check if there are any other cons at all. If not, there is no - * point in creating a new split con and changing workspace - * orientation. Instead, the operation is a no-op. */ - Con *child; - bool other_container = false; - TAILQ_FOREACH(child, &(parent->nodes_head), nodes) - if (child != con) - other_container = true; - - if (!other_container) { - DLOG("No other container found, we are not creating this split container.\n"); - return; - } - - /* 1: create a new split container */ - Con *new = con_new(NULL); - new->parent = parent; - - /* 2: copy layout and orientation from workspace */ - new->layout = parent->layout; - new->orientation = parent->orientation; - - Con *old_focused = TAILQ_FIRST(&(parent->focus_head)); - if (old_focused == TAILQ_END(&(parent->focus_head))) - old_focused = NULL; - - /* 3: move the existing cons of this workspace below the new con */ - DLOG("Moving cons\n"); - while (!TAILQ_EMPTY(&(parent->nodes_head))) { - child = TAILQ_FIRST(&(parent->nodes_head)); - con_detach(child); - con_attach(child, new, true); - } - - /* 4: switch workspace orientation */ - parent->orientation = orientation; - - /* 5: attach the new split container to the workspace */ - DLOG("Attaching new split to ws\n"); - con_attach(new, parent, false); - - /* 6: fix the percentages */ - con_fix_percent(parent); - - if (old_focused) - con_focus(old_focused); - - level_changed = true; - - break; - } - parent = parent->parent; - level_changed = true; - } - /* If we have no tiling cons (when moving a window out of a floating con to - * an otherwise empty workspace for example), we just attach the window to - * the workspace. */ - bool fix_percent = false; - if (TAILQ_EMPTY(&(parent->nodes_head))) { - con_detach(con); - con_fix_percent(con->parent); - con->parent = parent; - fix_percent = true; - - TAILQ_INSERT_HEAD(&(parent->nodes_head), con, nodes); - TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused); - } else { - Con *current = NULL, *loop; - /* Get the first tiling container in focus stack */ - TAILQ_FOREACH(loop, &(parent->focus_head), focused) { - if (loop->type == CT_FLOATING_CON) - continue; - current = loop; - break; - } - assert(current != TAILQ_END(&(parent->focus_head))); - - /* 2: chose next (or previous) */ - Con *next = current; - if (way == 'n') { - LOG("i would insert it after %p / %s\n", next, next->name); - - /* Have a look at the next container: If there is no next container or - * if it is a leaf node, we move the con one left to it. However, - * for split containers, we descend into it. */ - next = TAILQ_NEXT(next, nodes); - if (next == TAILQ_END(&(next->parent->nodes_head))) { - if (con == current) - return; - next = current; - } else { - if (level_changed && con_is_leaf(next)) { - next = current; - } else { - /* if this is a split container, we need to go down */ - next = con_descend_focused(next); - } - } - - con_detach(con); - if (con->parent != next->parent) { - con_fix_percent(con->parent); - con->parent = next->parent; - fix_percent = true; - } - - CALL(con->parent, on_remove_child); - - TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, con, nodes); - TAILQ_INSERT_HEAD(&(next->parent->focus_head), con, focused); - /* TODO: don’t influence focus handling? */ - } else { - LOG("i would insert it before %p / %s\n", current, current->name); - bool gone_down = false; - next = TAILQ_PREV(next, nodes_head, nodes); - if (next == TAILQ_END(&(next->parent->nodes_head))) { - if (con == current) { - DLOG("Cannot move, no other container in that direction\n"); - return; - } - next = current; - } else { - if (level_changed && con_is_leaf(next)) { - next = current; - } else { - /* if this is a split container, we need to go down */ - while (!TAILQ_EMPTY(&(next->focus_head))) { - gone_down = true; - next = TAILQ_FIRST(&(next->focus_head)); - } - } - } - - DLOG("detaching con = %p / %s, next = %p / %s\n", - con, con->name, next, next->name); - con_detach(con); - if (con->parent != next->parent) { - DLOG("different parents. new parent = %p / %s\n", next->parent, next->parent->name); - con_fix_percent(con->parent); - con->parent = next->parent; - fix_percent = true; - } - - /* After going down in the tree, we insert the container *after* - * the currently focused one even though the command used "before". - * This is to keep the user experience clear, since the before/after - * only signifies the direction of the movement on top-level */ - if (gone_down) - TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, con, nodes); - else TAILQ_INSERT_BEFORE(next, con, nodes); - TAILQ_INSERT_HEAD(&(next->parent->focus_head), con, focused); - /* TODO: don’t influence focus handling? */ - } - } - - /* fix the percentages in the container we moved to */ - if (fix_percent) { - int children = con_num_children(con->parent); - if (children == 1) { - con->percent = 1.0; - } else { - con->percent = 1.0 / (children - 1); - con_fix_percent(con->parent); - } - } - - /* We need to call con_focus() to fix the focus stack "above" the container - * we just inserted the focused container into (otherwise, the parent - * container(s) would still point to the old container(s)). */ - con_focus(con); - - /* fix the percentages in the container we moved from */ - if (level_changed) - con_fix_percent(old_parent); - - CALL(old_parent, on_remove_child); - - tree_flatten(croot); -} - /* * tree_flatten() removes pairs of redundant split containers, e.g.: * [workspace, horizontal] diff --git a/src/workspace.c b/src/workspace.c index 55f7dd17..c17eb7ed 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -487,3 +487,36 @@ void workspace_update_urgent_flag(Con *ws) { if (old_flag != ws->urgent) ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}"); } + +void ws_force_orientation(Con *ws, orientation_t orientation) { + /* 1: create a new split container */ + Con *split = con_new(NULL); + split->parent = ws; + + /* 2: copy layout and orientation from workspace */ + split->layout = ws->layout; + split->orientation = ws->orientation; + + Con *old_focused = TAILQ_FIRST(&(ws->focus_head)); + + /* 3: move the existing cons of this workspace below the new con */ + DLOG("Moving cons\n"); + while (!TAILQ_EMPTY(&(ws->nodes_head))) { + Con *child = TAILQ_FIRST(&(ws->nodes_head)); + con_detach(child); + con_attach(child, split, true); + } + + /* 4: switch workspace orientation */ + ws->orientation = orientation; + + /* 5: attach the new split container to the workspace */ + DLOG("Attaching new split to ws\n"); + con_attach(split, ws, false); + + /* 6: fix the percentages */ + con_fix_percent(ws); + + if (old_focused) + con_focus(old_focused); +} diff --git a/testcases/t/24-move.t b/testcases/t/24-move.t index 5d6a96ef..e887b9f3 100644 --- a/testcases/t/24-move.t +++ b/testcases/t/24-move.t @@ -13,13 +13,13 @@ use X11::XCB qw(:all); my $i3 = i3("/tmp/nestedcons"); my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +cmd "workspace $tmp"; ###################################################################### # 1) move a container which cannot be moved ###################################################################### -$i3->command('open')->recv; +cmd 'open'; my $old_content = get_ws_content($tmp); is(@{$old_content}, 1, 'one container on this workspace'); @@ -46,22 +46,22 @@ my $second = $content->[1]->{id}; is($content->[0]->{id}, $first, 'first container unmodified'); # Move the second container before the first one (→ swap them) -$i3->command('move before h')->recv; +$i3->command('move left')->recv; $content = get_ws_content($tmp); is($content->[0]->{id}, $second, 'first container modified'); # We should not be able to move any further -$i3->command('move before h')->recv; +$i3->command('move left')->recv; $content = get_ws_content($tmp); is($content->[0]->{id}, $second, 'first container unmodified'); # Now move in the other direction -$i3->command('move after h')->recv; +$i3->command('move right')->recv; $content = get_ws_content($tmp); is($content->[0]->{id}, $first, 'first container modified'); # We should not be able to move any further -$i3->command('move after h')->recv; +$i3->command('move right')->recv; $content = get_ws_content($tmp); is($content->[0]->{id}, $first, 'first container unmodified'); @@ -84,7 +84,7 @@ $content = get_ws_content($tmp); is(@{$content}, 3, 'three containers on this workspace'); my $third = $content->[2]->{id}; -$i3->command('move before h')->recv; +$i3->command('move left')->recv; $content = get_ws_content($tmp); is(@{$content}, 2, 'only two containers on this workspace'); my $nodes = $content->[1]->{nodes}; @@ -95,19 +95,21 @@ is($nodes->[1]->{id}, $third, 'third container on bottom'); # move it inside the split container ###################################################################### -$i3->command('move before v')->recv; +$i3->command('move up')->recv; $nodes = get_ws_content($tmp)->[1]->{nodes}; is($nodes->[0]->{id}, $third, 'third container on top'); is($nodes->[1]->{id}, $second, 'second container on bottom'); # move it outside again -$i3->command('move before h')->recv; +$i3->command('move left')->recv; $content = get_ws_content($tmp); is(@{$content}, 3, 'three nodes on this workspace'); -$i3->command('move after h')->recv; +# due to automatic flattening/cleanup, the remaining split container +# will be replaced by the con itself, so we will still have 3 nodes +$i3->command('move right')->recv; $content = get_ws_content($tmp); -is(@{$content}, 2, 'two nodes on this workspace'); +is(@{$content}, 3, 'two nodes on this workspace'); ###################################################################### # 4) We create two v-split containers on the workspace, then we move @@ -116,7 +118,7 @@ is(@{$content}, 2, 'two nodes on this workspace'); ###################################################################### my $otmp = get_unused_workspace(); -$i3->command("workspace $otmp")->recv; +cmd "workspace $otmp"; $i3->command("open")->recv; $i3->command("open")->recv; @@ -125,9 +127,9 @@ $i3->command("open")->recv; $i3->command("prev h")->recv; $i3->command("split v")->recv; $i3->command("open")->recv; -$i3->command("move after h")->recv; +$i3->command("move right")->recv; $i3->command("prev h")->recv; -$i3->command("move after h")->recv; +$i3->command("move right")->recv; $content = get_ws_content($otmp); is(@{$content}, 1, 'only one nodes on this workspace'); From 834f4d7bc29cc2e0b4b151c62c8bc468943fa2d7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Feb 2011 23:17:30 +0100 Subject: [PATCH 450/867] add missing function documentation --- include/con.h | 6 +++++- include/workspace.h | 6 ++++++ src/con.c | 11 +++++++++++ src/workspace.c | 6 ++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/include/con.h b/include/con.h index d5dc74e7..0d4df204 100644 --- a/include/con.h +++ b/include/con.h @@ -42,7 +42,11 @@ Con *con_get_output(Con *con); */ Con *con_get_workspace(Con *con); - +/** + * Searches parenst of the given 'con' until it reaches one with the specified + * 'orientation'. Aborts when it comes across a floating_con. + * + */ Con *con_parent_with_orientation(Con *con, orientation_t orientation); /** diff --git a/include/workspace.h b/include/workspace.h index b902f490..d903ea82 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -99,6 +99,12 @@ void workspace_map_clients(xcb_connection_t *conn, Workspace *ws); */ void workspace_update_urgent_flag(Con *ws); +/** + * 'Forces' workspace orientation by moving all cons into a new split-con with + * the same orientation as the workspace and then changing the workspace + * orientation. + * + */ void ws_force_orientation(Con *ws, orientation_t orientation); #endif diff --git a/src/con.c b/src/con.c index d36a0da4..92b05fd7 100644 --- a/src/con.c +++ b/src/con.c @@ -230,6 +230,11 @@ Con *con_get_workspace(Con *con) { return result; } +/* + * Searches parenst of the given 'con' until it reaches one with the specified + * 'orientation'. Aborts when it comes across a floating_con. + * + */ Con *con_parent_with_orientation(Con *con, orientation_t orientation) { DLOG("Searching for parent of Con %p with orientation %d\n", con, orientation); Con *parent = con->parent; @@ -759,6 +764,12 @@ void con_set_layout(Con *con, int layout) { con->layout = layout; } +/* + * Callback which will be called when removing a child from the given con. + * Kills the container if it is empty and replaces it with the child if there + * is exactly one child. + * + */ static void con_on_remove_child(Con *con) { DLOG("on_remove_child\n"); diff --git a/src/workspace.c b/src/workspace.c index c17eb7ed..4fcdd747 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -488,6 +488,12 @@ void workspace_update_urgent_flag(Con *ws) { ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}"); } +/* + * 'Forces' workspace orientation by moving all cons into a new split-con with + * the same orientation as the workspace and then changing the workspace + * orientation. + * + */ void ws_force_orientation(Con *ws, orientation_t orientation) { /* 1: create a new split container */ Con *split = con_new(NULL); From 53d9072ca72ba91c56c431e2aa02bf765713ea68 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 15 Feb 2011 02:20:29 +0100 Subject: [PATCH 451/867] implement TAILQ_SWAP (only for consecutive elements, order relevant) and use it --- include/queue.h | 13 ++ src/move.c | 39 ++-- tests/queue.h | 527 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/swap.c | 229 +++++++++++++++++++++ 4 files changed, 783 insertions(+), 25 deletions(-) create mode 100644 tests/queue.h create mode 100644 tests/swap.c diff --git a/include/queue.h b/include/queue.h index 75bb957a..0c685215 100644 --- a/include/queue.h +++ b/include/queue.h @@ -407,6 +407,19 @@ struct { \ _Q_INVALIDATE((elm)->field.tqe_next); \ } while (0) +/* Swaps two consecutive elements. 'second' *MUST* follow 'first' */ +#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) + /* * Circular queue definitions. */ diff --git a/src/move.c b/src/move.c index 2700f205..6088c6e4 100644 --- a/src/move.c +++ b/src/move.c @@ -109,33 +109,22 @@ void tree_move(int direction) { if (same_orientation == con->parent) { DLOG("We are in the same container\n"); Con *swap; - /* TODO: TAILQ_SWAP? */ - if (direction == TOK_LEFT || direction == TOK_UP) { - if (!(swap = TAILQ_PREV(con, nodes_head, nodes))) - return; + if (!(swap = (direction == TOK_LEFT || direction == TOK_UP ? + TAILQ_PREV(con, nodes_head, nodes) : + TAILQ_NEXT(con, nodes)))) + return; - if (!con_is_leaf(swap)) { - insert_con_into(con, con_descend_focused(swap), AFTER); - goto end; - } - - /* the container right of the current one is a normal one. */ - con_detach(con); - TAILQ_INSERT_BEFORE(swap, con, nodes); - TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused); - } else { - if (!(swap = TAILQ_NEXT(con, nodes))) - return; - - if (!con_is_leaf(swap)) { - insert_con_into(con, con_descend_focused(swap), AFTER); - goto end; - } - - con_detach(con); - TAILQ_INSERT_AFTER(&(swap->parent->nodes_head), swap, con, nodes); - TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused); + if (!con_is_leaf(swap)) { + insert_con_into(con, con_descend_focused(swap), AFTER); + goto end; } + if (direction == TOK_LEFT || direction == TOK_UP) + TAILQ_SWAP(swap, con, &(swap->parent->nodes_head), nodes); + else TAILQ_SWAP(con, swap, &(swap->parent->nodes_head), nodes); + + TAILQ_REMOVE(&(con->parent->focus_head), con, focused); + TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused); + DLOG("Swapped.\n"); return; } diff --git a/tests/queue.h b/tests/queue.h new file mode 100644 index 00000000..75bb957a --- /dev/null +++ b/tests/queue.h @@ -0,0 +1,527 @@ +/* $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 + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +/* + * 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) + +#endif /* !_SYS_QUEUE_H_ */ diff --git a/tests/swap.c b/tests/swap.c new file mode 100644 index 00000000..606b0f71 --- /dev/null +++ b/tests/swap.c @@ -0,0 +1,229 @@ +/* + * vim:ts=4:sw=4:expandtab + */ +#include +#include +#include +#include +#include +#include + +#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(); + +} From 6a6746b967d7e82676d3805c088feb9dcb924877 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Feb 2011 18:30:26 +0100 Subject: [PATCH 452/867] revert the replacement of a single h/v-split with its child container Makes more problems than it creates. Will use a different fix suggested by Merovius. --- src/con.c | 24 ------------------------ testcases/t/24-move.t | 2 +- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/src/con.c b/src/con.c index 92b05fd7..8992d132 100644 --- a/src/con.c +++ b/src/con.c @@ -787,28 +787,4 @@ static void con_on_remove_child(Con *con) { tree_close(con, false, false); return; } - - /* If we did not close the container, check if we have only a single child left */ - if (children == 1) { - Con *child = TAILQ_FIRST(&(con->nodes_head)); - Con *parent = con->parent; - DLOG("Container has only one child, replacing con %p with child %p\n", con, child); - - /* TODO: refactor it into con_swap */ - TAILQ_REPLACE(&(parent->nodes_head), con, child, nodes); - TAILQ_REPLACE(&(parent->focus_head), con, child, focused); - if (focused == con) - focused = child; - child->parent = parent; - child->percent = 0.0; - con_fix_percent(parent); - - con->parent = NULL; - x_con_kill(con); - free(con->name); - TAILQ_REMOVE(&all_cons, con, all_cons); - free(con); - - return; - } } diff --git a/testcases/t/24-move.t b/testcases/t/24-move.t index e887b9f3..993f48b0 100644 --- a/testcases/t/24-move.t +++ b/testcases/t/24-move.t @@ -109,7 +109,7 @@ is(@{$content}, 3, 'three nodes on this workspace'); # will be replaced by the con itself, so we will still have 3 nodes $i3->command('move right')->recv; $content = get_ws_content($tmp); -is(@{$content}, 3, 'two nodes on this workspace'); +is(@{$content}, 2, 'two nodes on this workspace'); ###################################################################### # 4) We create two v-split containers on the workspace, then we move From 86500c5b889c55cd9308d4fb9349605a944cb91c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Feb 2011 19:50:09 +0100 Subject: [PATCH 453/867] Skip containers which got only one child when looking for the next/previous one to focus --- src/tree.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index f2b1d90e..c741e65a 100644 --- a/src/tree.c +++ b/src/tree.c @@ -329,7 +329,8 @@ void tree_next(char way, orientation_t orientation) { /* 1: get the first parent with the same orientation */ Con *parent = focused->parent; while (focused->type != CT_WORKSPACE && - con_orientation(parent) != orientation) { + (con_orientation(parent) != orientation || + con_num_children(parent) == 1)) { LOG("need to go one level further up\n"); /* if the current parent is an output, we are at a workspace * and the orientation still does not match */ From c5ab16c00d3a47e95c5df239714dfd39bc0edaca Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Feb 2011 20:11:47 +0100 Subject: [PATCH 454/867] same fix, but for moving (search above the current con when moving is not possible in this direction) --- src/move.c | 90 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 39 deletions(-) diff --git a/src/move.c b/src/move.c index 6088c6e4..955cb6b0 100644 --- a/src/move.c +++ b/src/move.c @@ -87,47 +87,59 @@ void tree_move(int direction) { orientation_t o = (direction == TOK_LEFT || direction == TOK_RIGHT ? HORIZ : VERT); Con *same_orientation = con_parent_with_orientation(con, o); - /* There is no parent container with the same orientation */ - if (!same_orientation) { - if (con_is_floating(con)) { - /* this is a floating con, we just disable floating */ - floating_disable(con, true); - return; + /* The do {} while is used to 'restart' at this point with a different + * same_orientation, see the very last lines before the end of this block + * */ + do { + /* There is no parent container with the same orientation */ + if (!same_orientation) { + if (con_is_floating(con)) { + /* this is a floating con, we just disable floating */ + floating_disable(con, true); + return; + } + if (con_inside_floating(con)) { + /* 'con' should be moved out of a floating container */ + DLOG("Inside floating, moving to workspace\n"); + attach_to_workspace(con, con_get_workspace(con)); + goto end; + } + DLOG("Force-changing orientation\n"); + ws_force_orientation(con_get_workspace(con), o); + same_orientation = con_parent_with_orientation(con, o); } - if (con_inside_floating(con)) { - /* 'con' should be moved out of a floating container */ - DLOG("Inside floating, moving to workspace\n"); - attach_to_workspace(con, con_get_workspace(con)); - goto end; + + /* easy case: the move is within this container */ + if (same_orientation == con->parent) { + DLOG("We are in the same container\n"); + Con *swap; + if ((swap = (direction == TOK_LEFT || direction == TOK_UP ? + TAILQ_PREV(con, nodes_head, nodes) : + TAILQ_NEXT(con, nodes)))) { + if (!con_is_leaf(swap)) { + insert_con_into(con, con_descend_focused(swap), AFTER); + goto end; + } + if (direction == TOK_LEFT || direction == TOK_UP) + TAILQ_SWAP(swap, con, &(swap->parent->nodes_head), nodes); + else TAILQ_SWAP(con, swap, &(swap->parent->nodes_head), nodes); + + TAILQ_REMOVE(&(con->parent->focus_head), con, focused); + TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused); + + DLOG("Swapped.\n"); + return; + } + + /* If there was no con with which we could swap the current one, search + * again, but starting one level higher. If we are on the workspace + * level, don’t do that. The result would be a force change of + * workspace orientation, which is not necessary. */ + if (con->parent == con_get_workspace(con)) + return; + same_orientation = con_parent_with_orientation(con->parent, o); } - DLOG("Force-changing orientation\n"); - ws_force_orientation(con_get_workspace(con), o); - same_orientation = con_parent_with_orientation(con, o); - } - - /* easy case: the move is within this container */ - if (same_orientation == con->parent) { - DLOG("We are in the same container\n"); - Con *swap; - if (!(swap = (direction == TOK_LEFT || direction == TOK_UP ? - TAILQ_PREV(con, nodes_head, nodes) : - TAILQ_NEXT(con, nodes)))) - return; - - if (!con_is_leaf(swap)) { - insert_con_into(con, con_descend_focused(swap), AFTER); - goto end; - } - if (direction == TOK_LEFT || direction == TOK_UP) - TAILQ_SWAP(swap, con, &(swap->parent->nodes_head), nodes); - else TAILQ_SWAP(con, swap, &(swap->parent->nodes_head), nodes); - - TAILQ_REMOVE(&(con->parent->focus_head), con, focused); - TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused); - - DLOG("Swapped.\n"); - return; - } + } while (same_orientation == NULL); /* this time, we have to move to another container */ /* This is the container *above* 'con' which is inside 'same_orientation' */ From 579551a2bd84eab7bb2a9219ead03fbeba243335 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Feb 2011 21:45:57 +0100 Subject: [PATCH 455/867] partially update hacking-howto with an explanation of the moving code --- docs/hacking-howto | 129 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 1 deletion(-) diff --git a/docs/hacking-howto b/docs/hacking-howto index dff074cb..48d717f1 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -1,13 +1,15 @@ Hacking i3: How To ================== Michael Stapelberg -December 2009 +February 2010 This document is intended to be the first thing you read before looking and/or touching i3’s source code. It should contain all important information to help you understand why things are like they are. If it does not mention something you find necessary, please do not hesitate to contact me. +PLEASE BEWARE THAT THIS DOCUMENT IS ONLY PARTIALLY UPDATED FOR -tree YET! + == Window Managers A window manager is not necessarily needed to run X, but it is usually used in @@ -485,6 +487,131 @@ j, k and l, like in vim (h = left, j = down, k = up, l = right). When you just specify the direction keys, i3 will move the focus in that direction. You can provide "m" or "s" before the direction to move a window respectively or snap. +== Moving containers + +The movement code is pretty delicate. You need to consider all cases before +making any changes or before being able to fully understand how it works. + +=== Case 1: Moving inside the same container + +The reference layout for this case is a single workspace in horizontal +orientation with two containers on it. Focus is on the left container (1). + + +[width="15%",cols="^,^"] +|======== +| 1 | 2 +|======== + +When moving the left window to the right (command +move right+), tree_move will +look for a container with horizontal orientation and finds the parent of the +left container, that is, the workspace. Afterwards, it runs the code branch +commented with "the easy case": it calls TAILQ_NEXT to get the container right +of the current one and swaps both containers. + +=== Case 2: Move a container into a split container + +The reference layout for this case is a horizontal workspace with two +containers. The right container is a v-split with two containers. Focus is on +the left container (1). + +[width="15%",cols="^,^"] +|======== +1.2+^.^| 1 | 2 +| 3 +|======== + +When moving to the right (command +move right+), i3 will work like in case 1 +("the easy case"). However, as the right container is not a leaf container, but +a v-split, the left container (1) will be inserted at the right position (below +2, assuming that 2 is focused inside the v-split) by calling +insert_con_into+. + ++insert_con_into+ detaches the container from its parent and inserts it +before/after the given target container. Afterwards, the on_remove_child +callback is called on the old parent container which will then be closed, if +empty. + +Afterwards, +con_focus+ will be called to fix the focus stack and the tree will +be flattened. + +=== Case 3: Moving to non-existant top/bottom + +Like in case 1, the reference layout for this case is a single workspace in +horizontal orientation with two containers on it. Focus is on the left +container: + +[width="15%",cols="^,^"] +|======== +| 1 | 2 +|======== + +This time however, the command is +move up+ or +move down+. tree_move will look +for a container with vertical orientation. As it will not find any, ++same_orientation+ is NULL and therefore i3 will perform a forced orientation +change on the workspace by creating a new h-split container, moving the +workspace contents into it and then changing the workspace orientation to +vertical. Now it will again search for parent containers with vertical +orientation and it will find the workspace. + +This time, the easy case code path will not be run as we are not moving inside +the same container. Instead, +insert_con_into+ will be called with the focused +container and the container above/below the current one (on the level of ++same_orientation+). + +Now, +con_focus+ will be called to fix the focus stack and the tree will be +flattened. + +=== Case 4: Moving to existant top/bottom + +The reference layout for this case is a vertical workspace with two containers. +The bottom one is a h-split containing two containers (1 and 2). Focus is on +the bottom left container (1). + +[width="15%",cols="^,^"] +|======== +2+| 3 +| 1 | 2 +|======== + +This case is very much like case 3, only this time the forced workspace +orientation change does not need to be performed because the workspace already +is in vertical orientation. + +=== Case 5: Moving in one-child h-split + +The reference layout for this case is a horizontal workspace with two +containers having a v-split on the left side with a one-child h-split on the +bottom. Focus is on the bottom left container (2(h)): + +[width="15%",cols="^,^"] +|======== +| 1 1.2+^.^| 3 +| 2(h) +|======== + +In this case, +same_orientation+ will be set to the h-split container around +the focused container. However, when trying the easy case, the next/previous +container +swap+ will be NULL. Therefore, i3 will search again for a ++same_orientation+ container, this time starting from the parent of the h-split +container. + +After determining a new +same_orientation+ container (if it is NULL, the +orientation will be force-changed), this case is equivalent to case 2 or case +4. + + +=== Case 6: Floating containers + +The reference layout for this case is a horizontal workspace with two +containers plus one floating h-split container. Focus is on the floating +container. + +TODO: nice illustration. table not possible? + +When moving up/down, the container needs to leave the floating container and it +needs to be placed on the workspace (at workspace level). This is accomplished +by calling the function +attach_to_workspace+. + == Gotchas * Forgetting to call `xcb_flush(conn);` after sending a request. This usually From 0df960b7d7bb98292ed18661fd5e97f8aa552b53 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Feb 2011 02:11:03 +0100 Subject: [PATCH 456/867] also change the cursor keys to the new move syntax --- i3.config | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/i3.config b/i3.config index 786ee6a6..71511a50 100644 --- a/i3.config +++ b/i3.config @@ -72,10 +72,10 @@ bindsym Mod1+Shift+t move up bindsym Mod1+Shift+d move right # alternatively, you can use the cursor keys: -bindsym Mod1+Shift+Left move before h -bindsym Mod1+Shift+Right move after h -bindsym Mod1+Shift+Down move before v -bindsym Mod1+Shift+Up move after v +bindsym Mod1+Shift+Left move left +bindsym Mod1+Shift+Right move right +bindsym Mod1+Shift+Down move down +bindsym Mod1+Shift+Up move up # Workspaces (Mod1+1/2/…) bindsym Mod1+1 workspace 1 From 481ae6ccf22cb87ddc05de05ddfba61a0c48e413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sun, 20 Feb 2011 10:44:58 -0300 Subject: [PATCH 457/867] Support pkg-config if the modules are available. --- common.mk | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/common.mk b/common.mk index 9ac6dcd2..a9bab084 100644 --- a/common.mk +++ b/common.mk @@ -11,6 +11,11 @@ endif GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f .git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' .git/HEAD || echo 'unknown'))" VERSION:=$(shell git describe --tags --abbrev=0) +# An easier way to get CFLAGS and LDFLAGS falling back in case there's +# no pkg-config support for certain libraries +cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1)) +ldflags_for_lib = $(shell pkg-config --exists $(1) && pkg-config --libs $(1) || echo -l$(2)) + CFLAGS += -std=c99 CFLAGS += -pipe CFLAGS += -Wall @@ -19,23 +24,36 @@ CFLAGS += -Wall CFLAGS += -Wunused-value CFLAGS += -Iinclude CFLAGS += -I/usr/local/include +CFLAGS += $(call cflags_for_lib, xcb-event) +CFLAGS += $(call cflags_for_lib, xcb-property) +CFLAGS += $(call cflags_for_lib, xcb-keysyms) +CFLAGS += $(call cflags_for_lib, xcb-atom) +CFLAGS += $(call cflags_for_lib, xcb-aux) +CFLAGS += $(call cflags_for_lib, xcb-icccm) +CFLAGS += $(call cflags_for_lib, xcb-xinerama) +CFLAGS += $(call cflags_for_lib, xcb-randr) +CFLAGS += $(call cflags_for_lib, xcb) +CFLAGS += $(call cflags_for_lib, xcursor) +CFLAGS += $(call cflags_for_lib, x11) +CFLAGS += $(call cflags_for_lib, yajl) +CFLAGS += $(call cflags_for_lib, libev) CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" CFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" LDFLAGS += -lm -LDFLAGS += -lxcb-event -LDFLAGS += -lxcb-property -LDFLAGS += -lxcb-keysyms -LDFLAGS += -lxcb-atom -LDFLAGS += -lxcb-aux -LDFLAGS += -lxcb-icccm -LDFLAGS += -lxcb-xinerama -LDFLAGS += -lxcb-randr -LDFLAGS += -lxcb -LDFLAGS += -lyajl -LDFLAGS += -lXcursor -LDFLAGS += -lX11 -LDFLAGS += -lev +LDFLAGS += $(call ldflags_for_lib, xcb-event, xcb-event) +LDFLAGS += $(call ldflags_for_lib, xcb-property, xcb-property) +LDFLAGS += $(call ldflags_for_lib, xcb-keysyms, xcb-keysyms) +LDFLAGS += $(call ldflags_for_lib, xcb-atom, xcb-atom) +LDFLAGS += $(call ldflags_for_lib, xcb-aux, xcb-aux) +LDFLAGS += $(call ldflags_for_lib, xcb-icccm, xcb-icccm) +LDFLAGS += $(call ldflags_for_lib, xcb-xinerama, xcb-xinerama) +LDFLAGS += $(call ldflags_for_lib, xcb-randr, xcb-randr) +LDFLAGS += $(call ldflags_for_lib, xcb, xcb) +LDFLAGS += $(call ldflags_for_lib, xcursor, Xcursor) +LDFLAGS += $(call ldflags_for_lib, x11, X11) +LDFLAGS += $(call ldflags_for_lib, yajl, yajl) +LDFLAGS += $(call ldflags_for_lib, libev, ev) LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib ifeq ($(UNAME),NetBSD) From 7f89c716891c8e8c01d9289d6ceb668d742864b1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Feb 2011 23:43:03 +0100 Subject: [PATCH 458/867] Implement dock mode, update testsuite Currently, dock clients are only possible at the top. --- include/con.h | 2 + include/data.h | 29 +++++++++--- include/tree.h | 2 +- src/cmdparse.y | 2 +- src/con.c | 14 ++++-- src/ipc.c | 63 +++++++++++++------------ src/manage.c | 14 +++++- src/randr.c | 36 ++++++++++++++- src/render.c | 91 ++++++++++++++++++++++++++++++++++++- src/tree.c | 10 ++-- src/workspace.c | 32 +++++++++---- src/x.c | 2 +- testcases/t/02-fullscreen.t | 10 +++- testcases/t/16-nestedcons.t | 6 ++- testcases/t/lib/i3test.pm | 29 +++++++----- 15 files changed, 269 insertions(+), 73 deletions(-) diff --git a/include/con.h b/include/con.h index 0d4df204..e8944b62 100644 --- a/include/con.h +++ b/include/con.h @@ -182,6 +182,8 @@ Rect con_border_style_rect(Con *con); * borderless and the only element in the tabbed container, the border is not * rendered. * + * For children of a CT_DOCKAREA, the border style is always none. + * */ int con_border_style(Con *con); diff --git a/include/data.h b/include/data.h index 5c4a8852..e8a796cc 100644 --- a/include/data.h +++ b/include/data.h @@ -263,23 +263,40 @@ struct Match { enum { M_USER = 0, M_RESTART } source; - /* wo das fenster eingefügt werden soll. bei here wird es direkt - * diesem Con zugewiesen, also layout saving. bei active ist es - * ein assignment, welches an der momentan fokussierten stelle einfügt */ - enum { M_HERE = 0, M_ACTIVE } insert_where; + /* Where the window looking for a match should be inserted: + * + * M_HERE = the matched container will be replaced by the window + * (layout saving) + * M_ACTIVE = the window will be inserted next to the currently focused + * container below the matched container + * (assignments) + * M_BELOW = the window will be inserted as a child of the matched container + * (dockareas) + * + */ + enum { M_HERE = 0, M_ACTIVE, M_BELOW } insert_where; TAILQ_ENTRY(Match) matches; }; struct Con { bool mapped; - enum { CT_ROOT = 0, CT_OUTPUT = 1, CT_CON = 2, CT_FLOATING_CON = 3, CT_WORKSPACE = 4 } type; + enum { + CT_ROOT = 0, + CT_OUTPUT = 1, + CT_CON = 2, + CT_FLOATING_CON = 3, + CT_WORKSPACE = 4, + CT_DOCKAREA = 5 + } type; orientation_t orientation; struct Con *parent; struct Rect rect; struct Rect window_rect; struct Rect deco_rect; + /** the geometry this window requested when getting mapped */ + struct Rect geometry; char *name; @@ -332,7 +349,7 @@ struct Con { TAILQ_HEAD(swallow_head, Match) swallow_head; enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode; - enum { L_DEFAULT = 0, L_STACKED = 1, L_TABBED = 2 } layout; + enum { L_DEFAULT = 0, L_STACKED = 1, L_TABBED = 2, L_DOCKAREA = 3, L_OUTPUT = 4 } layout; border_style_t border_style; /** floating? (= not in tiling layout) This cannot be simply a bool * because we want to keep track of whether the status was set by the diff --git a/include/tree.h b/include/tree.h index 40d9a541..cdcb4878 100644 --- a/include/tree.h +++ b/include/tree.h @@ -24,7 +24,7 @@ void tree_init(); * Opens an empty container in the current container * */ -Con *tree_open_con(Con *con); +Con *tree_open_con(Con *con, bool focus_it); /** * Splits (horizontally or vertically) the given container by creating a new diff --git a/src/cmdparse.y b/src/cmdparse.y index d71773fe..33724264 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -403,7 +403,7 @@ open: TOK_OPEN { printf("opening new container\n"); - Con *con = tree_open_con(NULL); + Con *con = tree_open_con(NULL, true); asprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con); } ; diff --git a/src/con.c b/src/con.c index 8992d132..b1e652a2 100644 --- a/src/con.c +++ b/src/con.c @@ -226,7 +226,6 @@ Con *con_get_workspace(Con *con) { Con *result = con; while (result != NULL && result->type != CT_WORKSPACE) result = result->parent; - assert(result != NULL); return result; } @@ -694,6 +693,8 @@ Rect con_border_style_rect(Con *con) { * borderless and the only element in the tabbed container, the border is not * rendered. * + * For children of a CT_DOCKAREA, the border style is always none. + * */ int con_border_style(Con *con) { Con *fs = con_get_fullscreen_con(con->parent); @@ -708,6 +709,9 @@ int con_border_style(Con *con) { if (con->parent->layout == L_TABBED && con->border_style != BS_NORMAL) return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL); + if (con->parent->type == CT_DOCKAREA) + return BS_NONE; + return con->border_style; } @@ -773,8 +777,12 @@ void con_set_layout(Con *con, int layout) { static void con_on_remove_child(Con *con) { DLOG("on_remove_child\n"); - /* Nothing to do for workspaces */ - if (con->type == CT_WORKSPACE || con->type == CT_OUTPUT || con->type == CT_ROOT) { + /* Every container 'above' (in the hierarchy) the workspace content should + * not be closed when the last child was removed */ + if (con->type == CT_WORKSPACE || + con->type == CT_OUTPUT || + con->type == CT_ROOT || + con->type == CT_DOCKAREA) { DLOG("not handling, type = %d\n", con->type); return; } diff --git a/src/ipc.c b/src/ipc.c index a0dd64cc..fe1b24a8 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -288,44 +288,47 @@ IPC_HANDLER(get_workspaces) { Con *output; TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - Con *ws; - TAILQ_FOREACH(ws, &(output->nodes_head), nodes) { - assert(ws->type == CT_WORKSPACE); - y(map_open); + Con *child; + TAILQ_FOREACH(child, &(output->nodes_head), nodes) { + Con *ws; + TAILQ_FOREACH(ws, &(child->nodes_head), nodes) { + assert(ws->type == CT_WORKSPACE); + y(map_open); - ystr("num"); - if (ws->num == -1) - y(null); - else y(integer, ws->num); + ystr("num"); + if (ws->num == -1) + y(null); + else y(integer, ws->num); - ystr("name"); - ystr(ws->name); + ystr("name"); + ystr(ws->name); - ystr("visible"); - y(bool, workspace_is_visible(ws)); + ystr("visible"); + y(bool, workspace_is_visible(ws)); - ystr("focused"); - y(bool, ws == focused_ws); + ystr("focused"); + y(bool, ws == focused_ws); - ystr("rect"); - y(map_open); - ystr("x"); - y(integer, ws->rect.x); - ystr("y"); - y(integer, ws->rect.y); - ystr("width"); - y(integer, ws->rect.width); - ystr("height"); - y(integer, ws->rect.height); - y(map_close); + ystr("rect"); + y(map_open); + ystr("x"); + y(integer, ws->rect.x); + ystr("y"); + y(integer, ws->rect.y); + ystr("width"); + y(integer, ws->rect.width); + ystr("height"); + y(integer, ws->rect.height); + y(map_close); - ystr("output"); - ystr(output->name); + ystr("output"); + ystr(output->name); - ystr("urgent"); - y(bool, ws->urgent); + ystr("urgent"); + y(bool, ws->urgent); - y(map_close); + y(map_close); + } } } diff --git a/src/manage.c b/src/manage.c index a3bad45f..0c0dcae7 100644 --- a/src/manage.c +++ b/src/manage.c @@ -167,8 +167,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki LOG("using current container, focused = %p, focused->name = %s\n", focused, focused->name); nc = focused; - } else nc = tree_open_con(NULL); + } else nc = tree_open_con(NULL, true); } else { + /* M_ACTIVE are assignments */ if (match != NULL && match->insert_where == M_ACTIVE) { /* We need to go down the focus stack starting from nc */ while (TAILQ_FIRST(&(nc->focus_head)) != TAILQ_END(&(nc->focus_head))) { @@ -178,9 +179,16 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* We need to open a new con */ /* TODO: make a difference between match-once containers (directly assign * cwindow) and match-multiple (tree_open_con first) */ - nc = tree_open_con(nc->parent); + nc = tree_open_con(nc->parent, true); + } + + /* M_BELOW inserts the new window as a child of the one which was + * matched (e.g. dock areas) */ + else if (match != NULL && match->insert_where == M_BELOW) { + nc = tree_open_con(nc, !cwindow->dock); } } + DLOG("new container = %p\n", nc); nc->window = cwindow; x_reinit(nc); @@ -208,6 +216,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki con_by_window_id(cwindow->leader) != NULL)) want_floating = true; + nc->geometry = (Rect){ geom->x, geom->y, geom->width, geom->height }; + if (want_floating) { nc->rect.x = geom->x; nc->rect.y = geom->y; diff --git a/src/randr.c b/src/randr.c index e8b044bb..9abe9c7c 100644 --- a/src/randr.c +++ b/src/randr.c @@ -250,6 +250,7 @@ void output_init_con(Output *output) { FREE(con->name); con->name = sstrdup(output->name); con->type = CT_OUTPUT; + con->layout = L_OUTPUT; } con->rect = output->rect; output->con = con; @@ -257,12 +258,43 @@ void output_init_con(Output *output) { char *name; asprintf(&name, "[i3 con] output %s", con->name); x_set_name(con, name); - free(name); + FREE(name); if (reused) { DLOG("Not adding workspace, this was a reused con\n"); return; } + + DLOG("Changing layout, adding top/bottom dockarea\n"); + Con *topdock = con_new(NULL); + topdock->type = CT_DOCKAREA; + topdock->layout = L_DOCKAREA; + topdock->orientation = VERT; + /* this container swallows dock clients */ + Match *match = scalloc(sizeof(Match)); + match_init(match); + match->dock = true; + match->insert_where = M_BELOW; + TAILQ_INSERT_TAIL(&(topdock->swallow_head), match, matches); + + topdock->name = sstrdup("topdock"); + + asprintf(&name, "[i3 con] top dockarea %s", con->name); + x_set_name(topdock, name); + FREE(name); + DLOG("attaching\n"); + con_attach(topdock, con, false); + + DLOG("adding main content container\n"); + Con *content = con_new(NULL); + content->type = CT_CON; + content->name = sstrdup("content"); + + asprintf(&name, "[i3 con] content %s", con->name); + x_set_name(content, name); + FREE(name); + con_attach(content, con, false); + DLOG("Now adding a workspace\n"); /* add a workspace to this output */ @@ -295,7 +327,7 @@ void output_init_con(Output *output) { DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists); } ws->num = c; - con_attach(ws, con, false); + con_attach(ws, content, false); asprintf(&name, "[i3 con] workspace %s", ws->name); x_set_name(ws, name); diff --git a/src/render.c b/src/render.c index c6ebb5cc..c8ea3184 100644 --- a/src/render.c +++ b/src/render.c @@ -8,6 +8,73 @@ * container (for debugging purposes) */ static bool show_debug_borders = false; +/* + * Renders a container with layout L_OUTPUT. In this layout, all CT_DOCKAREAs + * get the height of their content and the remaining CT_CON gets the rest. + * + */ +static void render_l_output(Con *con) { + Con *child, *dockchild; + + int x = con->rect.x; + int y = con->rect.y; + int height = con->rect.height; + DLOG("Available height: %d\n", height); + + /* First pass: determine the height of all CT_DOCKAREAs (the sum of their + * children) and figure out how many pixels we have left for the rest */ + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + if (child->type != CT_DOCKAREA) + continue; + + child->rect.height = 0; + TAILQ_FOREACH(dockchild, &(child->nodes_head), nodes) + child->rect.height += dockchild->geometry.height; + DLOG("This dockarea's height: %d\n", child->rect.height); + + height -= child->rect.height; + } + + DLOG("Remaining: %d\n", height); + + /* Second pass: Set the widths/heights */ + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + if (child->type == CT_CON) { + if (height == -1) { + DLOG("More than one CT_CON on output container\n"); + assert(false); + } + child->rect.x = x; + child->rect.y = y; + child->rect.width = con->rect.width; + child->rect.height = height; + height = -1; + } + + else if (child->type != CT_DOCKAREA) { + DLOG("Child %p of type %d is inside the OUTPUT con\n", child, child->type); + assert(false); + } + + child->rect.x = x; + child->rect.y = y; + child->rect.width = con->rect.width; + + child->deco_rect.x = 0; + child->deco_rect.y = 0; + child->deco_rect.width = 0; + child->deco_rect.height = 0; + + y += child->rect.height; + + DLOG("child at (%d, %d) with (%d x %d)\n", + child->rect.x, child->rect.y, child->rect.width, child->rect.height); + DLOG("x now %d, y now %d\n", x, y); + x_raise_con(child); + render_con(child, false); + } +} + /* * "Renders" the given container (and its children), meaning that all rects are * updated correctly. Note that this function does not call any xcb_* @@ -95,7 +162,7 @@ void render_con(Con *con, bool render_fullscreen) { } /* Check for fullscreen nodes */ - Con *fullscreen = con_get_fullscreen_con(con); + Con *fullscreen = (con->type == CT_OUTPUT ? NULL : con_get_fullscreen_con(con)); if (fullscreen) { DLOG("got fs node: %p\n", fullscreen); fullscreen->rect = rect; @@ -130,6 +197,11 @@ void render_con(Con *con, bool render_fullscreen) { } } + if (con->layout == L_OUTPUT) { + render_l_output(con); + } else { + + /* FIXME: refactor this into separate functions: */ Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) { @@ -202,6 +274,21 @@ void render_con(Con *con, bool render_fullscreen) { } } + /* dockarea layout */ + else if (con->layout == L_DOCKAREA) { + DLOG("dockarea con\n"); + child->rect.x = x; + child->rect.y = y; + child->rect.width = rect.width; + child->rect.height = child->geometry.height; + + child->deco_rect.x = 0; + child->deco_rect.y = 0; + child->deco_rect.width = 0; + child->deco_rect.height = 0; + y += child->rect.height; + } + DLOG("child at (%d, %d) with (%d x %d)\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height); DLOG("x now %d, y now %d\n", x, y); @@ -221,7 +308,9 @@ void render_con(Con *con, bool render_fullscreen) { render_con(foc, false); } } + } + Con *child; TAILQ_FOREACH(child, &(con->floating_head), floating_windows) { DLOG("render floating:\n"); DLOG("floating child at (%d,%d) with %d x %d\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height); diff --git a/src/tree.c b/src/tree.c index c741e65a..3900b862 100644 --- a/src/tree.c +++ b/src/tree.c @@ -55,18 +55,21 @@ void tree_init() { * Opens an empty container in the current container * */ -Con *tree_open_con(Con *con) { +Con *tree_open_con(Con *con, bool focus_it) { if (con == NULL) { /* every focusable Con has a parent (outputs have parent root) */ con = focused->parent; /* If the parent is an output, we are on a workspace. In this case, * the new container needs to be opened as a leaf of the workspace. */ - if (con->type == CT_OUTPUT) + if (con->parent->type == CT_OUTPUT && con->type != CT_DOCKAREA) { con = focused; + } + /* If the currently focused container is a floating container, we * attach the new container to the workspace */ if (con->type == CT_FLOATING_CON) con = con->parent; + DLOG("con = %p\n", con); } assert(con != NULL); @@ -78,7 +81,8 @@ Con *tree_open_con(Con *con) { con_fix_percent(con); /* 5: focus the new container */ - con_focus(new); + if (focus_it) + con_focus(new); return new; } diff --git a/src/workspace.c b/src/workspace.c index 4fcdd747..d5875f99 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -18,18 +18,23 @@ * */ Con *workspace_get(const char *num) { - Con *output, *workspace = NULL, *current; + Con *output, *workspace = NULL, *current, *child; /* TODO: could that look like this in the future? GET_MATCHING_NODE(workspace, croot, strcasecmp(current->name, num) != 0); */ TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { TAILQ_FOREACH(current, &(output->nodes_head), nodes) { - if (strcasecmp(current->name, num) != 0) + if (current->type != CT_CON) continue; - workspace = current; - break; + TAILQ_FOREACH(child, &(current->nodes_head), nodes) { + if (strcasecmp(child->name, num) != 0) + continue; + + workspace = child; + break; + } } } @@ -37,7 +42,15 @@ Con *workspace_get(const char *num) { if (workspace == NULL) { LOG("need to create this one\n"); output = con_get_output(focused); - LOG("got output %p\n", output); + Con *child, *content = NULL; + TAILQ_FOREACH(child, &(output->nodes_head), nodes) { + if (child->type == CT_CON) { + content = child; + break; + } + } + assert(content != NULL); + LOG("got output %p with child %p\n", output, content); /* We need to attach this container after setting its type. con_attach * will handle CT_WORKSPACEs differently */ workspace = con_new(NULL); @@ -60,7 +73,7 @@ Con *workspace_get(const char *num) { else workspace->num = parsed_num; LOG("num = %d\n", workspace->num); workspace->orientation = HORIZ; - con_attach(workspace, output, false); + con_attach(workspace, content, false); ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); } @@ -214,8 +227,9 @@ void workspace_show(const char *num) { /* Check if the the currently focused con is on the same Output as the * workspace we chose as 'old'. If not, use the workspace of the currently * focused con */ - if (con_get_workspace(focused)->parent != old->parent) - old = con_get_workspace(focused); + Con *ws = con_get_workspace(focused); + if (ws && ws->parent != old->parent) + old = ws; /* enable fullscreen for the target workspace. If it happens to be the * same one we are currently on anyways, we can stop here. */ @@ -228,7 +242,7 @@ void workspace_show(const char *num) { LOG("switching to %p\n", workspace); Con *next = con_descend_focused(workspace); - if (TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) { + if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) { /* check if this workspace is currently visible */ if (!workspace_is_visible(old)) { LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); diff --git a/src/x.c b/src/x.c index d35c8c6a..5a13b51a 100644 --- a/src/x.c +++ b/src/x.c @@ -342,7 +342,7 @@ void x_draw_decoration(Con *con) { DLOG("il_parent = %p, layout = %d\n", il_parent, il_parent->layout); if (il_parent->layout == L_STACKED) indent_level++; - if (il_parent->type == CT_WORKSPACE) + if (il_parent->type == CT_WORKSPACE || il_parent->type == CT_DOCKAREA || il_parent->type == CT_OUTPUT) break; il_parent = il_parent->parent; indent_mult++; diff --git a/testcases/t/02-fullscreen.t b/testcases/t/02-fullscreen.t index e13bff63..27ae8411 100644 --- a/testcases/t/02-fullscreen.t +++ b/testcases/t/02-fullscreen.t @@ -18,7 +18,15 @@ sub fullscreen_windows { # get the output of this workspace my $tree = $i3->get_tree->recv; my @outputs = @{$tree->{nodes}}; -my $output = first { defined(first { $_->{name} eq $tmp } @{$_->{nodes}}) } @outputs; +my $output; +for my $o (@outputs) { + # get the first CT_CON of each output + my $content = first { $_->{type} == 2 } @{$o->{nodes}}; + if (defined(first { $_->{name} eq $tmp } @{$content->{nodes}})) { + $output = $o; + last; + } +} BEGIN { use_ok('X11::XCB::Window'); diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t index 365b0054..f40ec68e 100644 --- a/testcases/t/16-nestedcons.t +++ b/testcases/t/16-nestedcons.t @@ -3,6 +3,7 @@ use i3test tests => 7; use List::MoreUtils qw(all none); +use List::Util qw(first); my $i3 = i3("/tmp/nestedcons"); @@ -41,8 +42,9 @@ ok((all { $_->{type} == 1 } @nodes), 'all nodes are of type CT_OUTPUT'); ok((none { defined($_->{window}) } @nodes), 'no CT_OUTPUT contains a window'); ok((all { @{$_->{nodes}} > 0 } @nodes), 'all nodes have at least one leaf (workspace)'); my @workspaces; -for my $ws (map { @{$_->{nodes}} } @nodes) { - push @workspaces, $ws; +for my $ws (@nodes) { + my $content = first { $_->{type} == 2 } @{$ws->{nodes}}; + @workspaces = (@workspaces, @{$content->{nodes}}); } ok((all { $_->{type} == 4 } @workspaces), 'all workspaces are of type CT_WORKSPACE'); diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index e53bb4db..c1bb7ed3 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -65,10 +65,15 @@ sub open_empty_con { sub get_workspace_names { my $i3 = i3("/tmp/nestedcons"); - # TODO: use correct command as soon as AnyEvent::i3 is updated my $tree = $i3->get_tree->recv; - my @workspaces = map { @{$_->{nodes}} } @{$tree->{nodes}}; - [ map { $_->{name} } @workspaces ] + my @outputs = @{$tree->{nodes}}; + my @cons; + for my $output (@outputs) { + # get the first CT_CON of each output + my $content = first { $_->{type} == 2 } @{$output->{nodes}}; + @cons = (@cons, @{$content->{nodes}}); + } + [ map { $_->{name} } @cons ] } sub get_unused_workspace { @@ -82,11 +87,18 @@ sub get_ws { my ($name) = @_; my $i3 = i3("/tmp/nestedcons"); my $tree = $i3->get_tree->recv; - my @ws = map { @{$_->{nodes}} } @{$tree->{nodes}}; + + my @outputs = @{$tree->{nodes}}; + my @workspaces; + for my $output (@outputs) { + # get the first CT_CON of each output + my $content = first { $_->{type} == 2 } @{$output->{nodes}}; + @workspaces = (@workspaces, @{$content->{nodes}}); + } # as there can only be one workspace with this name, we can safely # return the first entry - return first { $_->{name} eq $name } @ws; + return first { $_->{name} eq $name } @workspaces; } # @@ -102,12 +114,7 @@ sub get_ws_content { sub get_focused { my ($ws) = @_; - my $i3 = i3("/tmp/nestedcons"); - my $tree = $i3->get_tree->recv; - - my @ws = map { @{$_->{nodes}} } @{$tree->{nodes}}; - my @cons = grep { $_->{name} eq $ws } @ws; - my $con = $cons[0]; + my $con = get_ws($ws); my @focused = @{$con->{focus}}; my $lf; From bafb065d7c985a377c223fc7c2ef1631853d6861 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Feb 2011 23:50:01 +0100 Subject: [PATCH 459/867] =?UTF-8?q?tests:=20don=E2=80=99t=20skip=20t/10-do?= =?UTF-8?q?ck.t,=20make=20it=20work=20again?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- testcases/t/10-dock.t | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/testcases/t/10-dock.t b/testcases/t/10-dock.t index f53626a5..66a05b26 100644 --- a/testcases/t/10-dock.t +++ b/testcases/t/10-dock.t @@ -10,9 +10,6 @@ BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } -SKIP: { - skip "Dock clients not yet implemented", 1; - my $x = X11::XCB::Connection->new; ##################################################################### @@ -30,7 +27,7 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30], background_color => '#FF0000', - type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), ); $window->map; @@ -44,7 +41,7 @@ my $fwindow = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30], background_color => '#FF0000', - type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), ); $fwindow->transient_for($window); @@ -54,4 +51,3 @@ sleep 0.25; diag( "Testing i3, Perl $], $^X" ); -} From 9a0bc77bafd4df4f68e8f8a656118ab4cd88801b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Feb 2011 00:23:07 +0100 Subject: [PATCH 460/867] bugfix: dock clients cannot be floating --- src/manage.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/manage.c b/src/manage.c index 0c0dcae7..6b77ec9d 100644 --- a/src/manage.c +++ b/src/manage.c @@ -216,6 +216,10 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki con_by_window_id(cwindow->leader) != NULL)) want_floating = true; + /* dock clients cannot be floating, that makes no sense */ + if (cwindow->dock) + want_floating = false; + nc->geometry = (Rect){ geom->x, geom->y, geom->width, geom->height }; if (want_floating) { From 95e8b1a46783426b4bd3101eff5774da4f496e32 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Feb 2011 00:23:25 +0100 Subject: [PATCH 461/867] tests: extend t/10-dock.t to make use of the tree --- testcases/t/10-dock.t | 50 +++++++++++++++++++++++++++++++++++++-- testcases/t/lib/i3test.pm | 2 +- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/testcases/t/10-dock.t b/testcases/t/10-dock.t index 66a05b26..dabb7bd8 100644 --- a/testcases/t/10-dock.t +++ b/testcases/t/10-dock.t @@ -1,7 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 2; +use i3test; use X11::XCB qw(:all); use Time::HiRes qw(sleep); use List::Util qw(first); @@ -11,6 +11,23 @@ BEGIN { } my $x = X11::XCB::Connection->new; +my $i3 = i3("/tmp/nestedcons"); + +##################################################################### +# verify that there is no dock window yet +##################################################################### + +my $tree = $i3->get_tree->recv; +my @outputs = @{$tree->{nodes}}; +# Children of all dockareas +my @docked; +for my $output (@outputs) { + @docked = (@docked, map { @{$_->{nodes}} } + grep { $_->{type} == 5 } + @{$output->{nodes}}); +} + +is(@docked, 0, 'no dock clients yet'); ##################################################################### # Create a dock window and see if it gets managed @@ -36,6 +53,34 @@ sleep 0.25; my $rect = $window->rect; is($rect->width, $primary->rect->width, 'dock client is as wide as the screen'); +is($rect->height, 30, 'height is unchanged'); + +##################################################################### +# check that we can find it in the layout tree at the expected position +##################################################################### + +$tree = $i3->get_tree->recv; +@outputs = @{$tree->{nodes}}; +@docked; +for my $output (@outputs) { + @docked = (@docked, map { @{$_->{nodes}} } + grep { $_->{type} == 5 } + @{$output->{nodes}}); +} + +is(@docked, 1, 'one dock client found'); + +# verify the position/size +my $docknode = $docked[0]; + +is($docknode->{rect}->{x}, 0, 'dock node placed at x=0'); +is($docknode->{rect}->{y}, 0, 'dock node placed at y=0'); +is($docknode->{rect}->{width}, $primary->rect->width, 'dock node as wide as the screen'); +is($docknode->{rect}->{height}, 30, 'dock node has unchanged height'); + +##################################################################### +# regression test: transient dock client +##################################################################### my $fwindow = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, @@ -49,5 +94,6 @@ $fwindow->map; sleep 0.25; +does_i3_live; -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index c1bb7ed3..7f6c0614 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -26,7 +26,7 @@ sub import { my $class = shift; my $pkg = caller; eval "package $pkg; -use Test::More qw(@_); +use Test::More" . (@_ > 0 ? " qw(@_)" : "") . "; use Test::Exception; use Data::Dumper; use AnyEvent::I3; From 272ab840c78801c76f2759a0dcff1ca238d23589 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Feb 2011 00:40:35 +0100 Subject: [PATCH 462/867] Fix fullscreen mode with dock clients --- src/render.c | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/render.c b/src/render.c index c8ea3184..9e2fd3db 100644 --- a/src/render.c +++ b/src/render.c @@ -21,6 +21,37 @@ static void render_l_output(Con *con) { int height = con->rect.height; DLOG("Available height: %d\n", height); + /* Find the content container and ensure that there is exactly one. Also + * check for any non-CT_DOCKAREA clients. */ + Con *content = NULL; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + if (child->type == CT_CON) { + if (content != NULL) { + DLOG("More than one CT_CON on output container\n"); + assert(false); + } + content = child; + } else if (child->type != CT_DOCKAREA) { + DLOG("Child %p of type %d is inside the OUTPUT con\n", child, child->type); + assert(false); + } + } + + assert(content != NULL); + + /* We need to find out if there is a fullscreen con on the current workspace + * and take the short-cut to render it directly (the user does not want to + * see the dockareas in that case) */ + Con *ws = con_get_fullscreen_con(content); + Con *fullscreen = con_get_fullscreen_con(ws); + if (fullscreen) { + DLOG("got fs node: %p\n", fullscreen); + fullscreen->rect = con->rect; + x_raise_con(fullscreen); + render_con(fullscreen, true); + return; + } + /* First pass: determine the height of all CT_DOCKAREAs (the sum of their * children) and figure out how many pixels we have left for the rest */ TAILQ_FOREACH(child, &(con->nodes_head), nodes) { @@ -40,20 +71,10 @@ static void render_l_output(Con *con) { /* Second pass: Set the widths/heights */ TAILQ_FOREACH(child, &(con->nodes_head), nodes) { if (child->type == CT_CON) { - if (height == -1) { - DLOG("More than one CT_CON on output container\n"); - assert(false); - } child->rect.x = x; child->rect.y = y; child->rect.width = con->rect.width; child->rect.height = height; - height = -1; - } - - else if (child->type != CT_DOCKAREA) { - DLOG("Child %p of type %d is inside the OUTPUT con\n", child, child->type); - assert(false); } child->rect.x = x; From 6b272fea556b9b0f750b34980325f2ccf786863b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Feb 2011 00:54:29 +0100 Subject: [PATCH 463/867] Bugfix: in get_workspaces, only consider the CT_CON, not the CT_DOCKAREAs (Thanks fernandotcl) --- src/ipc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ipc.c b/src/ipc.c index fe1b24a8..b80eae12 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -290,6 +290,9 @@ IPC_HANDLER(get_workspaces) { TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { Con *child; TAILQ_FOREACH(child, &(output->nodes_head), nodes) { + if (child->type != CT_CON) + continue; + Con *ws; TAILQ_FOREACH(ws, &(child->nodes_head), nodes) { assert(ws->type == CT_WORKSPACE); From a33d8698852ae5771001f19a71fa5ffa6aee6e05 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Feb 2011 01:12:22 +0100 Subject: [PATCH 464/867] Bugfix: Correctly open workspaces on additional outputs --- src/randr.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/randr.c b/src/randr.c index 9abe9c7c..a49675cc 100644 --- a/src/randr.c +++ b/src/randr.c @@ -306,7 +306,7 @@ void output_init_con(Output *output) { int c = 0; bool exists = true; while (exists) { - Con *out, *current; + Con *out, *current, *child; c++; @@ -316,11 +316,16 @@ void output_init_con(Output *output) { exists = false; TAILQ_FOREACH(out, &(croot->nodes_head), nodes) { TAILQ_FOREACH(current, &(out->nodes_head), nodes) { - if (strcasecmp(current->name, ws->name) != 0) + if (current->type != CT_CON) continue; - exists = true; - break; + TAILQ_FOREACH(child, &(current->nodes_head), nodes) { + if (strcasecmp(child->name, ws->name) != 0) + continue; + + exists = true; + break; + } } } From 35e79c87c8cf4fa621d5a82141909c0a929f6164 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Feb 2011 01:28:29 +0100 Subject: [PATCH 465/867] Place dock clients on the output corresponding to their geometry request --- include/con.h | 4 ++-- src/con.c | 31 ++++++++++++++++++++++++------- src/manage.c | 12 +++++++++++- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/include/con.h b/include/con.h index e8944b62..8faf43fc 100644 --- a/include/con.h +++ b/include/con.h @@ -83,11 +83,11 @@ Con *con_by_window_id(xcb_window_t window); Con *con_by_frame_id(xcb_window_t frame); /** - * Returns the first container which wants to swallow this window + * Returns the first container below 'con' which wants to swallow this window * TODO: priority * */ -Con *con_for_window(i3Window *window, Match **store_match); +Con *con_for_window(Con *con, i3Window *window, Match **store_match); /** * Returns the number of children of this container. diff --git a/src/con.c b/src/con.c index b1e652a2..31affbbc 100644 --- a/src/con.c +++ b/src/con.c @@ -365,24 +365,41 @@ Con *con_by_frame_id(xcb_window_t frame) { } /* - * Returns the first container which wants to swallow this window + * Returns the first container below 'con' which wants to swallow this window * TODO: priority * */ -Con *con_for_window(i3Window *window, Match **store_match) { - Con *con; +Con *con_for_window(Con *con, i3Window *window, Match **store_match) { + Con *child; Match *match; - DLOG("searching con for window %p\n", window); + DLOG("searching con for window %p starting at con %p\n", window, con); DLOG("class == %s\n", window->class_class); - TAILQ_FOREACH(con, &all_cons, all_cons) - TAILQ_FOREACH(match, &(con->swallow_head), matches) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(match, &(child->swallow_head), matches) { if (!match_matches_window(match, window)) continue; if (store_match != NULL) *store_match = match; - return con; + return child; } + Con *result = con_for_window(child, window, store_match); + if (result != NULL) + return result; + } + + TAILQ_FOREACH(child, &(con->floating_head), floating_windows) { + TAILQ_FOREACH(match, &(child->swallow_head), matches) { + if (!match_matches_window(match, window)) + continue; + if (store_match != NULL) + *store_match = match; + return child; + } + Con *result = con_for_window(child, window, store_match); + if (result != NULL) + return result; + } return NULL; } diff --git a/src/manage.c b/src/manage.c index 6b77ec9d..5b60e44c 100644 --- a/src/manage.c +++ b/src/manage.c @@ -160,8 +160,18 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* TODO: assignments */ /* TODO: two matches for one container */ + /* See if any container swallows this new window */ - nc = con_for_window(cwindow, &match); + Con *search_at = croot; + if (cwindow->dock) { + /* for dock windows, we start the search at the appropriate output */ + Output *output = get_output_containing(geom->x, geom->y); + if (output != NULL) { + DLOG("Starting search at output %s\n", output->name); + search_at = output->con; + } + } + nc = con_for_window(search_at, cwindow, &match); if (nc == NULL) { if (focused->type == CT_CON && con_accepts_window(focused)) { LOG("using current container, focused = %p, focused->name = %s\n", From a92b9dca73e220ee6475aabada64fd807aced7c0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Feb 2011 01:43:39 +0100 Subject: [PATCH 466/867] Bugfix: fix disabling RandR outputs --- Makefile | 2 +- include/all.h | 1 + include/output.h | 14 ++++++++++++++ src/output.c | 20 ++++++++++++++++++++ src/randr.c | 11 +++++++---- 5 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 include/output.h create mode 100644 src/output.c diff --git a/Makefile b/Makefile index 25da180e..5a0dd76f 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c -FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c src/move.c +FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c src/move.c src/output.c FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) diff --git a/include/all.h b/include/all.h index dd2b9365..23c7fe36 100644 --- a/include/all.h +++ b/include/all.h @@ -55,5 +55,6 @@ #include "resize.h" #include "sighandler.h" #include "move.h" +#include "output.h" #endif diff --git a/include/output.h b/include/output.h new file mode 100644 index 00000000..67652fa1 --- /dev/null +++ b/include/output.h @@ -0,0 +1,14 @@ +/* + * vim:ts=4:sw=4:expandtab + */ + +#ifndef _OUTPUT_H +#define _OUTPUT_H + +/** + * Returns the output container below the given output container. + * + */ +Con *output_get_content(Con *output); + +#endif diff --git a/src/output.c b/src/output.c new file mode 100644 index 00000000..68487924 --- /dev/null +++ b/src/output.c @@ -0,0 +1,20 @@ +/* + * vim:ts=4:sw=4:expandtab + */ + +#include "all.h" + +/* + * Returns the output container below the given output container. + * + */ +Con *output_get_content(Con *output) { + Con *child; + + TAILQ_FOREACH(child, &(output->nodes_head), nodes) + if (child->type == CT_CON) + return child; + + ELOG("output_get_content() called on non-output %p\n", output); + assert(false); +} diff --git a/src/randr.c b/src/randr.c index a49675cc..20e4f54e 100644 --- a/src/randr.c +++ b/src/randr.c @@ -584,7 +584,9 @@ void randr_query_outputs() { DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name); if ((first = get_first_output()) == NULL) - die("No usable outputs available\n"); + die("No usable outputs available\n"); + + Con *first_content = output_get_content(first->con); if (output->con != NULL) { /* We need to move the workspaces from the disappearing output to the first output */ @@ -598,12 +600,13 @@ void randr_query_outputs() { /* 2: iterate through workspaces and re-assign them */ Con *current; - while (!TAILQ_EMPTY(&(output->con->nodes_head))) { - current = TAILQ_FIRST(&(output->con->nodes_head)); + Con *old_content = output_get_content(output->con); + while (!TAILQ_EMPTY(&(old_content->nodes_head))) { + current = TAILQ_FIRST(&(old_content->nodes_head)); DLOG("Detaching current = %p / %s\n", current, current->name); con_detach(current); DLOG("Re-attaching current = %p / %s\n", current, current->name); - con_attach(current, first->con, false); + con_attach(current, first_content, false); DLOG("Done, next\n"); } DLOG("re-attached all workspaces\n"); From 9719b2124331d1d560a927a4e58b04f3aeb910d1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Feb 2011 01:55:36 +0100 Subject: [PATCH 467/867] refactor some places to use output_get_content() --- src/ipc.c | 66 ++++++++++++++++++++++--------------------------- src/workspace.c | 29 ++++++---------------- 2 files changed, 38 insertions(+), 57 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index b80eae12..c375ac0a 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -288,50 +288,44 @@ IPC_HANDLER(get_workspaces) { Con *output; TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - Con *child; - TAILQ_FOREACH(child, &(output->nodes_head), nodes) { - if (child->type != CT_CON) - continue; + Con *ws; + TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { + assert(ws->type == CT_WORKSPACE); + y(map_open); - Con *ws; - TAILQ_FOREACH(ws, &(child->nodes_head), nodes) { - assert(ws->type == CT_WORKSPACE); - y(map_open); + ystr("num"); + if (ws->num == -1) + y(null); + else y(integer, ws->num); - ystr("num"); - if (ws->num == -1) - y(null); - else y(integer, ws->num); + ystr("name"); + ystr(ws->name); - ystr("name"); - ystr(ws->name); + ystr("visible"); + y(bool, workspace_is_visible(ws)); - ystr("visible"); - y(bool, workspace_is_visible(ws)); + ystr("focused"); + y(bool, ws == focused_ws); - ystr("focused"); - y(bool, ws == focused_ws); + ystr("rect"); + y(map_open); + ystr("x"); + y(integer, ws->rect.x); + ystr("y"); + y(integer, ws->rect.y); + ystr("width"); + y(integer, ws->rect.width); + ystr("height"); + y(integer, ws->rect.height); + y(map_close); - ystr("rect"); - y(map_open); - ystr("x"); - y(integer, ws->rect.x); - ystr("y"); - y(integer, ws->rect.y); - ystr("width"); - y(integer, ws->rect.width); - ystr("height"); - y(integer, ws->rect.height); - y(map_close); + ystr("output"); + ystr(output->name); - ystr("output"); - ystr(output->name); + ystr("urgent"); + y(bool, ws->urgent); - ystr("urgent"); - y(bool, ws->urgent); - - y(map_close); - } + y(map_close); } } diff --git a/src/workspace.c b/src/workspace.c index d5875f99..2912278d 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -18,39 +18,26 @@ * */ Con *workspace_get(const char *num) { - Con *output, *workspace = NULL, *current, *child; + Con *output, *workspace = NULL, *child; /* TODO: could that look like this in the future? GET_MATCHING_NODE(workspace, croot, strcasecmp(current->name, num) != 0); */ - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - TAILQ_FOREACH(current, &(output->nodes_head), nodes) { - if (current->type != CT_CON) + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) + TAILQ_FOREACH(child, &(output_get_content(output)->nodes_head), nodes) { + if (strcasecmp(child->name, num) != 0) continue; - TAILQ_FOREACH(child, &(current->nodes_head), nodes) { - if (strcasecmp(child->name, num) != 0) - continue; - - workspace = child; - break; - } + workspace = child; + break; } - } LOG("getting ws %s\n", num); if (workspace == NULL) { LOG("need to create this one\n"); output = con_get_output(focused); - Con *child, *content = NULL; - TAILQ_FOREACH(child, &(output->nodes_head), nodes) { - if (child->type == CT_CON) { - content = child; - break; - } - } - assert(content != NULL); - LOG("got output %p with child %p\n", output, content); + Con *content = output_get_content(output); + LOG("got output %p with content %p\n", output, content); /* We need to attach this container after setting its type. con_attach * will handle CT_WORKSPACEs differently */ workspace = con_new(NULL); From 3dfe5c8a9a505ec7c82b8cefe3341e8ba3111584 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Feb 2011 01:58:57 +0100 Subject: [PATCH 468/867] bugfix: fix clicking on dock clients (Thanks mseed) --- src/con.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/con.c b/src/con.c index 31affbbc..07fb0c65 100644 --- a/src/con.c +++ b/src/con.c @@ -332,7 +332,7 @@ Con *con_inside_floating(Con *con) { if (con->floating >= FLOATING_AUTO_ON) return con->parent; - if (con->type == CT_WORKSPACE) + if (con->type == CT_WORKSPACE || con->type == CT_OUTPUT) return NULL; return con_inside_floating(con->parent); From f34b0456190c49d18f156e4fdf5c89084d726feb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Feb 2011 03:01:55 +0100 Subject: [PATCH 469/867] Fix dock client handling for inplace restarts --- src/ipc.c | 27 ++++++++++++++++++++++----- src/load_layout.c | 24 +++++++++++++++++++----- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index c375ac0a..a6f0dd5c 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -207,6 +207,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { dump_rect(gen, "rect", con->rect); dump_rect(gen, "window_rect", con->window_rect); + dump_rect(gen, "geometry", con->geometry); ystr("name"); ystr(con->name); @@ -224,8 +225,10 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("nodes"); y(array_open); Con *node; - TAILQ_FOREACH(node, &(con->nodes_head), nodes) { - dump_node(gen, node, inplace_restart); + if (con->type != CT_DOCKAREA || !inplace_restart) { + TAILQ_FOREACH(node, &(con->nodes_head), nodes) { + dump_node(gen, node, inplace_restart); + } } y(array_close); @@ -246,17 +249,31 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("fullscreen_mode"); y(integer, con->fullscreen_mode); + ystr("swallows"); + y(array_open); + Match *match; + TAILQ_FOREACH(match, &(con->swallow_head), matches) { + if (match->dock != -1) { + y(map_open); + ystr("dock"); + y(integer, match->dock); + ystr("insert_where"); + y(integer, match->insert_where); + y(map_close); + } + + /* TODO: the other swallow keys */ + } + if (inplace_restart) { if (con->window != NULL) { - ystr("swallows"); - y(array_open); y(map_open); ystr("id"); y(integer, con->window->id); y(map_close); - y(array_close); } } + y(array_close); y(map_close); } diff --git a/src/load_layout.c b/src/load_layout.c index 035b87c4..72b23d9f 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -16,6 +16,7 @@ static Con *to_focus; static bool parsing_swallows; static bool parsing_rect; static bool parsing_window_rect; +static bool parsing_geometry; struct Match *current_swallow; static int json_start_map(void *ctx) { @@ -26,7 +27,7 @@ static int json_start_map(void *ctx) { match_init(current_swallow); TAILQ_INSERT_TAIL(&(json_node->swallow_head), current_swallow, matches); } else { - if (!parsing_rect && !parsing_window_rect) { + if (!parsing_rect && !parsing_window_rect && !parsing_geometry) { if (last_key && strcasecmp(last_key, "floating_nodes") == 0) { Con *ws = con_get_workspace(json_node); json_node = con_new(NULL); @@ -45,7 +46,7 @@ static int json_start_map(void *ctx) { static int json_end_map(void *ctx) { LOG("end of map\n"); - if (!parsing_swallows && !parsing_rect && !parsing_window_rect) { + if (!parsing_swallows && !parsing_rect && !parsing_window_rect && !parsing_geometry) { LOG("attaching\n"); con_attach(json_node, json_node->parent, false); json_node = json_node->parent; @@ -54,6 +55,8 @@ static int json_end_map(void *ctx) { parsing_rect = false; if (parsing_window_rect) parsing_window_rect = false; + if (parsing_geometry) + parsing_geometry = false; return 1; } @@ -75,6 +78,8 @@ static int json_key(void *ctx, const unsigned char *val, unsigned int len) { parsing_rect = true; if (strcasecmp(last_key, "window_rect") == 0) parsing_window_rect = true; + if (strcasecmp(last_key, "geometry") == 0) + parsing_geometry = true; return 1; } @@ -129,8 +134,13 @@ static int json_int(void *ctx, long val) { if (strcasecmp(last_key, "num") == 0) json_node->num = val; - if (parsing_rect || parsing_window_rect) { - Rect *r = (parsing_rect ? &(json_node->rect) : &(json_node->window_rect)); + if (parsing_rect || parsing_window_rect || parsing_geometry) { + Rect *r; + if (parsing_rect) + r = &(json_node->rect); + else if (parsing_window_rect) + r = &(json_node->window_rect); + else r = &(json_node->geometry); if (strcasecmp(last_key, "x") == 0) r->x = val; else if (strcasecmp(last_key, "y") == 0) @@ -148,7 +158,10 @@ static int json_int(void *ctx, long val) { current_swallow->id = val; } if (strcasecmp(last_key, "dock") == 0) { - current_swallow->dock = true; + current_swallow->dock = val; + } + if (strcasecmp(last_key, "insert_where") == 0) { + current_swallow->insert_where = val; } } @@ -191,6 +204,7 @@ void tree_append_json(const char *filename) { to_focus = NULL; parsing_rect = false; parsing_window_rect = false; + parsing_geometry = false; setlocale(LC_NUMERIC, "C"); stat = yajl_parse(hand, (const unsigned char*)buf, n); if (stat != yajl_status_ok && From b6f81fe43cf84132ba72dac0da4d0c2ed197ee01 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Feb 2011 03:13:27 +0100 Subject: [PATCH 470/867] Bugfix: restore the original width/height with X11 border when restarting (Thanks Merovius) --- src/manage.c | 7 ++++++- src/render.c | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/manage.c b/src/manage.c index 5b60e44c..f41ab412 100644 --- a/src/manage.c +++ b/src/manage.c @@ -55,7 +55,11 @@ void restore_geometry() { Con *con; TAILQ_FOREACH(con, &all_cons, all_cons) if (con->window) { - DLOG("placing window at %d %d\n", con->rect.x, con->rect.y); + DLOG("Re-adding X11 border of %d px\n", con->border_width); + con->window_rect.width += (2 * con->border_width); + con->window_rect.height += (2 * con->border_width); + xcb_set_window_rect(conn, con->window->id, con->window_rect); + DLOG("placing window %08x at %d %d\n", con->window->id, con->rect.x, con->rect.y); xcb_reparent_window(conn, con->window->id, root, con->rect.x, con->rect.y); } @@ -154,6 +158,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki LOG("this window is a dock\n"); } + DLOG("Initial geometry: (%d, %d, %d, %d)\n", geom->x, geom->y, geom->width, geom->height); Con *nc; Match *match; diff --git a/src/render.c b/src/render.c index 9e2fd3db..af062c85 100644 --- a/src/render.c +++ b/src/render.c @@ -140,6 +140,7 @@ void render_con(Con *con, bool render_fullscreen) { *inset = rect_add(*inset, con_border_style_rect(con)); /* Obey x11 border */ + DLOG("X11 border: %d\n", con->border_width); inset->width -= (2 * con->border_width); inset->height -= (2 * con->border_width); From a678c16bc9fad9b566c804f1422e6a6d72fa62ed Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Feb 2011 12:20:55 +0100 Subject: [PATCH 471/867] tests: fix t/16-nestedcons.t --- testcases/t/16-nestedcons.t | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t index f40ec68e..23f894ba 100644 --- a/testcases/t/16-nestedcons.t +++ b/testcases/t/16-nestedcons.t @@ -23,6 +23,8 @@ my $expected = { id => ignore(), rect => ignore(), window_rect => ignore(), + geometry => ignore(), + swallows => ignore(), percent => 0, layout => 0, focus => ignore(), From 0f97b1fef6ed5eff4e67b854c3debcaa981e442a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Feb 2011 12:21:02 +0100 Subject: [PATCH 472/867] tests: add test for dock client + restart --- testcases/t/10-dock.t | 20 +---- testcases/t/50-regress-dock-restart.t | 111 ++++++++++++++++++++++++++ testcases/t/lib/i3test.pm | 15 +++- 3 files changed, 127 insertions(+), 19 deletions(-) create mode 100644 testcases/t/50-regress-dock-restart.t diff --git a/testcases/t/10-dock.t b/testcases/t/10-dock.t index dabb7bd8..aa93d8f3 100644 --- a/testcases/t/10-dock.t +++ b/testcases/t/10-dock.t @@ -17,16 +17,8 @@ my $i3 = i3("/tmp/nestedcons"); # verify that there is no dock window yet ##################################################################### -my $tree = $i3->get_tree->recv; -my @outputs = @{$tree->{nodes}}; # Children of all dockareas -my @docked; -for my $output (@outputs) { - @docked = (@docked, map { @{$_->{nodes}} } - grep { $_->{type} == 5 } - @{$output->{nodes}}); -} - +my @docked = get_dock_clients; is(@docked, 0, 'no dock clients yet'); ##################################################################### @@ -59,15 +51,7 @@ is($rect->height, 30, 'height is unchanged'); # check that we can find it in the layout tree at the expected position ##################################################################### -$tree = $i3->get_tree->recv; -@outputs = @{$tree->{nodes}}; -@docked; -for my $output (@outputs) { - @docked = (@docked, map { @{$_->{nodes}} } - grep { $_->{type} == 5 } - @{$output->{nodes}}); -} - +@docked = get_dock_clients; is(@docked, 1, 'one dock client found'); # verify the position/size diff --git a/testcases/t/50-regress-dock-restart.t b/testcases/t/50-regress-dock-restart.t new file mode 100644 index 00000000..a753abe2 --- /dev/null +++ b/testcases/t/50-regress-dock-restart.t @@ -0,0 +1,111 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Regression test for inplace restarting with dock clients +# +use X11::XCB qw(:all); +use Time::HiRes qw(sleep); +use i3test; + +BEGIN { + use_ok('X11::XCB::Window'); +} + +my $x = X11::XCB::Connection->new; +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace; +cmd "workspace $tmp"; + +##################################################################### +# verify that there is no dock window yet +##################################################################### + +# Children of all dockareas +my @docked = get_dock_clients; + +is(@docked, 0, 'no dock clients yet'); + +# open a dock client + +my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), +); + +$window->map; + +sleep 0.25; + +##################################################################### +# check that we can find it in the layout tree at the expected position +##################################################################### + +@docked = get_dock_clients; +is(@docked, 1, 'one dock client found'); + +# verify the height +my $docknode = $docked[0]; + +is($docknode->{rect}->{height}, 30, 'dock node has unchanged height'); + +# perform an inplace-restart +cmd 'restart'; + +sleep 0.25; + +does_i3_live; + + +##################################################################### +# check that we can still find the dock client +##################################################################### + +@docked = get_dock_clients; +is(@docked, 1, 'one dock client found'); +$docknode = $docked[0]; + +is($docknode->{rect}->{height}, 30, 'dock node has unchanged height after restart'); + +$window->destroy; + +sleep 0.25; + +@docked = get_dock_clients; +is(@docked, 0, 'no dock clients found'); + +##################################################################### +# create a dock client with a 1px border +##################################################################### + +$window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + border => 1, + rect => [ 0, 0, 30, 20], + background_color => '#00FF00', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), +); + +$window->map; + +sleep 0.25; + +@docked = get_dock_clients; +is(@docked, 1, 'one dock client found'); +$docknode = $docked[0]; + +is($docknode->{rect}->{height}, 20, 'dock node has unchanged height'); + +cmd 'restart'; +sleep 0.25; + +@docked = get_dock_clients; +is(@docked, 1, 'one dock client found'); +$docknode = $docked[0]; + +is($docknode->{rect}->{height}, 20, 'dock node has unchanged height'); + + +done_testing; diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 7f6c0614..d03c9e3c 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -11,7 +11,7 @@ use List::Util qw(first); use v5.10; use Exporter (); -our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window cmd does_i3_live); +our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window get_dock_clients cmd does_i3_live); my $tester = Test::Builder->new(); @@ -129,6 +129,19 @@ sub get_focused { return $lf; } +sub get_dock_clients { + my $tree = i3("/tmp/nestedcons")->get_tree->recv; + my @outputs = @{$tree->{nodes}}; + # Children of all dockareas + my @docked; + for my $output (@outputs) { + @docked = (@docked, map { @{$_->{nodes}} } + grep { $_->{type} == 5 } + @{$output->{nodes}}); + } + return @docked; +} + sub cmd { i3("/tmp/nestedcons")->command(@_)->recv } From ffc71859a3b9ee54410dfdbe733aba8b01b4b46c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Feb 2011 14:27:32 +0100 Subject: [PATCH 473/867] Implement support for top/bottom dock clients (according to _NET_WM_STRUT_PARTIAL or requested position) --- include/data.h | 25 +++++++++++++++++++++++-- include/window.h | 6 ++++++ src/con.c | 5 +++-- src/manage.c | 41 ++++++++++++++++++++++++++++++----------- src/match.c | 7 ++++++- src/randr.c | 24 +++++++++++++++++++++++- src/window.c | 24 ++++++++++++++++++++++-- 7 files changed, 113 insertions(+), 19 deletions(-) diff --git a/include/data.h b/include/data.h index e8a796cc..71347364 100644 --- a/include/data.h +++ b/include/data.h @@ -75,6 +75,18 @@ struct Rect { uint32_t height; } __attribute__((packed)); +/** + * Stores the reserved pixels on each screen edge read from a + * _NET_WM_STRUT_PARTIAL. + * + */ +struct reservedpx { + uint32_t left; + uint32_t right; + uint32_t top; + uint32_t bottom; +}; + /** * Used for the cache of colorpixels. * @@ -242,7 +254,10 @@ struct Window { bool uses_net_wm_name; /** Whether the window says it is a dock window */ - bool dock; + enum { W_NODOCK = 0, W_DOCK_TOP = 1, W_DOCK_BOTTOM = 2 } dock; + + /** Pixels the window reserves. left/right/top/bottom */ + struct reservedpx reserved; }; struct Match { @@ -254,7 +269,13 @@ struct Match { char *class; char *instance; char *mark; - int dock; + enum { + M_DONTCHECK = -1, + M_NODOCK = 0, + M_DOCK_ANY = 1, + M_DOCK_TOP = 2, + M_DOCK_BOTTOM = 3 + } dock; xcb_window_t id; Con *con_id; enum { M_ANY = 0, M_TILING, M_FLOATING } floating; diff --git a/include/window.h b/include/window.h index 6621a169..1c48c012 100644 --- a/include/window.h +++ b/include/window.h @@ -36,4 +36,10 @@ void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop); */ void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop); +/** + * Updates the _NET_WM_STRUT_PARTIAL (reserved pixels at the screen edges) + * + */ +void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop); + #endif diff --git a/src/con.c b/src/con.c index 07fb0c65..da41f944 100644 --- a/src/con.c +++ b/src/con.c @@ -123,8 +123,9 @@ void con_attach(Con *con, Con *parent, bool ignore_focus) { } } - /* Insert the container after the tiling container, if found */ - if (current) { + /* Insert the container after the tiling container, if found. + * When adding to a CT_OUTPUT, just append one after another. */ + if (current && parent->type != CT_OUTPUT) { DLOG("Inserting con = %p after last focused tiling con %p\n", con, current); TAILQ_INSERT_AFTER(nodes_head, current, con, nodes); diff --git a/src/manage.c b/src/manage.c index f41ab412..c602ee41 100644 --- a/src/manage.c +++ b/src/manage.c @@ -151,11 +151,39 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL)); window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL)); window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL)); + window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL)); + + /* Where to start searching for a container that swallows the new one? */ + Con *search_at = croot; xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, wm_type_cookie, NULL); if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DOCK])) { - cwindow->dock = true; - LOG("this window is a dock\n"); + LOG("This window is of type dock\n"); + Output *output = get_output_containing(geom->x, geom->y); + if (output != NULL) { + DLOG("Starting search at output %s\n", output->name); + search_at = output->con; + } + + /* find out the desired position of this dock window */ + if (cwindow->reserved.top > 0 && cwindow->reserved.bottom == 0) { + DLOG("Top dock client\n"); + cwindow->dock = W_DOCK_TOP; + } else if (cwindow->reserved.top == 0 && cwindow->reserved.bottom > 0) { + DLOG("Bottom dock client\n"); + cwindow->dock = W_DOCK_BOTTOM; + } else { + DLOG("Ignoring invalid reserved edges (_NET_WM_STRUT_PARTIAL), using position as fallback:\n"); + if (geom->y < (search_at->rect.height / 2)) { + DLOG("geom->y = %d < rect.height / 2 = %d, it is a top dock client\n", + geom->y, (search_at->rect.height / 2)); + cwindow->dock = W_DOCK_TOP; + } else { + DLOG("geom->y = %d >= rect.height / 2 = %d, it is a bottom dock client\n", + geom->y, (search_at->rect.height / 2)); + cwindow->dock = W_DOCK_BOTTOM; + } + } } DLOG("Initial geometry: (%d, %d, %d, %d)\n", geom->x, geom->y, geom->width, geom->height); @@ -167,15 +195,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* TODO: two matches for one container */ /* See if any container swallows this new window */ - Con *search_at = croot; - if (cwindow->dock) { - /* for dock windows, we start the search at the appropriate output */ - Output *output = get_output_containing(geom->x, geom->y); - if (output != NULL) { - DLOG("Starting search at output %s\n", output->name); - search_at = output->con; - } - } nc = con_for_window(search_at, cwindow, &match); if (nc == NULL) { if (focused->type == CT_CON && con_accepts_window(focused)) { diff --git a/src/match.c b/src/match.c index 42eba26e..da58047e 100644 --- a/src/match.c +++ b/src/match.c @@ -67,7 +67,12 @@ bool match_matches_window(Match *match, i3Window *window) { } LOG("match->dock = %d, window->dock = %d\n", match->dock, window->dock); - if (match->dock != -1 && window->dock == match->dock) { + if (match->dock != -1 && + ((window->dock == W_DOCK_TOP && match->dock == M_DOCK_TOP) || + (window->dock == W_DOCK_BOTTOM && match->dock == M_DOCK_BOTTOM) || + ((window->dock == W_DOCK_TOP || window->dock == W_DOCK_BOTTOM) && + match->dock == M_DOCK_ANY) || + (window->dock == W_NODOCK && match->dock == M_NODOCK))) { LOG("match made by dock\n"); return true; } diff --git a/src/randr.c b/src/randr.c index 20e4f54e..d4dc770b 100644 --- a/src/randr.c +++ b/src/randr.c @@ -273,7 +273,7 @@ void output_init_con(Output *output) { /* this container swallows dock clients */ Match *match = scalloc(sizeof(Match)); match_init(match); - match->dock = true; + match->dock = M_DOCK_TOP; match->insert_where = M_BELOW; TAILQ_INSERT_TAIL(&(topdock->swallow_head), match, matches); @@ -285,6 +285,8 @@ void output_init_con(Output *output) { DLOG("attaching\n"); con_attach(topdock, con, false); + /* content container */ + DLOG("adding main content container\n"); Con *content = con_new(NULL); content->type = CT_CON; @@ -295,6 +297,26 @@ void output_init_con(Output *output) { FREE(name); con_attach(content, con, false); + /* bottom dock container */ + Con *bottomdock = con_new(NULL); + bottomdock->type = CT_DOCKAREA; + bottomdock->layout = L_DOCKAREA; + bottomdock->orientation = VERT; + /* this container swallows dock clients */ + match = scalloc(sizeof(Match)); + match_init(match); + match->dock = M_DOCK_BOTTOM; + match->insert_where = M_BELOW; + TAILQ_INSERT_TAIL(&(bottomdock->swallow_head), match, matches); + + bottomdock->name = sstrdup("bottomdock"); + + asprintf(&name, "[i3 con] bottom dockarea %s", con->name); + x_set_name(bottomdock, name); + FREE(name); + DLOG("attaching\n"); + con_attach(bottomdock, con, false); + DLOG("Now adding a workspace\n"); /* add a workspace to this output */ diff --git a/src/window.c b/src/window.c index 82c881a4..11be7c6f 100644 --- a/src/window.c +++ b/src/window.c @@ -106,7 +106,7 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop) { win->name_len = strlen(new_name); } -/** +/* * Updates the CLIENT_LEADER (logical parent window). * */ @@ -125,7 +125,7 @@ void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop) { win->leader = *leader; } -/** +/* * Updates the TRANSIENT_FOR (logical parent window). * */ @@ -143,3 +143,23 @@ void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop) win->transient_for = transient_for; } + +/* + * Updates the _NET_WM_STRUT_PARTIAL (reserved pixels at the screen edges) + * + */ +void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop) { + if (prop == NULL || xcb_get_property_value_length(prop) == 0) { + DLOG("prop == NULL\n"); + return; + } + + uint32_t *strut; + if (!(strut = xcb_get_property_value(prop))) + return; + + DLOG("Reserved pixels changed to: left = %d, right = %d, top = %d, bottom = %d\n", + strut[0], strut[1], strut[2], strut[3]); + + win->reserved = (struct reservedpx){ strut[0], strut[1], strut[2], strut[3] }; +} From db0d66e545669465a2b79a6eabd1189d3766f86b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Feb 2011 14:55:03 +0100 Subject: [PATCH 474/867] tests: extend t/10-dock.t for top/bottom positioned dock clients by position/hint --- testcases/t/10-dock.t | 112 +++++++++++++++++++++++++++++++++++++- testcases/t/lib/i3test.pm | 17 +++++- 2 files changed, 125 insertions(+), 4 deletions(-) diff --git a/testcases/t/10-dock.t b/testcases/t/10-dock.t index aa93d8f3..f38defe0 100644 --- a/testcases/t/10-dock.t +++ b/testcases/t/10-dock.t @@ -51,7 +51,7 @@ is($rect->height, 30, 'height is unchanged'); # check that we can find it in the layout tree at the expected position ##################################################################### -@docked = get_dock_clients; +@docked = get_dock_clients('top'); is(@docked, 1, 'one dock client found'); # verify the position/size @@ -62,6 +62,116 @@ is($docknode->{rect}->{y}, 0, 'dock node placed at y=0'); is($docknode->{rect}->{width}, $primary->rect->width, 'dock node as wide as the screen'); is($docknode->{rect}->{height}, 30, 'dock node has unchanged height'); +$window->destroy; + +sleep 0.25; + +@docked = get_dock_clients(); +is(@docked, 0, 'no more dock clients'); + +##################################################################### +# check if it gets placed on bottom (by coordinates) +##################################################################### + +$window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 1000, 30, 30], + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), +); + +$window->map; + +sleep 0.25; + +my $rect = $window->rect; +is($rect->width, $primary->rect->width, 'dock client is as wide as the screen'); +is($rect->height, 30, 'height is unchanged'); + +@docked = get_dock_clients('bottom'); +is(@docked, 1, 'dock client on bottom'); + +$window->destroy; + +sleep 0.25; + +@docked = get_dock_clients(); +is(@docked, 0, 'no more dock clients'); + +##################################################################### +# check if it gets placed on bottom (by hint) +##################################################################### + +$window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 1000, 30, 30], + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), +); + +$window->_create(); + +# Add a _NET_WM_STRUT_PARTIAL hint +my $atomname = $x->atom(name => '_NET_WM_STRUT_PARTIAL'); +my $atomtype = $x->atom(name => 'CARDINAL'); + +$x->change_property( + PROP_MODE_REPLACE, + $window->id, + $atomname->id, + $atomtype->id, + 32, # 32 bit integer + 12, + pack('L12', 0, 0, 16, 0, 0, 0, 0, 0, 0, 1280, 0, 0) +); + +$window->map; + +sleep 0.25; + +@docked = get_dock_clients('top'); +is(@docked, 1, 'dock client on top'); + +$window->destroy; + +sleep 0.25; + +@docked = get_dock_clients(); +is(@docked, 0, 'no more dock clients'); + +$window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 1000, 30, 30], + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), +); + +$window->_create(); + +# Add a _NET_WM_STRUT_PARTIAL hint +my $atomname = $x->atom(name => '_NET_WM_STRUT_PARTIAL'); +my $atomtype = $x->atom(name => 'CARDINAL'); + +$x->change_property( + PROP_MODE_REPLACE, + $window->id, + $atomname->id, + $atomtype->id, + 32, # 32 bit integer + 12, + pack('L12', 0, 0, 0, 16, 0, 0, 0, 0, 0, 1280, 0, 0) +); + +$window->map; + +sleep 0.25; + +@docked = get_dock_clients('bottom'); +is(@docked, 1, 'dock client on bottom'); + +$window->destroy; + + ##################################################################### # regression test: transient dock client ##################################################################### diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index d03c9e3c..b9d168d6 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -8,6 +8,7 @@ use X11::XCB::Window; use X11::XCB qw(:all); use AnyEvent::I3; use List::Util qw(first); +use List::MoreUtils qw(lastval); use v5.10; use Exporter (); @@ -130,14 +131,24 @@ sub get_focused { } sub get_dock_clients { + my $which = shift; + my $tree = i3("/tmp/nestedcons")->get_tree->recv; my @outputs = @{$tree->{nodes}}; # Children of all dockareas my @docked; for my $output (@outputs) { - @docked = (@docked, map { @{$_->{nodes}} } - grep { $_->{type} == 5 } - @{$output->{nodes}}); + if (!defined($which)) { + @docked = (@docked, map { @{$_->{nodes}} } + grep { $_->{type} == 5 } + @{$output->{nodes}}); + } elsif ($which eq 'top') { + my $first = first { $_->{type} == 5 } @{$output->{nodes}}; + @docked = (@docked, @{$first->{nodes}}); + } elsif ($which eq 'bottom') { + my $last = lastval { $_->{type} == 5 } @{$output->{nodes}}; + @docked = (@docked, @{$last->{nodes}}); + } } return @docked; } From 9e0836608220242a2c2b745a0430a7c4b9e33ced Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Feb 2011 20:00:56 +0100 Subject: [PATCH 475/867] add proof-of-concept perl script to render the tree to SVG This will be useful to generate examples for the documentation. --- render-tree/Con.pm | 21 +++++++ render-tree/render.pl | 125 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 render-tree/Con.pm create mode 100755 render-tree/render.pl diff --git a/render-tree/Con.pm b/render-tree/Con.pm new file mode 100644 index 00000000..1830caf8 --- /dev/null +++ b/render-tree/Con.pm @@ -0,0 +1,21 @@ +# vim:ts=4:sw=4:expandtab +package Con; + +use Moose; +use MooseX::AttributeHelpers; +use v5.10; + +has 'name' => (is => 'ro', isa => 'Str'); +has 'width' => (is => 'rw', isa => 'Int', default => 100); +has '_nodes' => (is => 'ro', metaclass => 'Collection::Array', isa => 'ArrayRef[Con]', + default => sub { [] }, + provides => { + 'push' => 'add_node', + elements => 'nodes', + } +); +has 'parent' => (is => 'rw', isa => 'Con', predicate => 'has_parent'); + +__PACKAGE__->meta->make_immutable; + +1 diff --git a/render-tree/render.pl b/render-tree/render.pl new file mode 100755 index 00000000..bea5811a --- /dev/null +++ b/render-tree/render.pl @@ -0,0 +1,125 @@ +#!/usr/bin/env perl +# vim:ts=4:sw=4:expandtab +# © 2011 Michael Stapelberg, see LICENSE +# +# Needs SVG (libsvg-perl), IO::All (libio-all-perl), JSON::XS (libjson-xs-perl) and Moose (libmoose-perl) +# +# XXX: unfinished proof-of-concept. awaits a json dump in my.tree, renders to test.svg +# XXX: needs more beautifying (in the SVG but also in the code) +# XXX: has some rendering differences between firefox and chromium. maybe inkscape makes the file look the same in both browsers + +use strict; +use warnings; +use SVG; +use Data::Dumper; +use JSON::XS; +use IO::All; +use List::Util qw(sum); +use lib qw(.); +use Con; +use v5.10; + +my $input = io('my.tree')->slurp; +my $tree = decode_json($input); +my $root = parse_tree($tree); +render_tree($root); + +sub parse_tree { + my ($input, $parent) = @_; + my $con = Con->new(name => $input->{name}); + $con->parent($parent) if defined($parent); + for my $node (@{$input->{nodes}}) { + $con->add_node(parse_tree($node, $con)); + } + + return $con; +} + +sub render_tree { + my ($con) = @_; + say 'rendering con ' . $con->name; + my @nodes = $con->nodes; + for my $node (@nodes) { + render_tree($node); + } + + # nothing to calculate when there are no children + return unless @nodes > 0; + + $con->width((@nodes > 1 ? (@nodes - 1) * 20 : 0) + sum map { $_->width } @nodes); + + say $con->name . ' has width ' . $con->width; +} + +# TODO: figure out the height +my $svg = SVG->new(id => "tree", width => $root->width + 5, height => '1052'); + +my $l1 = $svg->group(id => 'layer1'); + +# gaussian blur (for drop shadows) +$svg->defs()->filter(id => 'dropshadow')->fe(-type => 'gaussianblur', stdDeviation => '2.19'); + +my $idcnt = 0; +my $y = 10; +render_svg($root, 0, 0); + +sub render_svg { + my ($con, $level, $x) = @_; + + my $indent = ' ' x $level; + + say $indent . 'svg-rendering con ' . $con->name . ' on level ' . $level; + say $indent . 'width: ' . $con->width; + + # render the dropshadow rect + $l1->rect( + id => 'outer_rect_shadow' . $idcnt, + style => 'opacity:1.0;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-opacity:1;stroke-miterlimit:4;filter:url(#dropshadow)', + width => "96", + height => '50', + #x => $x + ($con->has_parent ? ($con->parent->width - 100) / 2 : 0), + x => $x + ($con->width / 2) - (96 / 2) + 0, + y => 4 + $level * 70 + 0, + ); + $idcnt++; + + # render the main rect + $l1->rect( + id => 'outer_rect' . $idcnt, + style => 'opacity:1.0;fill:#c30000;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-opacity:1;stroke-miterlimit:4', + width => "96", + height => '50', + x => $x + ($con->width / 2) - (96 / 2), + y => 4 + $level * 70, + ); + + $idcnt++; + + # render the text + $l1->text( + style => 'font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:left;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Trebuchet MS;-inkscape-font-specification:Trebuchet MS', + x => $x + ($con->width / 2) - (100/2) + 5, + y => 4 + 15 + $level * 70, + id => 'title_'.$idcnt, + )->tspan(style => 'text-align:start;text-anchor:start')->cdata($con->name); + $idcnt++; + + $y = $y + 50; + my @nodes = $con->nodes; + my $startx = $x + ($con->width / 2); + + for my $node (@nodes) { + render_svg($node, $level + 1, $x); + my $mid = $x + ($node->width / 2); + $l1->path( + d => 'M ' . $startx . ',' . (4 + $level * 70 + 50) . ' ' . $mid . ',' . (4 + ($level+1) * 70), + id => 'path' . $idcnt, + style => 'fill:none;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' + ); + $x += $node->width + 20; + $idcnt++; + } + +} + +$svg->render > io('test.svg'); From f4ec0bceff688877447b31a8101b690516207c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Fri, 25 Feb 2011 20:31:26 -0300 Subject: [PATCH 476/867] Very minor issues found with statical analysis. The Clang Static Analyzer uncovered those issues: - The variable "changed" in handlers.c is written to, but it's never read since that specific write, so the write is not necessary. - In util.c, "tail" may be NULL. In that case, we shouldn't pass it to strlen because strlen's behavior is not defined when s is NULL. - In util.c, "write_index" is incremented twice. It's never used anymore after being incremented once, so the second increment is not necessary. --- src/handlers.c | 1 - src/util.c | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 4123caee..23e9c17a 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -751,7 +751,6 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w con->base_height = base_height; DLOG("client's base_height changed to %d\n", base_height); DLOG("client's base_width changed to %d\n", base_width); - changed = true; } /* If no aspect ratio was set or if it was invalid, we ignore the hints */ diff --git a/src/util.c b/src/util.c index 1ecce922..693027fa 100644 --- a/src/util.c +++ b/src/util.c @@ -199,7 +199,8 @@ char *resolve_tilde(const char *path) { head = globbuf.gl_pathv[0]; result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1); strncpy(result, head, strlen(head)); - strncat(result, tail, strlen(tail)); + if (tail) + strncat(result, tail, strlen(tail)); } globfree(&globbuf); @@ -353,7 +354,7 @@ void i3_restart(bool forget_layout) { /* add the arguments we'll replace */ new_argv[write_index++] = "--restart"; - new_argv[write_index++] = restart_filename; + new_argv[write_index] = restart_filename; /* swap the argvs */ start_argv = new_argv; From 269d360f3000a13f44715a7b46b4b48b0754f19a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Fri, 25 Feb 2011 21:21:48 -0300 Subject: [PATCH 477/867] Some assertions to make the static analyzer happy. Assertions give hints to the static analyzer about code paths where we make assumptions. Used the Clang Static Analyzer. --- src/render.c | 2 ++ src/workspace.c | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/render.c b/src/render.c index af062c85..499c62a3 100644 --- a/src/render.c +++ b/src/render.c @@ -200,6 +200,7 @@ void render_con(Con *con, bool render_fullscreen) { /* precalculate the sizes to be able to correct rounding errors */ int sizes[children]; if (con->layout == L_DEFAULT && children > 0) { + assert(!TAILQ_EMPTY(&con->nodes_head)); Con *child; int i = 0, assigned = 0; int total = con->orientation == HORIZ ? rect.width : rect.height; @@ -226,6 +227,7 @@ void render_con(Con *con, bool render_fullscreen) { /* FIXME: refactor this into separate functions: */ Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + assert(children > 0); /* default layout */ if (con->layout == L_DEFAULT) { diff --git a/src/workspace.c b/src/workspace.c index 2912278d..fa84e204 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -199,7 +199,7 @@ static void workspace_reassign_sticky(Con *con) { * */ void workspace_show(const char *num) { - Con *workspace, *current, *old; + Con *workspace, *current, *old = NULL; workspace = workspace_get(num); @@ -210,6 +210,7 @@ void workspace_show(const char *num) { old = current; current->fullscreen_mode = CF_NONE; } + assert(old != NULL); /* Check if the the currently focused con is on the same Output as the * workspace we chose as 'old'. If not, use the workspace of the currently From beaa85ceb94ea43bde5acc7c403f8fc314929273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sun, 27 Feb 2011 20:23:54 -0300 Subject: [PATCH 478/867] Don't leak this descriptor (thanks dothebart). Note that fclose closes the file descriptor frees the stream. --- src/cfgparse.y | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cfgparse.y b/src/cfgparse.y index c93c33e6..d3a5ecda 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -101,6 +101,7 @@ void parse_file(const char *f) { continue; } } + fclose(fstr); /* For every custom variable, see how often it occurs in the file and * how much extra bytes it requires when replaced. */ From d80105103c25c149609dc4ed635a240f8a13b5c9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 28 Feb 2011 17:23:50 +0100 Subject: [PATCH 479/867] Bugfix: Re-attach floating cons to the right container (Thanks mseed) This fixes #315. --- src/floating.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/floating.c b/src/floating.c index 25afa832..fac78a0f 100644 --- a/src/floating.c +++ b/src/floating.c @@ -158,7 +158,11 @@ void floating_disable(Con *con, bool automatic) { /* 3: re-attach to the parent of the currently focused con on the workspace * this floating con was on */ Con *focused = con_descend_focused(con_get_workspace(con)); - con->parent = focused->parent; + /* if there is no other container on this workspace, focused will be the + * workspace itself */ + if (focused->type == CT_WORKSPACE) + con->parent = focused; + else con->parent = focused->parent; /* XXX: We adjust the percentage value to start with a fair value. Floating * cons always have 1.0 as percent which doesn’t work so well when From b595ff05c534639afa63900cdd16bc4902ee2a12 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 28 Feb 2011 20:44:23 +0100 Subject: [PATCH 480/867] =?UTF-8?q?Fix=20regression:=20Don=E2=80=99t=20add?= =?UTF-8?q?=20floating=20nodes=20twice=20when=20restoring=20layout=20(+tes?= =?UTF-8?q?tcase)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/load_layout.c | 6 +++--- testcases/t/43-regress-floating-restart.t | 24 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 testcases/t/43-regress-floating-restart.t diff --git a/src/load_layout.c b/src/load_layout.c index 72b23d9f..92cfecbd 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -22,18 +22,18 @@ struct Match *current_swallow; static int json_start_map(void *ctx) { LOG("start of map, last_key = %s\n", last_key); if (parsing_swallows) { - LOG("TODO: create new swallow\n"); + LOG("creating new swallow\n"); current_swallow = smalloc(sizeof(Match)); match_init(current_swallow); TAILQ_INSERT_TAIL(&(json_node->swallow_head), current_swallow, matches); } else { if (!parsing_rect && !parsing_window_rect && !parsing_geometry) { if (last_key && strcasecmp(last_key, "floating_nodes") == 0) { + DLOG("New floating_node\n"); Con *ws = con_get_workspace(json_node); json_node = con_new(NULL); json_node->parent = ws; - TAILQ_INSERT_TAIL(&(ws->floating_head), json_node, floating_windows); - TAILQ_INSERT_TAIL(&(ws->focus_head), json_node, focused); + DLOG("Parent is workspace = %p\n", ws); } else { Con *parent = json_node; json_node = con_new(NULL); diff --git a/testcases/t/43-regress-floating-restart.t b/testcases/t/43-regress-floating-restart.t new file mode 100644 index 00000000..974c1ae1 --- /dev/null +++ b/testcases/t/43-regress-floating-restart.t @@ -0,0 +1,24 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Regression: floating windows are tiling after restarting, closing them crashes i3 +# +use i3test tests => 1; +use Time::HiRes qw(sleep); +use X11::XCB qw(:all); + +my $tmp = get_unused_workspace(); +cmd "workspace $tmp"; + +cmd 'open'; +cmd 'mode toggle'; +cmd 'restart'; + +sleep 0.5; + +diag('Checking if i3 still lives'); + +does_i3_live; + +my $ws = get_ws($tmp); +diag('ws = ' . Dumper($ws)); From 33c2b4e58232f7384d996c8aaf6a512605525c3f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 28 Feb 2011 23:59:56 +0100 Subject: [PATCH 481/867] Bugfix: Correctly fix the percent values for resizing when making a floating con tiling (Thanks mseed) (+testcase) --- src/floating.c | 7 ++----- testcases/t/51-regress-float-size.t | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 testcases/t/51-regress-float-size.t diff --git a/src/floating.c b/src/floating.c index fac78a0f..558f7f9a 100644 --- a/src/floating.c +++ b/src/floating.c @@ -164,11 +164,8 @@ void floating_disable(Con *con, bool automatic) { con->parent = focused; else con->parent = focused->parent; - /* XXX: We adjust the percentage value to start with a fair value. Floating - * cons always have 1.0 as percent which doesn’t work so well when - * re-inserting (the formerly floating con would get 50% of the target - * con). */ - con->percent = (1.0 / con_num_children(con->parent)); + /* con_fix_percent will adjust the percent value */ + con->percent = 0.0; TAILQ_INSERT_TAIL(&(con->parent->nodes_head), con, nodes); TAILQ_INSERT_TAIL(&(con->parent->focus_head), con, focused); diff --git a/testcases/t/51-regress-float-size.t b/testcases/t/51-regress-float-size.t new file mode 100644 index 00000000..b3ff9a47 --- /dev/null +++ b/testcases/t/51-regress-float-size.t @@ -0,0 +1,20 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Regression test for setting a window to floating, tiling and opening a new window +# +use Time::HiRes qw(sleep); +use i3test; + +my $tmp = get_unused_workspace; +cmd "workspace $tmp"; + + +cmd 'open'; +cmd 'mode toggle'; +cmd 'mode toggle'; +cmd 'open'; + +does_i3_live; + +done_testing; From e0647b7fc2336c4f9e54eff6da6f7d2627232cc7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 3 Mar 2011 13:59:42 +0100 Subject: [PATCH 482/867] =?UTF-8?q?Bugfix:=20don=E2=80=99t=20focus=20dock?= =?UTF-8?q?=20clients=20(Thanks=20mseed,=20mist)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #321 and #323 --- src/click.c | 7 +++++++ src/handlers.c | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/click.c b/src/click.c index 8617cf0b..51472954 100644 --- a/src/click.c +++ b/src/click.c @@ -307,6 +307,13 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ } } + if ((clicked_into ? clicked_into->parent->type : con->parent->type) == CT_DOCKAREA) { + DLOG("Not handling, this client is inside a dockarea\n"); + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + xcb_flush(conn); + return 0; + } + /* click to focus, either on the clicked window or its child if thas was a * click into a child decoration */ con_focus((clicked_into ? clicked_into : con)); diff --git a/src/handlers.c b/src/handlers.c index 23e9c17a..35d8367a 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -181,6 +181,11 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, return 1; } + if (con->parent->type == CT_DOCKAREA) { + DLOG("Ignoring, this is a dock client\n"); + return 1; + } + /* see if the user entered the window on a certain window decoration */ int layout = (enter_child ? con->parent->layout : con->layout); Con *child; From 77640da9e7741a90caf2852fb7d8ac426ef91796 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 3 Mar 2011 14:03:06 +0100 Subject: [PATCH 483/867] =?UTF-8?q?Bugfix:=20don=E2=80=99t=20focus=20the?= =?UTF-8?q?=20dockarea=20when=20closing=20a=20dock=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tree.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index 3900b862..31177109 100644 --- a/src/tree.c +++ b/src/tree.c @@ -184,7 +184,12 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { if (kill_window || !dont_kill_parent || con == focused) { DLOG("focusing %p / %s\n", next, next->name); /* TODO: check if the container (or one of its children) was focused */ - con_focus(next); + if (next->type == CT_DOCKAREA) { + /* Instead of focusing the dockarea, we need to restore focus to the workspace */ + con_focus(con_descend_focused(output_get_content(next->parent))); + } else { + con_focus(next); + } } else { DLOG("not focusing because we're not killing anybody"); From 62362a464d729938024f5451d6f413414b496340 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 3 Mar 2011 14:14:35 +0100 Subject: [PATCH 484/867] fix invalid memory access in xcb_reply_contains_atom (Thanks ys) --- src/xcb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xcb.c b/src/xcb.c index 53a90685..d2aeaadd 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -347,7 +347,7 @@ bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom) { if ((atoms = xcb_get_property_value(prop)) == NULL) return false; - for (int i = 0; i < xcb_get_property_value_length(prop); i++) + for (int i = 0; i < xcb_get_property_value_length(prop) / (prop->format / 8); i++) if (atoms[i] == atom) return true; From a038d2674bed411e7e1867c1a4957d8b9734f3a6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 3 Mar 2011 14:30:13 +0100 Subject: [PATCH 485/867] Bugfix: 'level up' needs to stop at the workspace con + testcase (Thanks mseed) --- src/tree.c | 5 +++-- testcases/t/52-regress-level-up.t | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 testcases/t/52-regress-level-up.t diff --git a/src/tree.c b/src/tree.c index 31177109..3c209fbc 100644 --- a/src/tree.c +++ b/src/tree.c @@ -268,8 +268,9 @@ void tree_split(Con *con, orientation_t orientation) { */ void level_up() { /* We can focus up to the workspace, but not any higher in the tree */ - if (focused->parent->type != CT_CON && - focused->parent->type != CT_WORKSPACE) { + if ((focused->parent->type != CT_CON && + focused->parent->type != CT_WORKSPACE) || + focused->type == CT_WORKSPACE) { printf("cannot go up\n"); return; } diff --git a/testcases/t/52-regress-level-up.t b/testcases/t/52-regress-level-up.t new file mode 100644 index 00000000..19b0f85d --- /dev/null +++ b/testcases/t/52-regress-level-up.t @@ -0,0 +1,21 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Regression test for using level-up to get to the 'content'-container and +# toggle floating +# +use Time::HiRes qw(sleep); +use i3test; + +my $tmp = get_unused_workspace; +cmd "workspace $tmp"; + + +cmd 'open'; +cmd 'level up'; +cmd 'level up'; +cmd 'mode toggle'; + +does_i3_live; + +done_testing; From b484b9ab32170781a482a2dd5b2478a5ac50ca49 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 3 Mar 2011 14:55:02 +0100 Subject: [PATCH 486/867] =?UTF-8?q?Don=E2=80=99t=20create=20floating=20con?= =?UTF-8?q?tainers=20from=20whole=20workspaces=20when=20they=20are=20empty?= =?UTF-8?q?=20(Thanks=20mseed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes #327 --- src/floating.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/floating.c b/src/floating.c index 558f7f9a..349056a7 100644 --- a/src/floating.c +++ b/src/floating.c @@ -30,6 +30,10 @@ void floating_enable(Con *con, bool automatic) { * are children of the workspace. */ if (con->type == CT_WORKSPACE) { LOG("This is a workspace, creating new container around content\n"); + if (con_num_children(con) == 0) { + LOG("Workspace is empty, aborting\n"); + return; + } /* TODO: refactor this with src/con.c:con_set_layout */ Con *new = con_new(NULL); new->parent = con; From caa1ac1a9f6b99eb6a8fd303cf446fae8c9f7725 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 3 Mar 2011 16:22:22 +0100 Subject: [PATCH 487/867] Use the original geometry for floating windows --- src/floating.c | 5 ++- src/manage.c | 13 +++---- testcases/t/53-floating-originalsize.t | 52 ++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 testcases/t/53-floating-originalsize.t diff --git a/src/floating.c b/src/floating.c index 349056a7..4a2e904f 100644 --- a/src/floating.c +++ b/src/floating.c @@ -98,7 +98,10 @@ void floating_enable(Con *con, bool automatic) { int deco_height = font->height + 5; DLOG("Original rect: (%d, %d) with %d x %d\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height); - nc->rect = con->rect; + nc->rect = con->geometry; + /* Raise the width/height to at least 75x50 (minimum size for windows) */ + nc->rect.width = max(nc->rect.width, 75); + nc->rect.height = max(nc->rect.height, 50); /* add pixels for the decoration */ /* TODO: don’t add them when the user automatically puts new windows into * 1pixel/borderless mode */ diff --git a/src/manage.c b/src/manage.c index c602ee41..cd82d020 100644 --- a/src/manage.c +++ b/src/manage.c @@ -254,16 +254,15 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if (cwindow->dock) want_floating = false; + /* Store the requested geometry. The width/height gets raised to at least + * 75x50 when entering floating mode, which is the minimum size for a + * window to be useful (smaller windows are usually overlays/toolbars/… + * which are not managed by the wm anyways). We store the original geometry + * here because it’s used for dock clients. */ nc->geometry = (Rect){ geom->x, geom->y, geom->width, geom->height }; if (want_floating) { - nc->rect.x = geom->x; - nc->rect.y = geom->y; - /* We respect the geometry wishes of floating windows, as long as they - * are bigger than our minimal useful size (75x50). */ - nc->rect.width = max(geom->width, 75); - nc->rect.height = max(geom->height, 50); - DLOG("geometry = %d x %d\n", nc->rect.width, nc->rect.height); + DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height); floating_enable(nc, false); } diff --git a/testcases/t/53-floating-originalsize.t b/testcases/t/53-floating-originalsize.t new file mode 100644 index 00000000..65462e5f --- /dev/null +++ b/testcases/t/53-floating-originalsize.t @@ -0,0 +1,52 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Test if the requested width/height is set after making the window floating. +# +use Time::HiRes qw(sleep); +use X11::XCB qw(:all); +use i3test; + +my $tmp = get_unused_workspace; +cmd "workspace $tmp"; + +my $x = X11::XCB::Connection->new; + +# Create a floating window which is smaller than the minimum enforced size of i3 +my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 400, 150], + background_color => '#C0C0C0', +); + +isa_ok($window, 'X11::XCB::Window'); + +$window->map; + +sleep 0.25; + +my ($absolute, $top) = $window->rect; + +ok($window->mapped, 'Window is mapped'); +cmp_ok($absolute->{width}, '>', 400, 'i3 raised the width'); +cmp_ok($absolute->{height}, '>', 150, 'i3 raised the height'); + +cmd 'mode toggle'; +sleep 0.25; + +($absolute, $top) = $window->rect; + +diag('new width: ' . $absolute->{width}); +diag('new height: ' . $absolute->{height}); + +# we compare with a tolerance of ± 20 pixels for borders in each direction +# (overkill, but hey) +cmp_ok($absolute->{width}, '>', 400-20, 'width now > 380'); +cmp_ok($absolute->{width}, '<', 400+20, 'width now < 420'); +cmp_ok($absolute->{height}, '>', 150-20, 'height now > 130'); +cmp_ok($absolute->{height}, '<', 150+20, 'height now < 170'); + +#cmp_ok($absolute->{width}, '>=', 75, 'i3 raised the width to 75'); +#cmp_ok($absolute->{height}, '>=', 50, 'i3 raised the height to 50'); + +done_testing; From f6a21994bfd0edbf9376a9562ba7bdba3595e08a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 3 Mar 2011 16:36:18 +0100 Subject: [PATCH 488/867] Re-implement focus follows mouse for outputs That is, moving your mouse pointer to a different workspace which does not have any windows on it yet will correctly update the focus. --- src/handlers.c | 58 +++++++++++++++++++------------------------------- 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 35d8367a..0404b8bf 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -102,8 +102,6 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ return 1; } -#if 0 - /* * Called with coordinates of an enter_notify event or motion_notify event * to check if the user crossed virtual screen boundaries and adjust the @@ -111,41 +109,30 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ * */ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) { - Output *output; + Output *output; - if ((output = get_output_containing(x, y)) == NULL) { - ELOG("ERROR: No such screen\n"); - return; - } - if (output == c_ws->output) - return; + /* If the user disable focus follows mouse, we have nothing to do here */ + if (config.disable_focus_follows_mouse) + return; - c_ws->current_row = current_row; - c_ws->current_col = current_col; - c_ws = output->current_workspace; - current_row = c_ws->current_row; - current_col = c_ws->current_col; - DLOG("We're now on output %p\n", output); + if ((output = get_output_containing(x, y)) == NULL) { + ELOG("ERROR: No such screen\n"); + return; + } - /* While usually this function is only called when the user switches - * to a different output using his mouse (and thus the output is - * empty), it may be that the following race condition occurs: - * 1) the user actives a new output (say VGA1). - * 2) the cursor is sent to the first pixel of the new VGA1, thus - * generating an enter_notify for the screen (the enter_notify - * is not yet received by i3). - * 3) i3 requeries screen configuration and maps a workspace onto the - * new output. - * 4) the enter_notify event arrives and c_ws is set to the new - * workspace but the existing windows on the new workspace are not - * focused. - * - * Therefore, we re-set the focus here to be sure it’s correct. */ - Client *first_client = SLIST_FIRST(&(c_ws->focus_stack)); - if (first_client != NULL) - set_focus(global_conn, first_client, true); + if (output->con == NULL) { + ELOG("ERROR: The screen is not recognized by i3 (no container associated)\n"); + return; + } + + /* Focus the output on which the user moved his cursor */ + Con *old_focused = focused; + con_focus(con_descend_focused(output_get_content(output->con))); + + /* If the focus changed, we re-render to get updated decorations */ + if (old_focused != focused) + tree_render(); } -#endif /* * When the user moves the mouse pointer onto a window, this callback gets called. @@ -177,7 +164,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, /* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */ if (con == NULL) { DLOG("Getting screen at %d x %d\n", event->root_x, event->root_y); - //check_crossing_screen_boundary(event->root_x, event->root_y); + check_crossing_screen_boundary(event->root_x, event->root_y); return 1; } @@ -236,8 +223,7 @@ int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notif Con *con; if ((con = con_by_frame_id(event->event)) == NULL) { - /* TODO; handle root window: */ - //check_crossing_screen_boundary(event->root_x, event->root_y); + check_crossing_screen_boundary(event->root_x, event->root_y); return 1; } From 0a2ee1d2aa52c5bc793b2292a9d653fddb9a2395 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 4 Mar 2011 15:21:18 +0100 Subject: [PATCH 489/867] Bugfix: Focus workspace after closing one of multiple dock clients (+testcase) (Thanks mseed) --- src/con.c | 6 +++ testcases/t/54-regress-multiple-dock.t | 72 ++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 testcases/t/54-regress-multiple-dock.t diff --git a/src/con.c b/src/con.c index da41f944..01381206 100644 --- a/src/con.c +++ b/src/con.c @@ -617,6 +617,12 @@ Con *con_next_focused(Con *con) { return next; } + /* dock clients cannot be focused, so we focus the workspace instead */ + if (con->parent->type == CT_DOCKAREA) { + DLOG("selecting workspace for dock client\n"); + return con_descend_focused(output_get_content(con->parent->parent)); + } + /* try to focus the next container on the same level as this one */ next = TAILQ_NEXT(con, focused); diff --git a/testcases/t/54-regress-multiple-dock.t b/testcases/t/54-regress-multiple-dock.t new file mode 100644 index 00000000..9e7353d4 --- /dev/null +++ b/testcases/t/54-regress-multiple-dock.t @@ -0,0 +1,72 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Regression test for closing one of multiple dock clients +# +use X11::XCB qw(:all); +use Time::HiRes qw(sleep); +use i3test; + +BEGIN { + use_ok('X11::XCB::Window'); +} + +my $x = X11::XCB::Connection->new; +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace; +cmd "workspace $tmp"; + +##################################################################### +# verify that there is no dock window yet +##################################################################### + +# Children of all dockareas +my @docked = get_dock_clients; + +is(@docked, 0, 'no dock clients yet'); + +##################################################################### +# open a dock client +##################################################################### + +my $first = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), +); + +$first->map; + +sleep 0.25; + +##################################################################### +# Open a second dock client +##################################################################### + +my $second = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), +); + +$second->map; + +sleep 0.25; + +##################################################################### +# Kill the second dock client +##################################################################### +cmd "nop destroying dock client"; +$second->destroy; + +##################################################################### +# Now issue a focus command +##################################################################### +cmd 'next v'; + +does_i3_live; + +done_testing; From 24463718cc2ae6d44d75104bb8ade636985ff86c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 5 Mar 2011 20:23:29 +0100 Subject: [PATCH 490/867] refactor the click handling completely Also re-implements floating modifier on tiling cons. Fixes: #288 and a lot of headache :) --- src/click.c | 642 ++++++++++++++++------------------------------------ 1 file changed, 198 insertions(+), 444 deletions(-) diff --git a/src/click.c b/src/click.c index 51472954..8c0d7f66 100644 --- a/src/click.c +++ b/src/click.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009-2010 Michael Stapelberg and contributors + * © 2009-2011 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -21,142 +21,53 @@ #include "all.h" -#if 0 -static struct Stack_Window *get_stack_window(xcb_window_t window_id) { - struct Stack_Window *current; - SLIST_FOREACH(current, &stack_wins, stack_windows) { - if (current->window != window_id) - continue; - - return current; - } - - return NULL; -} +typedef enum { CLICK_BORDER = 0, CLICK_DECORATION = 1, CLICK_INSIDE = 2 } click_destination_t; /* - * Checks if the button press was on a stack window, handles focus setting and returns true - * if so, or false otherwise. + * Finds the correct pair of first/second cons between the resize will take + * place according to the passed border position (top, left, right, bottom), + * then calls resize_graphical_handler(). * */ -static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event_t *event) { - struct Stack_Window *stack_win; +static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event) { + DLOG("border = %d\n", border); + char way = (border == BORDER_TOP || border == BORDER_LEFT ? 'p' : 'n'); + orientation_t orientation = (border == BORDER_TOP || border == BORDER_BOTTOM ? VERT : HORIZ); - /* If we find a corresponding stack window, we can handle the event */ - if ((stack_win = get_stack_window(event->event)) == NULL) - return false; - - /* A stack window was clicked, we check if it was button4 or button5 - which are scroll up / scroll down. */ - if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) { - direction_t direction = (event->detail == XCB_BUTTON_INDEX_4 ? D_UP : D_DOWN); - focus_window_in_container(conn, CUR_CELL, direction); - return true; + /* look for a parent container with the right orientation */ + Con *first = NULL, *second = NULL; + Con *resize_con = con; + while (resize_con->type != CT_WORKSPACE && + resize_con->parent->orientation != orientation) + resize_con = resize_con->parent; + if (resize_con->type != CT_WORKSPACE && + resize_con->parent->orientation == orientation) { + first = resize_con; + second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes); + if (second == TAILQ_END(&(first->nodes_head))) { + second = NULL; } - - /* It was no scrolling, so we calculate the destination client by - dividing the Y position of the event through the height of a window - decoration and then set the focus to this client. */ - i3Font *font = load_font(conn, config.font); - int decoration_height = (font->height + 2 + 2); - int destination = (event->event_y / decoration_height), - c = 0, - num_clients = 0; - Client *client; - Container *container = stack_win->container; - - CIRCLEQ_FOREACH(client, &(container->clients), clients) - num_clients++; - - /* If we don’t have any clients in this container, we cannot do - * anything useful anyways. */ - if (num_clients == 0) - return true; - - if (container->mode == MODE_TABBED) - destination = (event->event_x / (container->width / num_clients)); - else if (container->mode == MODE_STACK && - container->stack_limit != STACK_LIMIT_NONE) { - if (container->stack_limit == STACK_LIMIT_COLS) { - int wrap = ceil((float)num_clients / container->stack_limit_value); - int clicked_column = (event->event_x / (stack_win->rect.width / container->stack_limit_value)); - int clicked_row = (event->event_y / decoration_height); - DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row); - destination = (wrap * clicked_column) + clicked_row; - } else { - int width = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value)); - int clicked_column = (event->event_x / width); - int clicked_row = (event->event_y / decoration_height); - DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row); - destination = (container->stack_limit_value * clicked_column) + clicked_row; - } - } - - DLOG("Click on stack_win for client %d\n", destination); - CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients) - if (c++ == destination) { - set_focus(conn, client, true); - return true; - } - - return true; -} - -/* - * Checks if the button press was on a bar, switches to the workspace and returns true - * if so, or false otherwise. - * - */ -static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *event) { - Output *output; - TAILQ_FOREACH(output, &outputs, outputs) { - if (output->bar != event->event) - continue; - - DLOG("Click on a bar\n"); - - /* Check if the button was one of button4 or button5 (scroll up / scroll down) */ - if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) { - Workspace *ws = c_ws; - if (event->detail == XCB_BUTTON_INDEX_5) { - while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) { - if (ws->output == output) { - workspace_show(conn, ws->num + 1); - return true; - } - } - } else { - while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) { - if (ws->output == output) { - workspace_show(conn, ws->num + 1); - return true; - } - } - } - return true; - } - int drawn = 0; - /* Because workspaces can be on different outputs, we need to loop - through all of them and decide to count it based on its ->output */ - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->output != output) - continue; - DLOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n", - ws->num, drawn, ws->text_width); - if (event->event_x > (drawn + 1) && - event->event_x <= (drawn + 1 + ws->text_width + 5 + 5)) { - workspace_show(conn, ws->num + 1); - return true; - } - - drawn += ws->text_width + 5 + 5 + 2; - } - return true; + else if (way == 'p') { + Con *tmp = first; + first = second; + second = tmp; } + } + if (first == NULL || second == NULL) { + DLOG("Resize not possible\n"); return false; + } + else { + assert(first != second); + assert(first->parent == second->parent); + resize_graphical_handler(first, second, orientation, event); + } + + DLOG("After resize handler, rendering\n"); + tree_render(); + return true; } /* @@ -167,346 +78,189 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e * to the client). * */ -static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client, - xcb_button_press_event_t *event) { - /* Only the right mouse button is interesting for us at the moment */ - if (event->detail != 3) - return false; +static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *event) { + /* The client is in tiling layout. We can still initiate a resize with the + * right mouse button, by chosing the border which is the most near one to + * the position of the mouse pointer */ + int to_right = con->rect.width - event->event_x, + to_left = event->event_x, + to_top = event->event_y, + to_bottom = con->rect.height - event->event_y; - /* The client is in tiling layout. We can still - * initiate a resize with the right mouse button, - * by chosing the border which is the most near one - * to the position of the mouse pointer */ - int to_right = client->rect.width - event->event_x, - to_left = event->event_x, - to_top = event->event_y, - to_bottom = client->rect.height - event->event_y; - resize_orientation_t orientation = O_VERTICAL; - Container *con = client->container; - Workspace *ws = con->workspace; - int first = 0, second = 0; + DLOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n", + to_right, to_left, to_top, to_bottom); - DLOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n", - to_right, to_left, to_top, to_bottom); + if (to_right < to_left && + to_right < to_top && + to_right < to_bottom) + return tiling_resize_for_border(con, BORDER_RIGHT, event); - if (to_right < to_left && - to_right < to_top && - to_right < to_bottom) { - /* …right border */ - first = con->col + (con->colspan - 1); - DLOG("column %d\n", first); + if (to_left < to_right && + to_left < to_top && + to_left < to_bottom) + return tiling_resize_for_border(con, BORDER_LEFT, event); - if (!cell_exists(ws, first, con->row) || - (first == (ws->cols-1))) - return false; + if (to_top < to_right && + to_top < to_left && + to_top < to_bottom) + return tiling_resize_for_border(con, BORDER_TOP, event); - second = first + 1; - } else if (to_left < to_right && - to_left < to_top && - to_left < to_bottom) { - /* …left border */ - if (con->col == 0) - return false; + if (to_bottom < to_right && + to_bottom < to_left && + to_bottom < to_top) + return tiling_resize_for_border(con, BORDER_BOTTOM, event); - first = con->col - 1; - second = con->col; - } else if (to_top < to_right && - to_top < to_left && - to_top < to_bottom) { - /* This was a press on the top border */ - if (con->row == 0) - return false; - first = con->row - 1; - second = con->row; - orientation = O_HORIZONTAL; - } else if (to_bottom < to_right && - to_bottom < to_left && - to_bottom < to_top) { - /* …bottom border */ - first = con->row + (con->rowspan - 1); - if (!cell_exists(ws, con->col, first) || - (first == (ws->rows-1))) - return false; + return false; +} - second = first + 1; - orientation = O_HORIZONTAL; +/* + * Finds out which border was clicked on and calls tiling_resize_for_border(). + * + */ +static bool tiling_resize(Con *con, xcb_button_press_event_t *event, click_destination_t dest) { + /* check if this was a click on the window border (and on which one) */ + Rect bsr = con_border_style_rect(con); + DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x\n", + event->event_x, event->event_y, con, event->event); + DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width); + if (dest == CLICK_DECORATION) + return tiling_resize_for_border(con, BORDER_TOP, event); + + if (event->event_x >= 0 && event->event_x <= bsr.x && + event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) + return tiling_resize_for_border(con, BORDER_LEFT, event); + + if (event->event_x >= (con->window_rect.x + con->window_rect.width) && + event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) + return tiling_resize_for_border(con, BORDER_RIGHT, event); + + if (event->event_y >= (con->window_rect.y + con->window_rect.height)) + return tiling_resize_for_border(con, BORDER_BOTTOM, event); + + return false; +} + +/* + * Being called by handle_button_press, this function calls the appropriate + * functions for resizing/dragging. + * + */ +static int route_click(Con *con, xcb_button_press_event_t *event, bool mod_pressed, click_destination_t dest) { + DLOG("--> click properties: mod = %d, destination = %d\n", mod_pressed, dest); + DLOG("--> OUTCOME = %p\n", con); + DLOG("type = %d, name = %s\n", con->type, con->name); + + /* get the floating con */ + Con *floatingcon = con_inside_floating(con); + const bool proportional = (event->state & BIND_SHIFT); + + /* 1: focus this con */ + con_focus(con); + + const bool in_stacked = (con->parent->layout == L_STACKED || con->parent->layout == L_TABBED); + + /* 2: for floating containers, we also want to raise them on click */ + if (floatingcon != NULL) { + floating_raise_con(floatingcon); + + /* 3: floating_modifier plus left mouse button drags */ + if (mod_pressed && event->detail == 1) { + floating_drag_window(floatingcon, event); + return 1; } - return resize_graphical_handler(conn, ws, first, second, orientation, event); -} -#endif + /* 3: resize (floating) if this was a click on the left/right/bottom + * border. also try resizing (tiling) if it was a click on the top + * border, but continue if that does not work */ + if (mod_pressed && event->detail == 3) { + DLOG("floating resize due to floatingmodifier\n"); + floating_resize_window(floatingcon, proportional, event); + return 1; + } + if (!in_stacked && dest == CLICK_DECORATION) { + /* try tiling resize, but continue if it doesn’t work */ + DLOG("tiling resize with fallback\n"); + if (tiling_resize(con, event, dest)) + goto done; + } + + if (dest == CLICK_BORDER) { + DLOG("floating resize due to border click\n"); + floating_resize_window(floatingcon, proportional, event); + return 1; + } + + /* 4: dragging, if this was a click on a decoration (which did not lead + * to a resize) */ + if (!in_stacked && dest == CLICK_DECORATION) { + floating_drag_window(floatingcon, event); + return 1; + } + + goto done; + } + + if (in_stacked) { + /* for stacked/tabbed cons, floating modifier + right click resizes the + * parent container */ + if (mod_pressed && event->detail == 3) + if (floating_mod_on_tiled_client(con->parent, event)) + return 1; + goto done; + } + + /* 3: floating modifier pressed, initiate a resize */ + if (mod_pressed && event->detail == 3) { + if (floating_mod_on_tiled_client(con, event)) + return 1; + } + /* 4: otherwise, check for border/decoration clicks and resize */ + else if (dest == CLICK_BORDER || dest == CLICK_DECORATION) { + DLOG("Should trry resizing (tiling)\n"); + tiling_resize(con, event, dest); + } + +done: + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + xcb_flush(conn); + tree_render(); + return 0; +} + +/* + * The button press X callback. This function determines whether the floating + * modifier is pressed and where the user clicked (decoration, border, inside + * the window). + * + * Then, route_click is called on the appropriate con. + * + */ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) { - /* TODO: dragging floating windows by grabbing their decoration does not - * work right now. We need to somehow recognize that special case: either - * we check if the con with the clicked decoration is the only con inside - * its parent (so that you can only drag single floating windows, not - * floating containers with multiple windows) *or* we somehow find out - * which decoration(s) are at the top and enable grabbing for them while - * resizing for the others. Maybe we could process the resizing parameters - * first and check if resizing is possible: if yes, resize, if not, drag. - * - * Also raise on click on decoration is not working. */ Con *con; DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event); - con = con_by_window_id(event->event); - bool border_click = (con == NULL); const uint32_t mod = config.floating_modifier; bool mod_pressed = (mod != 0 && (event->state & mod) == mod); + DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail); + if ((con = con_by_window_id(event->event))) + return route_click(con, event, mod_pressed, CLICK_INSIDE); - if (border_click) - con = con_by_frame_id(event->event); - - DLOG("border_click = %d, mod_pressed = %d\n", border_click, mod_pressed); - - Con *clicked_into = NULL; + if (!(con = con_by_frame_id(event->event))) { + ELOG("Clicked into unknown window?!\n"); + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + xcb_flush(conn); + return 0; + } + /* Check if the click was on the decoration of a child */ Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) { if (!rect_contains(child->deco_rect, event->event_x, event->event_y)) continue; - clicked_into = child; - break; + return route_click(child, event, mod_pressed, CLICK_DECORATION); } - DLOG("clicked_into = %p\n", clicked_into); - - /* See if this was a click with the configured modifier. If so, we need - * to move around the client if it was floating. if not, we just process - * as usual. */ - DLOG("state = %d, floating_modifier = %d\n", event->state, config.floating_modifier); - if (border_click || mod_pressed) { - if (con == NULL) { - LOG("Not handling, floating_modifier was pressed and no client found\n"); - return 1; - } - DLOG("handling\n"); -#if 0 - if (con->fullscreen) { - LOG("Not handling, client is in fullscreen mode\n"); - return 1; - } -#endif - Con *floatingcon = con; - if ((border_click && con->type == CT_FLOATING_CON) || - ((floatingcon = con_inside_floating(con)) != NULL && - clicked_into == NULL)) { - LOG("button %d pressed\n", event->detail); - if (event->detail == 1) { - LOG("left mouse button, dragging\n"); - floating_drag_window(floatingcon, event); - } - else if (event->detail == 3) { - bool proportional = (event->state & BIND_SHIFT); - DLOG("right mouse button\n"); - floating_resize_window(floatingcon, proportional, event); - } - return 1; - } - } - - if ((clicked_into ? clicked_into->parent->type : con->parent->type) == CT_DOCKAREA) { - DLOG("Not handling, this client is inside a dockarea\n"); - xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); - xcb_flush(conn); - return 0; - } - - /* click to focus, either on the clicked window or its child if thas was a - * click into a child decoration */ - con_focus((clicked_into ? clicked_into : con)); - - /* for floating containers, we also want to raise them on click */ - Con *floatingcon = con_inside_floating(con); - if (floatingcon != NULL) - floating_raise_con(floatingcon); - - tree_render(); - - /* if we clicked into a child decoration on a stacked/tabbed container, we - * are done and don’t want to resize */ - if (clicked_into && - (con->layout == L_STACKED || con->layout == L_TABBED)) - return 1; - - /* check if this was a click on the window border (and on which one) */ - Rect bsr = con_border_style_rect(con); - DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x, border_click = %d, clicked_into = %p\n", - event->event_x, event->event_y, con, event->event, border_click, clicked_into); - DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width); - char way = '\0'; - int orientation; - if (clicked_into) { - DLOG("BORDER top\n"); - way = 'p'; - orientation = VERT; - } else if (event->event_x >= 0 && event->event_x <= bsr.x && - event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) { - DLOG("BORDER left\n"); - way = 'p'; - orientation = HORIZ; - } else if (event->event_x >= (con->window_rect.x + con->window_rect.width) && - event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) { - DLOG("BORDER right\n"); - way = 'n'; - orientation = HORIZ; - } else if (event->event_y >= (con->window_rect.y + con->window_rect.height)) { - DLOG("BORDER bottom\n"); - way = 'n'; - orientation = VERT; - } - - /* look for a parent container with the right orientation */ - Con *first = NULL, *second = NULL; - if (way != '\0') { - Con *resize_con = clicked_into ? clicked_into : con; - while (resize_con->type != CT_WORKSPACE && - resize_con->parent->orientation != orientation) - resize_con = resize_con->parent; - if (resize_con->type != CT_WORKSPACE && - resize_con->parent->orientation == orientation) { - first = resize_con; - second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes); - if (second == TAILQ_END(&(first->nodes_head))) { - second = NULL; - } - else if (way == 'p') { - Con *tmp = first; - first = second; - second = tmp; - } - } - } - - if (first == NULL || second == NULL) { - DLOG("Replaying click\n"); - /* No border click, replay the click */ - xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); - xcb_flush(conn); - return 0; - } - else { - assert(first != second); - assert(first->parent == second->parent); - resize_graphical_handler(first, second, orientation, event); - } - - DLOG("After resize handler, rendering\n"); - tree_render(); - - return 0; -#if 0 - if (client == NULL) { - /* The client was neither on a client’s titlebar nor on a client itself, maybe on a stack_window? */ - if (button_press_stackwin(conn, event)) - return 1; - - /* Or on a bar? */ - if (button_press_bar(conn, event)) - return 1; - - DLOG("Could not handle this button press\n"); - return 1; - } - - /* Set focus in any case */ - set_focus(conn, client, true); - - /* Let’s see if this was on the borders (= resize). If not, we’re done */ - DLOG("press button on x=%d, y=%d\n", event->event_x, event->event_y); - resize_orientation_t orientation = O_VERTICAL; - Container *con = client->container; - int first, second; - - if (client->dock) { - DLOG("dock. done.\n"); - xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); - xcb_flush(conn); - return 1; - } - - DLOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width); - - /* Some clients (xfontsel for example) seem to pass clicks on their - * window to the parent window, thus we receive an event here which in - * reality is a border_click. Check for the position and fix state. */ - if (border_click && - event->event_x >= client->child_rect.x && - event->event_x <= (client->child_rect.x + client->child_rect.width) && - event->event_y >= client->child_rect.y && - event->event_y <= (client->child_rect.y + client->child_rect.height)) { - DLOG("Fixing border_click = false because of click in child\n"); - border_click = false; - } - - if (!border_click) { - DLOG("client. done.\n"); - xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); - /* Floating clients should be raised on click */ - if (client_is_floating(client)) - xcb_raise_window(conn, client->frame); - xcb_flush(conn); - return 1; - } - - /* Don’t handle events inside the titlebar, only borders are interesting */ - i3Font *font = load_font(conn, config.font); - if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) { - DLOG("click on titlebar\n"); - - /* Floating clients can be dragged by grabbing their titlebar */ - if (client_is_floating(client)) { - /* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */ - xcb_raise_window(conn, client->frame); - xcb_flush(conn); - - floating_drag_window(conn, client, event); - } - return 1; - } - - if (client_is_floating(client)) - return floating_border_click(conn, client, event); - - Workspace *ws = con->workspace; - - if (event->event_y < 2) { - /* This was a press on the top border */ - if (con->row == 0) - return 1; - first = con->row - 1; - second = con->row; - orientation = O_HORIZONTAL; - } else if (event->event_y >= (client->rect.height - 2)) { - /* …bottom border */ - first = con->row + (con->rowspan - 1); - if (!cell_exists(ws, con->col, first) || - (first == (ws->rows-1))) - return 1; - - second = first + 1; - orientation = O_HORIZONTAL; - } else if (event->event_x <= 2) { - /* …left border */ - if (con->col == 0) - return 1; - - first = con->col - 1; - second = con->col; - } else if (event->event_x > 2) { - /* …right border */ - first = con->col + (con->colspan - 1); - DLOG("column %d\n", first); - - if (!cell_exists(ws, first, con->row) || - (first == (ws->cols-1))) - return 1; - - second = first + 1; - } - - return resize_graphical_handler(conn, ws, first, second, orientation, event); -#endif + return route_click(con, event, mod_pressed, CLICK_BORDER); } From 5024c0da95838ad67b076a00e9b8f89d37577d24 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 5 Mar 2011 20:35:16 +0100 Subject: [PATCH 491/867] document the different cases for click handling --- docs/hacking-howto | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/hacking-howto b/docs/hacking-howto index 48d717f1..03da8ef1 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -612,6 +612,21 @@ When moving up/down, the container needs to leave the floating container and it needs to be placed on the workspace (at workspace level). This is accomplished by calling the function +attach_to_workspace+. +== Click handling + +Without much ado, here is the list of cases which need to be considered: + +* click to focus (tiling + floating) and raise (floating) +* click to focus/raise when in stacked/tabbed mode +* floating_modifier + left mouse button to drag a floating con +* floating_modifier + right mouse button to resize a floating con +* click on decoration in a floating con to either initiate a resize (if there + is more than one child in the floating con) or to drag the + floating con (if it’s the one at the top). +* click on border in a floating con to resize the floating con +* floating_modifier + right mouse button to resize a tiling con +* click on border/decoration to resize a tiling con + == Gotchas * Forgetting to call `xcb_flush(conn);` after sending a request. This usually From 88ab66e742c62bae389116065b6e2267a51b1d8c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2011 02:06:05 +0100 Subject: [PATCH 492/867] =?UTF-8?q?Bugfix:=20don=E2=80=99t=20focus=20dock?= =?UTF-8?q?=20clients=20with=20new=20click=20handling=20code=20(Thanks=20m?= =?UTF-8?q?seed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/click.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/click.c b/src/click.c index 8c0d7f66..8028f683 100644 --- a/src/click.c +++ b/src/click.c @@ -150,6 +150,10 @@ static int route_click(Con *con, xcb_button_press_event_t *event, bool mod_press DLOG("--> OUTCOME = %p\n", con); DLOG("type = %d, name = %s\n", con->type, con->name); + /* don’t handle dockarea cons, they must not be focused */ + if (con->parent->type == CT_DOCKAREA) + goto done; + /* get the floating con */ Con *floatingcon = con_inside_floating(con); const bool proportional = (event->state & BIND_SHIFT); From 4514146ed96b81cfa3238f686e342452fa34acb8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2011 02:38:21 +0100 Subject: [PATCH 493/867] Use the combined geometry of children when setting a split container to floating (+testcase) (Thanks mseed) Fixes #332 --- src/floating.c | 12 +++++ src/x.c | 3 +- testcases/t/55-floating-split-size.t | 70 ++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 testcases/t/55-floating-split-size.t diff --git a/src/floating.c b/src/floating.c index 4a2e904f..2218da29 100644 --- a/src/floating.c +++ b/src/floating.c @@ -98,7 +98,19 @@ void floating_enable(Con *con, bool automatic) { int deco_height = font->height + 5; DLOG("Original rect: (%d, %d) with %d x %d\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height); + Rect zero = { 0, 0, 0, 0 }; nc->rect = con->geometry; + /* If the geometry was not set (split containers), we need to determine a + * sensible one by combining the geometry of all children */ + if (memcmp(&(nc->rect), &zero, sizeof(Rect)) == 0) { + DLOG("Geometry not set, combining children\n"); + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + DLOG("child geometry: %d x %d\n", child->geometry.width, child->geometry.height); + nc->rect.width += child->geometry.width; + nc->rect.height = max(nc->rect.height, child->geometry.height); + } + } /* Raise the width/height to at least 75x50 (minimum size for windows) */ nc->rect.width = max(nc->rect.width, 75); nc->rect.height = max(nc->rect.height, 50); diff --git a/src/x.c b/src/x.c index 5a13b51a..f932f9cb 100644 --- a/src/x.c +++ b/src/x.c @@ -149,8 +149,7 @@ void x_move_win(Con *src, Con *dest) { return; } - Rect zero; - memset(&zero, 0, sizeof(Rect)); + Rect zero = { 0, 0, 0, 0 }; if (memcmp(&(state_dest->window_rect), &(zero), sizeof(Rect)) == 0) { memcpy(&(state_dest->window_rect), &(state_src->window_rect), sizeof(Rect)); DLOG("COPYING RECT\n"); diff --git a/testcases/t/55-floating-split-size.t b/testcases/t/55-floating-split-size.t new file mode 100644 index 00000000..26671eb0 --- /dev/null +++ b/testcases/t/55-floating-split-size.t @@ -0,0 +1,70 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Test to see if i3 combines the geometry of all children in a split container +# when setting the split container to floating +# +use X11::XCB qw(:all); +use Time::HiRes qw(sleep); +use i3test; + +BEGIN { + use_ok('X11::XCB::Window'); +} + +my $x = X11::XCB::Connection->new; +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace; +cmd "workspace $tmp"; + +##################################################################### +# open a window with 200x80 +##################################################################### + +my $first = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 200, 80], + background_color => '#FF0000', +); + +$first->map; + +sleep 0.25; + +##################################################################### +# Open a second window with 300x90 +##################################################################### + +my $second = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 300, 90], + background_color => '#00FF00', +); + +$second->map; + +sleep 0.25; + +##################################################################### +# Set the parent to floating +##################################################################### +cmd 'nop setting floating'; +cmd 'level up'; +cmd 'mode floating'; + +##################################################################### +# Get geometry of the first floating node (the split container) +##################################################################### + +my @nodes = @{get_ws($tmp)->{floating_nodes}}; +my $rect = $nodes[0]->{rect}; + +# we compare the width with ± 20 pixels for borders +cmp_ok($rect->{width}, '>', 500-20, 'width now > 480'); +cmp_ok($rect->{width}, '<', 500+20, 'width now < 520'); +# we compare the height with ± 40 pixels for decorations +cmp_ok($rect->{height}, '>', 90-40, 'width now > 50'); +cmp_ok($rect->{height}, '<', 90+40, 'width now < 130'); + +done_testing; From 1891a385ad0288f66384cab69f2f7ae4ef7e5eec Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2011 13:35:07 +0100 Subject: [PATCH 494/867] Bugfix: also stop searching for resize cons when reaching a floatingcon (Thanks julien) --- src/click.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/click.c b/src/click.c index 8028f683..31b925fa 100644 --- a/src/click.c +++ b/src/click.c @@ -39,9 +39,12 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press Con *first = NULL, *second = NULL; Con *resize_con = con; while (resize_con->type != CT_WORKSPACE && - resize_con->parent->orientation != orientation) + resize_con->type != CT_FLOATING_CON && + resize_con->parent->orientation != orientation) resize_con = resize_con->parent; + if (resize_con->type != CT_WORKSPACE && + resize_con->type != CT_FLOATING_CON && resize_con->parent->orientation == orientation) { first = resize_con; second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes); From 51ff0f80a6068099ca635e685fdcd554718b57e2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2011 14:15:46 +0100 Subject: [PATCH 495/867] Obey minimum size when resizing floating windows Fixes #285 --- include/con.h | 7 ++++++ src/con.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/floating.c | 8 +++---- 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/include/con.h b/include/con.h index 8faf43fc..8ffa7ce2 100644 --- a/include/con.h +++ b/include/con.h @@ -195,4 +195,11 @@ int con_border_style(Con *con); */ void con_set_layout(Con *con, int layout); +/** + * Determines the minimum size of the given con by looking at its children (for + * split/stacked/tabbed cons). Will be called when resizing floating cons + * + */ +Rect con_minimum_size(Con *con); + #endif diff --git a/src/con.c b/src/con.c index 01381206..91f11d24 100644 --- a/src/con.c +++ b/src/con.c @@ -820,3 +820,61 @@ static void con_on_remove_child(Con *con) { return; } } + +/* + * Determines the minimum size of the given con by looking at its children (for + * split/stacked/tabbed cons). Will be called when resizing floating cons + * + */ +Rect con_minimum_size(Con *con) { + DLOG("Determining minimum size for con %p\n", con); + + if (con_is_leaf(con)) { + DLOG("leaf node, returning 75x50\n"); + return (Rect){ 0, 0, 75, 50 }; + } + + if (con->type == CT_FLOATING_CON) { + DLOG("floating con\n"); + Con *child = TAILQ_FIRST(&(con->nodes_head)); + return con_minimum_size(child); + } + + if (con->layout == L_STACKED || con->layout == L_TABBED) { + uint32_t max_width = 0, max_height = 0, deco_height = 0; + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + Rect min = con_minimum_size(child); + deco_height += child->deco_rect.height; + max_width = max(max_width, min.width); + max_height = max(max_height, min.height); + } + DLOG("stacked/tabbed now, returning %d x %d + deco_rect = %d\n", + max_width, max_height, deco_height); + return (Rect){ 0, 0, max_width, max_height + deco_height }; + } + + /* For horizontal/vertical split containers we sum up the width (h-split) + * or height (v-split) and use the maximum of the height (h-split) or width + * (v-split) as minimum size. */ + if (con->orientation == HORIZ || con->orientation == VERT) { + uint32_t width = 0, height = 0; + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + Rect min = con_minimum_size(child); + if (con->orientation == HORIZ) { + width += min.width; + height = max(height, min.height); + } else { + height += min.height; + width = max(width, min.width); + } + } + DLOG("split container, returning width = %d x height = %d\n", width, height); + return (Rect){ 0, 0, width, height }; + } + + ELOG("Unhandled case, type = %d, layout = %d, orientation = %d\n", + con->type, con->layout, con->orientation); + assert(false); +} diff --git a/src/floating.c b/src/floating.c index 2218da29..6aea7a91 100644 --- a/src/floating.c +++ b/src/floating.c @@ -284,12 +284,10 @@ DRAGGING_CB(resize_window_callback) { dest_height = old_rect->height - (new_y - event->root_y); else dest_height = old_rect->height + (new_y - event->root_y); - /* TODO: minimum window size */ -#if 0 /* Obey minimum window size */ - dest_width = max(dest_width, client_min_width(client)); - dest_height = max(dest_height, client_min_height(client)); -#endif + Rect minimum = con_minimum_size(con); + dest_width = max(dest_width, minimum.width); + dest_height = max(dest_height, minimum.height); /* User wants to keep proportions, so we may have to adjust our values */ if (params->proportional) { From 7154fecbbf61e69ff9d514082bad946d4501a90f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2011 15:45:42 +0100 Subject: [PATCH 496/867] Implement the popup_during_fullscreen option, set default to leave_fullscreen Fixes #333 --- include/config.h | 6 ++++++ src/cfgparse.l | 3 +++ src/cfgparse.y | 17 +++++++++++++++++ src/manage.c | 13 ++++++++++++- 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/include/config.h b/include/config.h index 6ab6baee..986f7aa2 100644 --- a/include/config.h +++ b/include/config.h @@ -127,6 +127,12 @@ struct Config { struct Colortriple unfocused; struct Colortriple urgent; } bar; + + /** What should happen when a new popup is opened during fullscreen mode */ + enum { + PDF_LEAVE_FULLSCREEN = 0, + PDF_IGNORE = 1 + } popup_during_fullscreen; }; /** diff --git a/src/cfgparse.l b/src/cfgparse.l index 66afb14b..b8b1d73d 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -99,6 +99,9 @@ none { return TOK_NONE; } 1pixel { return TOK_1PIXEL; } focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; } workspace_bar { return TOKWORKSPACEBAR; } +popup_during_fullscreen { return TOK_POPUP_DURING_FULLSCREEN; } +ignore { return TOK_IGNORE; } +leave_fullscreen { return TOK_LEAVE_FULLSCREEN; } default { /* yylval.number = MODE_DEFAULT; */return TOKCONTAINERMODE; } stacking { /* yylval.number = MODE_STACK; */return TOKCONTAINERMODE; } tabbed { /* yylval.number = MODE_TABBED; */return TOKCONTAINERMODE; } diff --git a/src/cfgparse.y b/src/cfgparse.y index d3a5ecda..adb02122 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -233,6 +233,9 @@ void parse_file(const char *f) { %token TOKWORKSPACEBAR "workspace_bar" %token TOKCONTAINERMODE "default/stacking/tabbed" %token TOKSTACKLIMIT "stack-limit" +%token TOK_POPUP_DURING_FULLSCREEN "popup_during_fullscreen" +%token TOK_IGNORE "ignore" +%token TOK_LEAVE_FULLSCREEN "leave_fullscreen" %% @@ -260,6 +263,7 @@ line: | terminal | font | comment + | popup_during_fullscreen ; comment: @@ -641,3 +645,16 @@ binding_modifier: | TOKCONTROL { $$ = BIND_CONTROL; } | TOKSHIFT { $$ = BIND_SHIFT; } ; + +popup_during_fullscreen: + TOK_POPUP_DURING_FULLSCREEN WHITESPACE popup_setting + { + DLOG("popup_during_fullscreen setting: %d\n", $3); + config.popup_during_fullscreen = $3; + } + ; + +popup_setting: + TOK_IGNORE { $$ = PDF_IGNORE; } + | TOK_LEAVE_FULLSCREEN { $$ = PDF_LEAVE_FULLSCREEN; } + ; diff --git a/src/manage.c b/src/manage.c index cd82d020..53526f6a 100644 --- a/src/manage.c +++ b/src/manage.c @@ -247,9 +247,20 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if (cwindow->transient_for != XCB_NONE || (cwindow->leader != XCB_NONE && cwindow->leader != cwindow->id && - con_by_window_id(cwindow->leader) != NULL)) + con_by_window_id(cwindow->leader) != NULL)) { + LOG("This window is transiert for another window, setting floating\n"); want_floating = true; + if (config.popup_during_fullscreen == PDF_LEAVE_FULLSCREEN) { + Con *ws = con_get_workspace(nc); + Con *fs = con_get_fullscreen_con(ws); + if (fs != NULL) { + LOG("There is a fullscreen window, leaving fullscreen mode\n"); + con_toggle_fullscreen(fs); + } + } + } + /* dock clients cannot be floating, that makes no sense */ if (cwindow->dock) want_floating = false; From 0689f6d8f1c14d7730ad229d83f1689d66382064 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2011 20:45:03 +0100 Subject: [PATCH 497/867] Bugfix: use tree_render() instead of x_push_changes() to re-render and update the stack --- src/handlers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index 0404b8bf..e8506843 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -204,7 +204,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, return 1; con_focus(con_descend_focused(con)); - x_push_changes(croot); + tree_render(); return 1; } From 4f26316aaae9a9eac0a56f40f45136bbf70b1844 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2011 21:48:49 +0100 Subject: [PATCH 498/867] x: disable all events while re-stacking windows, prevents unwanted EnterNotifys --- src/x.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/x.c b/src/x.c index f932f9cb..03a4f851 100644 --- a/src/x.c +++ b/src/x.c @@ -571,6 +571,12 @@ void x_push_changes(Con *con) { con_state *state; DLOG("-- PUSHING WINDOW STACK --\n"); + DLOG("Disabling EnterNotify\n"); + uint32_t values[1] = { XCB_NONE }; + CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { + xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); + } + DLOG("Done, EnterNotify disabled\n"); bool order_changed = false; /* X11 correctly represents the stack if we push it from bottom to top */ CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { @@ -590,6 +596,15 @@ void x_push_changes(Con *con) { } state->initial = false; } + DLOG("Re-enabling EnterNotify\n"); + values[0] = FRAME_EVENT_MASK; + CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { + xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); + } + DLOG("Done, EnterNotify re-enabled\n"); + + free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL)); + DLOG("\n\n PUSHING CHANGES\n\n"); x_push_node(con); @@ -631,8 +646,8 @@ void x_push_changes(Con *con) { */ void x_raise_con(Con *con) { con_state *state; - DLOG("raising in new stack: %p / %s\n", con, con->name); state = state_for_frame(con->frame); + DLOG("raising in new stack: %p / %s / %s / xid %08x\n", con, con->name, con->window ? con->window->name_json : "", state->id); CIRCLEQ_REMOVE(&state_head, state, state); CIRCLEQ_INSERT_HEAD(&state_head, state, state); From b0b195318c9c264e1b053f286e61df3ef854cff0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2011 21:49:17 +0100 Subject: [PATCH 499/867] rendering: bugfix: stack child windows of stacked/tabbed cons according to their focus fixes focusing the wrong window after closing a con --- src/render.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/render.c b/src/render.c index 499c62a3..37ee6e47 100644 --- a/src/render.c +++ b/src/render.c @@ -323,13 +323,17 @@ void render_con(Con *con, bool render_fullscreen) { /* in a stacking or tabbed container, we ensure the focused client is raised */ if (con->layout == L_STACKED || con->layout == L_TABBED) { - Con *foc = TAILQ_FIRST(&(con->focus_head)); - if (foc != TAILQ_END(&(con->focus_head))) { - DLOG("con %p is stacking, raising %p\n", con, foc); - x_raise_con(foc); - /* by rendering the stacked container again, we handle the case - * that we have a non-leaf-container inside the stack. */ - render_con(foc, false); + DLOG("stacked/tabbed, raising focused reverse\n"); + TAILQ_FOREACH_REVERSE(child, &(con->focus_head), focus_head, focused) + x_raise_con(child); + DLOG("done\n"); + if ((child = TAILQ_FIRST(&(con->focus_head)))) { + DLOG("con %p is stacking, raising %p\n", con, child); + /* By rendering the stacked container again, we handle the case + * that we have a non-leaf-container inside the stack. In that + * case, the children of the non-leaf-container need to be raised + * aswell. */ + render_con(child, false); } } } From 1585c36ab2bda83694e23a99ff41c6e256fb690a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2011 21:49:53 +0100 Subject: [PATCH 500/867] x: remove XCB_EVENT_MASK_ENTER_WINDOW from child event mask, already handled in parent This prevents unwanted EnterNotifys when switching cons in a stacked con with an h-split as second child in the stacked con. --- include/xcb.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/xcb.h b/include/xcb.h index 4b01d900..1dc141e6 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -32,8 +32,7 @@ while rendering the layout) */ /** The XCB_CW_EVENT_MASK for the child (= real window) */ #define CHILD_EVENT_MASK (XCB_EVENT_MASK_PROPERTY_CHANGE | \ - XCB_EVENT_MASK_STRUCTURE_NOTIFY | \ - XCB_EVENT_MASK_ENTER_WINDOW) + XCB_EVENT_MASK_STRUCTURE_NOTIFY) /** The XCB_CW_EVENT_MASK for its frame */ #define FRAME_EVENT_MASK (XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed/released */ \ From c17b3b756089dd0d27daba287a15aeb867b2274d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2011 21:56:15 +0100 Subject: [PATCH 501/867] remove left-over xcb_aux_sync (debugging code) --- src/x.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/x.c b/src/x.c index 03a4f851..d4b95315 100644 --- a/src/x.c +++ b/src/x.c @@ -603,9 +603,6 @@ void x_push_changes(Con *con) { } DLOG("Done, EnterNotify re-enabled\n"); - free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL)); - - DLOG("\n\n PUSHING CHANGES\n\n"); x_push_node(con); From 8ce5f2a21bfc2194a0cca1da223778bcb29872af Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2011 22:02:02 +0100 Subject: [PATCH 502/867] Bugfix: Fix crash with transient dock clients caused by 7154fecbb --- src/manage.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/manage.c b/src/manage.c index 53526f6a..192bcbf3 100644 --- a/src/manage.c +++ b/src/manage.c @@ -252,9 +252,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki want_floating = true; if (config.popup_during_fullscreen == PDF_LEAVE_FULLSCREEN) { - Con *ws = con_get_workspace(nc); - Con *fs = con_get_fullscreen_con(ws); - if (fs != NULL) { + Con *ws, *fs; + if ((ws = con_get_workspace(nc)) && + (fs = con_get_fullscreen_con(ws))) { LOG("There is a fullscreen window, leaving fullscreen mode\n"); con_toggle_fullscreen(fs); } From 287d7f9527838dc3fa4720af2cf1e3069e1f6e54 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2011 23:26:02 +0100 Subject: [PATCH 503/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20focus=20new?= =?UTF-8?q?=20cons=20when=20there=20is=20a=20fullscreen=20con=20(Thanks=20?= =?UTF-8?q?dothebart)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also, remove the focus_it parameter from tree_open_con, it makes more sense to call con_focus outside of the function. --- include/tree.h | 2 +- src/cmdparse.y | 3 ++- src/manage.c | 29 +++++++++++++++++++---------- src/tree.c | 6 +----- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/include/tree.h b/include/tree.h index cdcb4878..40d9a541 100644 --- a/include/tree.h +++ b/include/tree.h @@ -24,7 +24,7 @@ void tree_init(); * Opens an empty container in the current container * */ -Con *tree_open_con(Con *con, bool focus_it); +Con *tree_open_con(Con *con); /** * Splits (horizontally or vertically) the given container by creating a new diff --git a/src/cmdparse.y b/src/cmdparse.y index 33724264..0abcab08 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -403,7 +403,8 @@ open: TOK_OPEN { printf("opening new container\n"); - Con *con = tree_open_con(NULL, true); + Con *con = tree_open_con(NULL); + con_focus(con); asprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con); } ; diff --git a/src/manage.c b/src/manage.c index 192bcbf3..789bd8a9 100644 --- a/src/manage.c +++ b/src/manage.c @@ -201,7 +201,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki LOG("using current container, focused = %p, focused->name = %s\n", focused, focused->name); nc = focused; - } else nc = tree_open_con(NULL, true); + } else nc = tree_open_con(NULL); } else { /* M_ACTIVE are assignments */ if (match != NULL && match->insert_where == M_ACTIVE) { @@ -213,13 +213,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* We need to open a new con */ /* TODO: make a difference between match-once containers (directly assign * cwindow) and match-multiple (tree_open_con first) */ - nc = tree_open_con(nc->parent, true); + nc = tree_open_con(nc->parent); } /* M_BELOW inserts the new window as a child of the one which was * matched (e.g. dock areas) */ else if (match != NULL && match->insert_where == M_BELOW) { - nc = tree_open_con(nc, !cwindow->dock); + nc = tree_open_con(nc); } } @@ -234,6 +234,18 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki x_set_name(nc, name); free(name); + Con *ws = con_get_workspace(nc); + Con *fs = (ws ? con_get_fullscreen_con(ws) : NULL); + + if (fs == NULL) { + DLOG("Not in fullscreen mode, focusing\n"); + if (!cwindow->dock) + con_focus(nc); + else DLOG("dock, not focusing\n"); + } else { + DLOG("fs = %p, ws = %p, not focusing\n", fs, ws); + } + /* set floating if necessary */ bool want_floating = false; if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DIALOG]) || @@ -251,13 +263,10 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki LOG("This window is transiert for another window, setting floating\n"); want_floating = true; - if (config.popup_during_fullscreen == PDF_LEAVE_FULLSCREEN) { - Con *ws, *fs; - if ((ws = con_get_workspace(nc)) && - (fs = con_get_fullscreen_con(ws))) { - LOG("There is a fullscreen window, leaving fullscreen mode\n"); - con_toggle_fullscreen(fs); - } + if (config.popup_during_fullscreen == PDF_LEAVE_FULLSCREEN && + fs != NULL) { + LOG("There is a fullscreen window, leaving fullscreen mode\n"); + con_toggle_fullscreen(fs); } } diff --git a/src/tree.c b/src/tree.c index 3c209fbc..ef254a8f 100644 --- a/src/tree.c +++ b/src/tree.c @@ -55,7 +55,7 @@ void tree_init() { * Opens an empty container in the current container * */ -Con *tree_open_con(Con *con, bool focus_it) { +Con *tree_open_con(Con *con) { if (con == NULL) { /* every focusable Con has a parent (outputs have parent root) */ con = focused->parent; @@ -80,10 +80,6 @@ Con *tree_open_con(Con *con, bool focus_it) { /* 4: re-calculate child->percent for each child */ con_fix_percent(con); - /* 5: focus the new container */ - if (focus_it) - con_focus(new); - return new; } From 0a240572410ac7cfbe92233d50c671be77d83877 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 7 Mar 2011 00:06:27 +0100 Subject: [PATCH 504/867] When leaving fullscreen, set focus to con which was opened during fullscreen (+testcase) (Thanks dothebart) --- src/manage.c | 6 +++ testcases/t/56-fullscreen-focus.t | 65 +++++++++++++++++++++++++++++++ testcases/t/lib/i3test.pm | 7 +++- 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 testcases/t/56-fullscreen-focus.t diff --git a/src/manage.c b/src/manage.c index 789bd8a9..1465457e 100644 --- a/src/manage.c +++ b/src/manage.c @@ -244,6 +244,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki else DLOG("dock, not focusing\n"); } else { DLOG("fs = %p, ws = %p, not focusing\n", fs, ws); + /* Insert the new container in focus stack *after* the currently + * focused (fullscreen) con. This way, the new container will be + * focused after we return from fullscreen mode */ + Con *first = TAILQ_FIRST(&(nc->parent->focus_head)); + TAILQ_REMOVE(&(nc->parent->focus_head), nc, focused); + TAILQ_INSERT_AFTER(&(nc->parent->focus_head), first, nc, focused); } /* set floating if necessary */ diff --git a/testcases/t/56-fullscreen-focus.t b/testcases/t/56-fullscreen-focus.t new file mode 100644 index 00000000..7d8b16ca --- /dev/null +++ b/testcases/t/56-fullscreen-focus.t @@ -0,0 +1,65 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Test if new containers get focused when there is a fullscreen container at +# the time of launching the new one. +# +use X11::XCB qw(:all); +use Time::HiRes qw(sleep); +use i3test; + +BEGIN { + use_ok('X11::XCB::Window'); +} + +my $x = X11::XCB::Connection->new; +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace; +cmd "workspace $tmp"; + +##################################################################### +# open the left window +##################################################################### + +my $left = open_standard_window($x, '#ff0000'); + +is($x->input_focus, $left->id, 'left window focused'); + +diag("left = " . $left->id); + +##################################################################### +# Open the right window +##################################################################### + +my $right = open_standard_window($x, '#00ff00'); + +diag("right = " . $right->id); + +##################################################################### +# Set the right window to fullscreen +##################################################################### +cmd 'nop setting fullscreen'; +cmd 'fullscreen'; + +##################################################################### +# Open a third window +##################################################################### + +my $third = open_standard_window($x, '#0000ff'); + +diag("third = " . $third->id); + +# move the fullscreen window to a different ws + +my $tmp2 = get_unused_workspace; + +cmd "move workspace $tmp2"; + +# verify that the third window has the focus + +sleep 0.25; + +is($x->input_focus, $third->id, 'third window focused'); + +done_testing; diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index b9d168d6..87a34981 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -9,6 +9,7 @@ use X11::XCB qw(:all); use AnyEvent::I3; use List::Util qw(first); use List::MoreUtils qw(lastval); +use Time::HiRes qw(sleep); use v5.10; use Exporter (); @@ -41,12 +42,14 @@ use warnings; } sub open_standard_window { - my ($x) = @_; + my ($x, $color) = @_; + + $color ||= '#c0c0c0'; my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], - background_color => '#C0C0C0', + background_color => $color, ); $window->name('Window ' . counter_window()); From a9c549b43f16139d63bd6ac330c7462d0036c4e3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 7 Mar 2011 20:24:23 +0100 Subject: [PATCH 505/867] properly clean all files in 'make clean' --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5a0dd76f..4b2f240e 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,7 @@ dist: distclean rm -rf i3-${VERSION} clean: - rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.yy.c loglevels.tmp include/loglevels.h + rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.yy.c src/cfgparse.output src/cmdparse.tab.{c,h} src/cmdparse.yy.c src/cmdparse.output loglevels.tmp include/loglevels.h $(MAKE) -C docs clean $(MAKE) -C man clean $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg clean From 74b90cd83ffb2cdd532d2c527cd40d20963bb32e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 9 Mar 2011 18:08:01 +0100 Subject: [PATCH 506/867] Bugfix: Send WM_DELETE / kill window the right way (Thanks dothebart) Fixes #336 --- src/tree.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tree.c b/src/tree.c index ef254a8f..2d09dd91 100644 --- a/src/tree.c +++ b/src/tree.c @@ -123,9 +123,10 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { } if (con->window != NULL) { - if (kill_window) + if (kill_window) { x_window_kill(con->window->id); - else { + return; + } else { /* un-parent the window */ xcb_reparent_window(conn, con->window->id, root, 0, 0); /* TODO: client_unmap to set state to withdrawn */ From 86637d2e07909cb4e8b47adad701395a8d31c2dc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 9 Mar 2011 18:36:45 +0100 Subject: [PATCH 507/867] Bugfix: Make level up a noop during fullscreen mode (+testcase) (Thanks dothebart) Fixes #341 --- src/tree.c | 8 ++- testcases/t/57-regress-fullscreen-level-up.t | 51 ++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 testcases/t/57-regress-fullscreen-level-up.t diff --git a/src/tree.c b/src/tree.c index 2d09dd91..a66e0f49 100644 --- a/src/tree.c +++ b/src/tree.c @@ -264,11 +264,17 @@ void tree_split(Con *con, orientation_t orientation) { * */ void level_up() { + /* We cannot go up when we are in fullscreen mode at the moment, that would + * be totally not intuitive */ + if (focused->fullscreen_mode != CF_NONE) { + LOG("Currently in fullscreen, not going up\n"); + return; + } /* We can focus up to the workspace, but not any higher in the tree */ if ((focused->parent->type != CT_CON && focused->parent->type != CT_WORKSPACE) || focused->type == CT_WORKSPACE) { - printf("cannot go up\n"); + LOG("Cannot go up any further\n"); return; } con_focus(focused->parent); diff --git a/testcases/t/57-regress-fullscreen-level-up.t b/testcases/t/57-regress-fullscreen-level-up.t new file mode 100644 index 00000000..124ebd44 --- /dev/null +++ b/testcases/t/57-regress-fullscreen-level-up.t @@ -0,0 +1,51 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Regression test: level up should be a noop during fullscreen mode +# +use X11::XCB qw(:all); +use Time::HiRes qw(sleep); +use i3test; + +BEGIN { + use_ok('X11::XCB::Window'); +} + +my $x = X11::XCB::Connection->new; +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = get_unused_workspace; +cmd "workspace $tmp"; + +##################################################################### +# open a window, verify it’s not in fullscreen mode +##################################################################### + +my $win = open_standard_window($x); + +my $nodes = get_ws_content $tmp; +is(@$nodes, 1, 'exactly one client'); +is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen'); + +##################################################################### +# make it fullscreen +##################################################################### + +cmd 'nop making fullscreen'; +cmd 'fullscreen'; + +my $nodes = get_ws_content $tmp; +is($nodes->[0]->{fullscreen_mode}, 1, 'client fullscreen now'); + +##################################################################### +# send level up, try to un-fullscreen +##################################################################### +cmd 'level up'; +cmd 'fullscreen'; + +my $nodes = get_ws_content $tmp; +is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen any longer'); + +does_i3_live; + +done_testing; From 57e7cc8f6aaa402460329c18eaee70f8ccea5145 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 9 Mar 2011 18:47:00 +0100 Subject: [PATCH 508/867] tests: fix t/19-match.t, needs a delay for i3 to pick up the UnmapNotify event --- testcases/t/19-match.t | 5 +++++ testcases/t/lib/i3test.pm | 1 + 2 files changed, 6 insertions(+) diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t index 727109a0..bfeda815 100644 --- a/testcases/t/19-match.t +++ b/testcases/t/19-match.t @@ -46,9 +46,14 @@ $content = get_ws_content($tmp); ok(@{$content} == 1, 'window still there'); # now kill the window +cmd 'nop now killing the window'; my $id = $win->{id}; $i3->command(qq|[con_id="$id"] kill|)->recv; +# give i3 some time to pick up the UnmapNotify event +sleep 0.25; + +cmd 'nop checking if its gone'; $content = get_ws_content($tmp); ok(@{$content} == 0, 'window killed'); diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 87a34981..2251e25f 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -32,6 +32,7 @@ use Test::More" . (@_ > 0 ? " qw(@_)" : "") . "; use Test::Exception; use Data::Dumper; use AnyEvent::I3; +use Time::HiRes qw(sleep); use Test::Deep qw(eq_deeply cmp_deeply cmp_set cmp_bag cmp_methods useclass noclass set bag subbagof superbagof subsetof supersetof superhashof subhashof bool str arraylength Isa ignore methods regexprefonly regexpmatches num regexponly scalref reftype hashkeysonly blessed array re hash regexpref hash_each shallow array_each code arrayelementsonly arraylengthonly scalarrefonly listmethods any hashkeys isa); use v5.10; use strict; From 2524b5262d1bfc5dc4d1015f6882c8e1cad3eaa7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 9 Mar 2011 20:25:17 +0100 Subject: [PATCH 509/867] tests: clean up/beautify all tests (code style / test style) --- testcases/t/01-tile.t | 14 ++--- testcases/t/02-fullscreen.t | 27 ++++----- testcases/t/03-unmanaged.t | 4 +- testcases/t/04-floating.t | 9 ++- testcases/t/05-ipc.t | 18 +++--- testcases/t/06-focus.t | 20 +++---- testcases/t/08-focus-stack.t | 15 ++--- testcases/t/10-dock.t | 1 - testcases/t/11-goto.t | 15 +++-- testcases/t/12-floating-resize.t | 13 ++-- testcases/t/13-urgent.t | 24 ++++---- testcases/t/14-client-leader.t | 17 +++--- testcases/t/15-ipc-workspaces.t | 4 +- testcases/t/16-nestedcons.t | 4 +- testcases/t/17-workspace.t | 19 +++--- testcases/t/18-openkill.t | 19 +++--- testcases/t/19-match.t | 15 ++--- testcases/t/20-multiple-cmds.t | 16 ++--- testcases/t/21-next-prev.t | 38 ++++++------ testcases/t/22-split.t | 30 ++++------ testcases/t/24-move.t | 63 ++++++++++---------- testcases/t/26-regress-close.t | 8 +-- testcases/t/27-regress-floating-parent.t | 32 +++++----- testcases/t/28-open-order.t | 13 ++-- testcases/t/29-focus-after-close.t | 27 ++++----- testcases/t/30-close-empty-split.t | 35 +++++------ testcases/t/31-stacking-order.t | 26 ++++---- testcases/t/32-move-workspace.t | 5 +- testcases/t/33-size-hints.t | 8 +-- testcases/t/34-invalid-command.t | 4 +- testcases/t/35-floating-focus.t | 39 ++++++------ testcases/t/36-floating-ws-empty.t | 11 ++-- testcases/t/37-floating-unmap.t | 13 ++-- testcases/t/38-floating-attach.t | 7 ++- testcases/t/39-ws-numbers.t | 17 +++--- testcases/t/40-focus-lost.t | 11 ++-- testcases/t/41-resize.t | 8 +-- testcases/t/42-regress-move-floating.t | 8 +-- testcases/t/43-regress-floating-restart.t | 9 ++- testcases/t/44-regress-floating-resize.t | 9 ++- testcases/t/45-flattening.t | 7 ++- testcases/t/46-floating-reinsert.t | 7 ++- testcases/t/47-regress-floatingmove.t | 7 ++- testcases/t/48-regress-floatingmovews.t | 11 ++-- testcases/t/50-regress-dock-restart.t | 4 +- testcases/t/51-regress-float-size.t | 5 +- testcases/t/52-regress-level-up.t | 5 +- testcases/t/53-floating-originalsize.t | 4 +- testcases/t/54-regress-multiple-dock.t | 4 +- testcases/t/55-floating-split-size.t | 4 +- testcases/t/56-fullscreen-focus.t | 4 +- testcases/t/57-regress-fullscreen-level-up.t | 4 +- testcases/t/lib/i3test.pm | 8 ++- 53 files changed, 334 insertions(+), 415 deletions(-) diff --git a/testcases/t/01-tile.t b/testcases/t/01-tile.t index b40eae24..0db3b83b 100644 --- a/testcases/t/01-tile.t +++ b/testcases/t/01-tile.t @@ -1,11 +1,11 @@ #!perl +# vim:ts=4:sw=4:expandtab -use i3test tests => 4; +use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); BEGIN { - use_ok('X11::XCB::Window'); + use_ok('X11::XCB::Window'); } my $x = X11::XCB::Connection->new; @@ -13,9 +13,9 @@ my $x = X11::XCB::Connection->new; my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30); my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => $original_rect, - background_color => '#C0C0C0', + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => $original_rect, + background_color => '#C0C0C0', ); isa_ok($window, 'X11::XCB::Window'); @@ -29,4 +29,4 @@ sleep(0.25); my $new_rect = $window->rect; ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned"); -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/02-fullscreen.t b/testcases/t/02-fullscreen.t index 27ae8411..258eec99 100644 --- a/testcases/t/02-fullscreen.t +++ b/testcases/t/02-fullscreen.t @@ -1,15 +1,13 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 24; +use i3test; use X11::XCB qw(:all); use List::Util qw(first); -use Time::HiRes qw(sleep); my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; sub fullscreen_windows { scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($tmp)} @@ -52,20 +50,20 @@ is_deeply($window->rect, $original_rect, "rect unmodified before mapping"); $window->map; -sleep(0.25); +sleep 0.25; # open another container to make the window get only half of the screen -$i3->command('open')->recv; +cmd 'open'; my $new_rect = $window->rect; ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned"); $original_rect = $new_rect; -sleep(0.25); +sleep 0.25; $window->fullscreen(1); -sleep(0.25); +sleep 0.25; $new_rect = $window->rect; ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen"); @@ -89,7 +87,7 @@ $window->unmap; # open another container because the empty one will swallow the window we # map in a second -$i3->command('open')->recv; +cmd 'open'; $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30); $window = $x->root->create_child( @@ -129,7 +127,7 @@ my $swindow = $x->root->create_child( ); $swindow->map; -sleep(0.25); +sleep 0.25; ok(!$swindow->mapped, 'window not mapped while fullscreen window active'); @@ -158,16 +156,15 @@ sleep 0.25; is(fullscreen_windows(), 0, 'amount of fullscreen windows after disabling'); -$i3->command('fullscreen')->recv; +cmd 'fullscreen'; is(fullscreen_windows(), 1, 'amount of fullscreen windows after fullscreen command'); -$i3->command('fullscreen')->recv; +cmd 'fullscreen'; is(fullscreen_windows(), 0, 'amount of fullscreen windows after fullscreen command'); # clean up the workspace so that it will be cleaned when switching away -$i3->command('kill')->recv for (@{get_ws_content($tmp)}); +cmd 'kill' for (@{get_ws_content($tmp)}); - -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/03-unmanaged.t b/testcases/t/03-unmanaged.t index 3e8cdfc1..1ef934ee 100644 --- a/testcases/t/03-unmanaged.t +++ b/testcases/t/03-unmanaged.t @@ -1,7 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 5; +use i3test; use X11::XCB qw(:all); BEGIN { @@ -30,4 +30,4 @@ isa_ok($new_rect, 'X11::XCB::Rect'); is_deeply($new_rect, $original_rect, "window untouched"); -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/04-floating.t b/testcases/t/04-floating.t index a4ebccf6..d4aea828 100644 --- a/testcases/t/04-floating.t +++ b/testcases/t/04-floating.t @@ -1,9 +1,8 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 11; +use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); BEGIN { use_ok('X11::XCB::Window'); @@ -24,7 +23,7 @@ isa_ok($window, 'X11::XCB::Window'); $window->map; -sleep(0.25); +sleep 0.25; my ($absolute, $top) = $window->rect; @@ -47,7 +46,7 @@ isa_ok($window, 'X11::XCB::Window'); $window->map; -sleep(0.25); +sleep 0.25; ($absolute, $top) = $window->rect; @@ -61,4 +60,4 @@ cmp_ok($top->{y}, '==', 19, 'i3 mapped it to y=18'); $window->unmap; -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/05-ipc.t b/testcases/t/05-ipc.t index 62a3a9d4..a910c930 100644 --- a/testcases/t/05-ipc.t +++ b/testcases/t/05-ipc.t @@ -1,9 +1,8 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 2; +use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); @@ -11,28 +10,25 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +fresh_workspace; ##################################################################### # Ensure IPC works by switching workspaces ##################################################################### # Create a window so we can get a focus different from NULL -my $window = i3test::open_standard_window($x); +my $window = open_standard_window($x); diag("window->id = " . $window->id); -sleep(0.25); +sleep 0.25; my $focus = $x->input_focus; diag("old focus = $focus"); -# Switch to the nineth workspace -my $otmp = get_unused_workspace(); -$i3->command("workspace $otmp")->recv; +# Switch to another workspace +fresh_workspace; my $new_focus = $x->input_focus; isnt($focus, $new_focus, "Focus changed"); -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/06-focus.t b/testcases/t/06-focus.t index 5c3de5d8..798a5e69 100644 --- a/testcases/t/06-focus.t +++ b/testcases/t/06-focus.t @@ -1,9 +1,8 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 6; +use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); @@ -12,21 +11,20 @@ BEGIN { my $x = X11::XCB::Connection->new; my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; ##################################################################### # Create two windows and make sure focus switching works ##################################################################### # Change mode of the container to "default" for following tests -$i3->command('layout default')->recv; -$i3->command('split v')->recv; +cmd 'layout default'; +cmd 'split v'; -my $top = i3test::open_standard_window($x); -my $mid = i3test::open_standard_window($x); -my $bottom = i3test::open_standard_window($x); -sleep(0.25); +my $top = open_standard_window($x); +my $mid = open_standard_window($x); +my $bottom = open_standard_window($x); +sleep 0.25; diag("top id = " . $top->id); diag("mid id = " . $mid->id); @@ -106,4 +104,4 @@ is($focus, $top->id, "Top window focused (wrapping to the bottom works)"); #is($focus, $right->id, "right window focused"); -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/08-focus-stack.t b/testcases/t/08-focus-stack.t index ce04feca..8a408267 100644 --- a/testcases/t/08-focus-stack.t +++ b/testcases/t/08-focus-stack.t @@ -3,9 +3,8 @@ # Checks if the focus is correctly restored, when creating a floating client # over an unfocused tiling client and destroying the floating one again. -use i3test tests => 4; +use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); BEGIN { use_ok('X11::XCB::Window') or BAIL_OUT('Could not load X11::XCB::Window'); @@ -14,13 +13,11 @@ BEGIN { my $x = X11::XCB::Connection->new; my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +fresh_workspace; - -$i3->command('split h')->recv; -my $tiled_left = i3test::open_standard_window($x); -my $tiled_right = i3test::open_standard_window($x); +cmd 'split h'; +my $tiled_left = open_standard_window($x); +my $tiled_right = open_standard_window($x); sleep 0.25; @@ -49,4 +46,4 @@ sleep 0.25; is($x->input_focus, $focus, 'Focus correctly restored'); -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/10-dock.t b/testcases/t/10-dock.t index f38defe0..5fa35e1a 100644 --- a/testcases/t/10-dock.t +++ b/testcases/t/10-dock.t @@ -3,7 +3,6 @@ use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); use List::Util qw(first); BEGIN { diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t index 31c5187f..929af5c2 100644 --- a/testcases/t/11-goto.t +++ b/testcases/t/11-goto.t @@ -3,7 +3,6 @@ use i3test tests => 6; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); use Digest::SHA1 qw(sha1_base64); BEGIN { @@ -13,20 +12,19 @@ BEGIN { my $x = X11::XCB::Connection->new; my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +fresh_workspace; -$i3->command('split h')->recv; +cmd 'split h'; ##################################################################### # Create two windows and make sure focus switching works ##################################################################### -my $top = i3test::open_standard_window($x); +my $top = open_standard_window($x); sleep 0.25; -my $mid = i3test::open_standard_window($x); +my $mid = open_standard_window($x); sleep 0.25; -my $bottom = i3test::open_standard_window($x); +my $bottom = open_standard_window($x); sleep 0.25; diag("top id = " . $top->id); @@ -40,7 +38,7 @@ diag("bottom id = " . $bottom->id); sub focus_after { my $msg = shift; - $i3->command($msg)->recv; + cmd $msg; return $x->input_focus; } @@ -67,3 +65,4 @@ is($focus, $top->id, "Top window focused"); $focus = focus_after(qq|[con_mark="$random_mark"] focus|); is($focus, $mid->id, "goto worked"); +done_testing; diff --git a/testcases/t/12-floating-resize.t b/testcases/t/12-floating-resize.t index 42ca43c0..09297df0 100644 --- a/testcases/t/12-floating-resize.t +++ b/testcases/t/12-floating-resize.t @@ -4,9 +4,8 @@ # the workspace to be empty). # TODO: skip it by default? -use i3test tests => 15; +use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); @@ -14,9 +13,7 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +fresh_workspace; ##################################################################### # Create a floating window and see if resizing works @@ -67,11 +64,13 @@ sub test_resize { test_resize; # Test borderless -$i3->command('border none')->recv; +cmd 'border none'; test_resize; # Test with 1-px-border -$i3->command('border 1pixel')->recv; +cmd 'border 1pixel'; test_resize; + +done_testing; diff --git a/testcases/t/13-urgent.t b/testcases/t/13-urgent.t index 710d1892..20539dbc 100644 --- a/testcases/t/13-urgent.t +++ b/testcases/t/13-urgent.t @@ -1,9 +1,8 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 10; +use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); use List::Util qw(first); BEGIN { @@ -12,23 +11,21 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; ##################################################################### # Create two windows and put them in stacking mode ##################################################################### -$i3->command('split v')->recv; +cmd 'split v'; -my $top = i3test::open_standard_window($x); -my $bottom = i3test::open_standard_window($x); +my $top = open_standard_window($x); +my $bottom = open_standard_window($x); my @urgent = grep { $_->{urgent} == 1 } @{get_ws_content($tmp)}; is(@urgent, 0, 'no window got the urgent flag'); -#$i3->command('layout stacking')->recv; +# cmd 'layout stacking'; ##################################################################### # Add the urgency hint, switch to a different workspace and back again @@ -45,7 +42,7 @@ is($top_info->{urgent}, 1, 'top window is marked urgent'); is($bottom_info->{urgent}, 0, 'bottom window is not marked urgent'); is(@urgent, 1, 'exactly one window got the urgent flag'); -$i3->command('[id="' . $top->id . '"] focus')->recv; +cmd '[id="' . $top->id . '"] focus'; @urgent = grep { $_->{urgent} == 1 } @{get_ws_content($tmp)}; is(@urgent, 0, 'no window got the urgent flag after focusing'); @@ -62,8 +59,7 @@ is(@urgent, 0, 'no window got the urgent flag after re-setting urgency hint'); my $ws = get_ws($tmp); is($ws->{urgent}, 0, 'urgent flag not set on workspace'); -my $otmp = get_unused_workspace(); -$i3->command("workspace $otmp")->recv; +my $otmp = fresh_workspace; $top->add_hint('urgency'); sleep 0.5; @@ -71,9 +67,9 @@ sleep 0.5; $ws = get_ws($tmp); is($ws->{urgent}, 1, 'urgent flag set on workspace'); -$i3->command("workspace $tmp")->recv; +cmd "workspace $tmp"; $ws = get_ws($tmp); is($ws->{urgent}, 0, 'urgent flag not set on workspace after switching'); -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/14-client-leader.t b/testcases/t/14-client-leader.t index ef488f5a..d5675e46 100644 --- a/testcases/t/14-client-leader.t +++ b/testcases/t/14-client-leader.t @@ -1,9 +1,8 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 7; +use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); BEGIN { use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); @@ -12,8 +11,7 @@ BEGIN { my $x = X11::XCB::Connection->new; my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; #################################################################################### # first part: test if a floating window will be correctly positioned above its leader @@ -114,11 +112,10 @@ $window->map; sleep 0.25; ######################################################################### -# Switch workspace to 10 and open a child window. It should be positioned -# on workspace 9. +# Switch to a different workspace and open a child window. It should be opened +# on the old workspace. ######################################################################### -my $otmp = get_unused_workspace(); -$i3->command("workspace $otmp")->recv; +fresh_workspace; my $child = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, @@ -135,8 +132,10 @@ sleep 0.25; isnt($x->input_focus, $child->id, "Child window focused"); # Switch back -$i3->command("workspace $tmp")->recv; +cmd "workspace $tmp"; is($x->input_focus, $child->id, "Child window focused"); } + +done_testing; diff --git a/testcases/t/15-ipc-workspaces.t b/testcases/t/15-ipc-workspaces.t index 6c0996c5..0b4f819a 100644 --- a/testcases/t/15-ipc-workspaces.t +++ b/testcases/t/15-ipc-workspaces.t @@ -1,7 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 2; +use i3test; use List::MoreUtils qw(all); my $i3 = i3("/tmp/nestedcons"); @@ -22,4 +22,4 @@ ok($name_exists, "All workspaces have a name"); } -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t index 23f894ba..e8a124f9 100644 --- a/testcases/t/16-nestedcons.t +++ b/testcases/t/16-nestedcons.t @@ -1,7 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 7; +use i3test; use List::MoreUtils qw(all none); use List::Util qw(first); @@ -65,4 +65,4 @@ $i3->command('open')->recv; #diag(Dumper($tree)); -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/17-workspace.t b/testcases/t/17-workspace.t index 33046bec..e7892b2b 100644 --- a/testcases/t/17-workspace.t +++ b/testcases/t/17-workspace.t @@ -4,19 +4,14 @@ # Tests whether we can switch to a non-existant workspace # (necessary for further tests) # -use i3test tests => 3; - -my $i3 = i3("/tmp/nestedcons"); +use i3test; sub workspace_exists { my ($name) = @_; ($name ~~ @{get_workspace_names()}) } -my $tmp = get_unused_workspace(); -diag("Temporary workspace name: $tmp\n"); - -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; ok(workspace_exists($tmp), 'workspace created'); # if the workspace could not be created, we cannot run any other test # (every test starts by creating its workspace) @@ -24,17 +19,17 @@ if (!workspace_exists($tmp)) { BAIL_OUT('Cannot create workspace, further tests make no sense'); } -my $otmp = get_unused_workspace(); +my $otmp = fresh_workspace; diag("Other temporary workspace name: $otmp\n"); # As the old workspace was empty, it should get # cleaned up as we switch away from it -$i3->command("workspace $otmp")->recv; +cmd "workspace $otmp"; ok(!workspace_exists($tmp), 'old workspace cleaned up'); # Switch to the same workspace again to make sure it doesn’t get cleaned up -$i3->command("workspace $otmp")->recv; -$i3->command("workspace $otmp")->recv; +cmd "workspace $otmp"; +cmd "workspace $otmp"; ok(workspace_exists($otmp), 'other workspace still exists'); -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/18-openkill.t b/testcases/t/18-openkill.t index b78877f7..e2a729c5 100644 --- a/testcases/t/18-openkill.t +++ b/testcases/t/18-openkill.t @@ -4,21 +4,18 @@ # Tests whether opening an empty container and killing it again works # use List::Util qw(first); -use i3test tests => 6; +use i3test; -my $i3 = i3("/tmp/nestedcons"); - -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); # Open a new container -$i3->command("open")->recv; +cmd 'open'; ok(@{get_ws_content($tmp)} == 1, 'container opened'); -$i3->command("kill")->recv; +cmd 'kill'; ok(@{get_ws_content($tmp)} == 0, 'container killed'); ############################################################## @@ -26,18 +23,18 @@ ok(@{get_ws_content($tmp)} == 0, 'container killed'); # by its ID to test if the parser correctly matches the window ############################################################## -$i3->command('open')->recv; -$i3->command('open')->recv; +cmd 'open'; +cmd 'open'; ok(@{get_ws_content($tmp)} == 2, 'two containers opened'); my $content = get_ws_content($tmp); my $not_focused = first { !$_->{focused} } @{$content}; my $id = $not_focused->{id}; -$i3->command("[con_id=\"$id\"] kill")->recv; +cmd "[con_id=\"$id\"] kill"; $content = get_ws_content($tmp); ok(@{$content} == 1, 'one container killed'); ok($content->[0]->{id} != $id, 'correct window killed'); -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t index bfeda815..7efdd95c 100644 --- a/testcases/t/19-match.t +++ b/testcases/t/19-match.t @@ -3,13 +3,10 @@ # # Tests all kinds of matching methods # -use i3test tests => 4; +use i3test; use X11::XCB qw(:all); -my $i3 = i3("/tmp/nestedcons"); - -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); @@ -39,8 +36,8 @@ my $win = $content->[0]; ###################################################################### # TODO: use PCRE expressions # TODO: specify more match types -$i3->command(q|[class="*"] kill|)->recv; -$i3->command(q|[con_id="99999"] kill|)->recv; +cmd q|[class="*"] kill|; +cmd q|[con_id="99999"] kill|; $content = get_ws_content($tmp); ok(@{$content} == 1, 'window still there'); @@ -48,7 +45,7 @@ ok(@{$content} == 1, 'window still there'); # now kill the window cmd 'nop now killing the window'; my $id = $win->{id}; -$i3->command(qq|[con_id="$id"] kill|)->recv; +cmd qq|[con_id="$id"] kill|; # give i3 some time to pick up the UnmapNotify event sleep 0.25; @@ -59,4 +56,4 @@ ok(@{$content} == 0, 'window killed'); # TODO: same test, but with pcre expressions -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/20-multiple-cmds.t b/testcases/t/20-multiple-cmds.t index f1714d80..379d08ee 100644 --- a/testcases/t/20-multiple-cmds.t +++ b/testcases/t/20-multiple-cmds.t @@ -3,22 +3,18 @@ # # Tests multiple commands (using ';') and multiple operations (using ',') # -use i3test tests => 24; -use X11::XCB qw(:all); +use i3test; -my $i3 = i3("/tmp/nestedcons"); - -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; sub multiple_cmds { my ($cmd) = @_; - $i3->command('open')->recv; - $i3->command('open')->recv; + cmd 'open'; + cmd 'open'; ok(@{get_ws_content($tmp)} == 2, 'two containers opened'); - $i3->command($cmd)->recv; + cmd $cmd; ok(@{get_ws_content($tmp)} == 0, "both containers killed (cmd = $cmd)"); } multiple_cmds('kill;kill'); @@ -36,4 +32,4 @@ multiple_cmds("kill \t ; \t kill"); # TODO: need a non-invasive command before implementing a test which uses ',' -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/21-next-prev.t b/testcases/t/21-next-prev.t index 688a4984..042573a4 100644 --- a/testcases/t/21-next-prev.t +++ b/testcases/t/21-next-prev.t @@ -3,27 +3,23 @@ # # Tests focus switching (next/prev) # -use i3test tests => 14; -use X11::XCB qw(:all); +use i3test; -my $i3 = i3("/tmp/nestedcons"); - -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; ###################################################################### # Open one container, verify that 'next v' and 'next h' do nothing ###################################################################### -$i3->command('open')->recv; +cmd 'open'; my ($nodes, $focus) = get_ws_content($tmp); my $old_focused = $focus->[0]; -$i3->command('next v')->recv; +cmd 'next v'; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $old_focused, 'focus did not change with only one con'); -$i3->command('next h')->recv; +cmd 'next h'; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $old_focused, 'focus did not change with only one con'); @@ -32,38 +28,38 @@ is($focus->[0], $old_focused, 'focus did not change with only one con'); ###################################################################### my $left = $old_focused; -$i3->command('open')->recv; +cmd 'open'; ($nodes, $focus) = get_ws_content($tmp); isnt($old_focused, $focus->[0], 'new container is focused'); my $mid = $focus->[0]; -$i3->command('open')->recv; +cmd 'open'; ($nodes, $focus) = get_ws_content($tmp); isnt($old_focused, $focus->[0], 'new container is focused'); my $right = $focus->[0]; -$i3->command('next h')->recv; +cmd 'next h'; ($nodes, $focus) = get_ws_content($tmp); isnt($focus->[0], $right, 'focus did change'); is($focus->[0], $left, 'left container focused (wrapping)'); -$i3->command('next h')->recv; +cmd 'next h'; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $mid, 'middle container focused'); -$i3->command('next h')->recv; +cmd 'next h'; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $right, 'right container focused'); -$i3->command('prev h')->recv; +cmd 'prev h'; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $mid, 'middle container focused'); -$i3->command('prev h')->recv; +cmd 'prev h'; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $left, 'left container focused'); -$i3->command('prev h')->recv; +cmd 'prev h'; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $right, 'right container focused'); @@ -72,11 +68,11 @@ is($focus->[0], $right, 'right container focused'); # Test synonyms (horizontal/vertical instead of h/v) ###################################################################### -$i3->command('prev horizontal')->recv; +cmd 'prev horizontal'; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $mid, 'middle container focused'); -$i3->command('next horizontal')->recv; +cmd 'next horizontal'; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $right, 'right container focused'); @@ -84,9 +80,9 @@ is($focus->[0], $right, 'right container focused'); # Test focus command ###################################################################### -$i3->command(qq|[con_id="$mid"] focus|)->recv; +cmd qq|[con_id="$mid"] focus|; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $mid, 'middle container focused'); -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/22-split.t b/testcases/t/22-split.t index dde9cba1..3484c7fc 100644 --- a/testcases/t/22-split.t +++ b/testcases/t/22-split.t @@ -3,17 +3,14 @@ # # Tests splitting # -use i3test tests => 14; +use i3test; use X11::XCB qw(:all); -my $i3 = i3("/tmp/nestedcons"); - -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; my $ws = get_ws($tmp); is($ws->{orientation}, 'horizontal', 'orientation horizontal by default'); -$i3->command('split v')->recv; +cmd 'split v'; $ws = get_ws($tmp); is($ws->{orientation}, 'vertical', 'split v changes workspace orientation'); @@ -21,8 +18,8 @@ is($ws->{orientation}, 'vertical', 'split v changes workspace orientation'); # Open two containers, split, open another container. Then verify # the layout is like we expect it to be ###################################################################### -$i3->command('open')->recv; -$i3->command('open')->recv; +cmd 'open'; +cmd 'open'; my $content = get_ws_content($tmp); is(@{$content}, 2, 'two containers on workspace level'); @@ -34,8 +31,8 @@ is(@{$second->{nodes}}, 0, 'second container has no children (yet)'); my $old_name = $second->{name}; -$i3->command('split h')->recv; -$i3->command('open')->recv; +cmd 'split h'; +cmd 'open'; $content = get_ws_content($tmp); @@ -57,16 +54,15 @@ is($second->{nodes}->[0]->{name}, $old_name, 'found old second container'); # Test splitting multiple times without actually creating windows ###################################################################### -$tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +$tmp = fresh_workspace; $ws = get_ws($tmp); is($ws->{orientation}, 'horizontal', 'orientation horizontal by default'); -$i3->command('split v')->recv; +cmd 'split v'; $ws = get_ws($tmp); is($ws->{orientation}, 'vertical', 'split v changes workspace orientation'); -$i3->command('open')->recv; +cmd 'open'; my @content = @{get_ws_content($tmp)}; # recursively sums up all nodes and their children @@ -82,15 +78,15 @@ sub sum_nodes { } my $old_count = sum_nodes(\@content); -$i3->command('split v')->recv; +cmd 'split v'; @content = @{get_ws_content($tmp)}; $old_count = sum_nodes(\@content); -$i3->command('split v')->recv; +cmd 'split v'; @content = @{get_ws_content($tmp)}; my $count = sum_nodes(\@content); is($count, $old_count, 'not more windows after splitting again'); -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/24-move.t b/testcases/t/24-move.t index 993f48b0..ebb7dc66 100644 --- a/testcases/t/24-move.t +++ b/testcases/t/24-move.t @@ -7,13 +7,11 @@ # 3) move a container inside another container # 4) move a container in a different direction so that we need to go up in tree # -use i3test tests => 16; -use X11::XCB qw(:all); +use i3test; my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace(); -cmd "workspace $tmp"; +my $tmp = fresh_workspace; ###################################################################### # 1) move a container which cannot be moved @@ -26,10 +24,10 @@ is(@{$old_content}, 1, 'one container on this workspace'); my $first = $old_content->[0]->{id}; -#$i3->command('move before h')->recv; -#$i3->command('move before v')->recv; -#$i3->command('move after v')->recv; -#$i3->command('move after h')->recv; +#cmd 'move before h'; +#cmd 'move before v'; +#cmd 'move after v'; +#cmd 'move after h'; my $content = get_ws_content($tmp); #is_deeply($old_content, $content, 'workspace unmodified after useless moves'); @@ -38,7 +36,7 @@ my $content = get_ws_content($tmp); # 2) move a container before another single container ###################################################################### -$i3->command('open')->recv; +cmd 'open'; $content = get_ws_content($tmp); is(@{$content}, 2, 'two containers on this workspace'); my $second = $content->[1]->{id}; @@ -46,22 +44,22 @@ my $second = $content->[1]->{id}; is($content->[0]->{id}, $first, 'first container unmodified'); # Move the second container before the first one (→ swap them) -$i3->command('move left')->recv; +cmd 'move left'; $content = get_ws_content($tmp); is($content->[0]->{id}, $second, 'first container modified'); # We should not be able to move any further -$i3->command('move left')->recv; +cmd 'move left'; $content = get_ws_content($tmp); is($content->[0]->{id}, $second, 'first container unmodified'); # Now move in the other direction -$i3->command('move right')->recv; +cmd 'move right'; $content = get_ws_content($tmp); is($content->[0]->{id}, $first, 'first container modified'); # We should not be able to move any further -$i3->command('move right')->recv; +cmd 'move right'; $content = get_ws_content($tmp); is($content->[0]->{id}, $first, 'first container unmodified'); @@ -76,15 +74,15 @@ is($content->[0]->{id}, $first, 'first container unmodified'); # | first | ------ | third | # | | | | # -------------------------- -$i3->command('split v')->recv; -$i3->command('level up')->recv; -$i3->command('open')->recv; +cmd 'split v'; +cmd 'level up'; +cmd 'open'; $content = get_ws_content($tmp); is(@{$content}, 3, 'three containers on this workspace'); my $third = $content->[2]->{id}; -$i3->command('move left')->recv; +cmd 'move left'; $content = get_ws_content($tmp); is(@{$content}, 2, 'only two containers on this workspace'); my $nodes = $content->[1]->{nodes}; @@ -95,19 +93,19 @@ is($nodes->[1]->{id}, $third, 'third container on bottom'); # move it inside the split container ###################################################################### -$i3->command('move up')->recv; +cmd 'move up'; $nodes = get_ws_content($tmp)->[1]->{nodes}; is($nodes->[0]->{id}, $third, 'third container on top'); is($nodes->[1]->{id}, $second, 'second container on bottom'); # move it outside again -$i3->command('move left')->recv; +cmd 'move left'; $content = get_ws_content($tmp); is(@{$content}, 3, 'three nodes on this workspace'); # due to automatic flattening/cleanup, the remaining split container # will be replaced by the con itself, so we will still have 3 nodes -$i3->command('move right')->recv; +cmd 'move right'; $content = get_ws_content($tmp); is(@{$content}, 2, 'two nodes on this workspace'); @@ -117,21 +115,20 @@ is(@{$content}, 2, 'two nodes on this workspace'); # container needs to be closed. Verify that it will be closed. ###################################################################### -my $otmp = get_unused_workspace(); -cmd "workspace $otmp"; +my $otmp = fresh_workspace; -$i3->command("open")->recv; -$i3->command("open")->recv; -$i3->command("split v")->recv; -$i3->command("open")->recv; -$i3->command("prev h")->recv; -$i3->command("split v")->recv; -$i3->command("open")->recv; -$i3->command("move right")->recv; -$i3->command("prev h")->recv; -$i3->command("move right")->recv; +cmd "open"; +cmd "open"; +cmd "split v"; +cmd "open"; +cmd "prev h"; +cmd "split v"; +cmd "open"; +cmd "move right"; +cmd "prev h"; +cmd "move right"; $content = get_ws_content($otmp); is(@{$content}, 1, 'only one nodes on this workspace'); -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/26-regress-close.t b/testcases/t/26-regress-close.t index 349ffca5..8aec87d7 100644 --- a/testcases/t/26-regress-close.t +++ b/testcases/t/26-regress-close.t @@ -4,11 +4,9 @@ # Regression: closing of floating clients did crash i3 when closing the # container which contained this client. # -use i3test tests => 1; -use X11::XCB qw(:all); +use i3test; -my $tmp = get_unused_workspace(); -cmd "workspace $tmp"; +fresh_workspace; cmd 'open'; cmd 'mode toggle'; @@ -17,4 +15,4 @@ cmd 'kill'; does_i3_live; -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/27-regress-floating-parent.t b/testcases/t/27-regress-floating-parent.t index d72fd350..52b8b9c0 100644 --- a/testcases/t/27-regress-floating-parent.t +++ b/testcases/t/27-regress-floating-parent.t @@ -3,43 +3,39 @@ # # Regression: make a container floating, kill its parent, make it tiling again # -use i3test tests => 4; -use X11::XCB qw(:all); +use i3test; -my $i3 = i3("/tmp/nestedcons"); +my $tmp = fresh_workspace; -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; - -$i3->command('open')->recv; +cmd 'open'; my $left = get_focused($tmp); -$i3->command('open')->recv; +cmd 'open'; my $old = get_focused($tmp); -$i3->command('split v')->recv; -$i3->command('open')->recv; +cmd 'split v'; +cmd 'open'; my $floating = get_focused($tmp); diag("focused floating: " . get_focused($tmp)); -$i3->command('mode toggle')->recv; +cmd 'mode toggle'; # TODO: eliminate this race conditition sleep 1; # kill old container -$i3->command(qq|[con_id="$old"] focus|)->recv; +cmd qq|[con_id="$old"] focus|; is(get_focused($tmp), $old, 'old container focused'); -$i3->command('kill')->recv; +cmd 'kill'; # kill left container -$i3->command(qq|[con_id="$left"] focus|)->recv; +cmd qq|[con_id="$left"] focus|; is(get_focused($tmp), $left, 'old container focused'); -$i3->command('kill')->recv; +cmd 'kill'; # focus floating window, make it tiling again -$i3->command(qq|[con_id="$floating"] focus|)->recv; +cmd qq|[con_id="$floating"] focus|; is(get_focused($tmp), $floating, 'floating window focused'); sleep 1; -$i3->command('mode toggle')->recv; +cmd 'mode toggle'; does_i3_live; -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/28-open-order.t b/testcases/t/28-open-order.t index 98718e13..f6b26b04 100644 --- a/testcases/t/28-open-order.t +++ b/testcases/t/28-open-order.t @@ -4,12 +4,11 @@ # Check if new containers are opened after the currently focused one instead # of always at the end use List::Util qw(first); -use i3test tests => 7; +use i3test; my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); @@ -26,8 +25,8 @@ isnt($first, $second, 'different container focused'); # see if new containers open after the currently focused ############################################################## -$i3->command(qq|[con_id="$first"] focus|)->recv; -$i3->command('open')->recv; +cmd qq|[con_id="$first"] focus|; +cmd 'open'; $content = get_ws_content($tmp); ok(@{$content} == 3, 'three containers opened'); @@ -35,6 +34,4 @@ is($content->[0]->{id}, $first, 'first container unmodified'); isnt($content->[1]->{id}, $second, 'second container replaced'); is($content->[2]->{id}, $second, 'third container unmodified'); -diag( "Testing i3, Perl $], $^X" ); -# - +done_testing; diff --git a/testcases/t/29-focus-after-close.t b/testcases/t/29-focus-after-close.t index 3370484c..d922578c 100644 --- a/testcases/t/29-focus-after-close.t +++ b/testcases/t/29-focus-after-close.t @@ -3,26 +3,24 @@ # # Check if the focus is correctly restored after closing windows. # -use i3test tests => 9; +use i3test; use List::Util qw(first); -use Time::HiRes qw(sleep); my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); my $first = open_empty_con($i3); my $second = open_empty_con($i3); -$i3->command('split v')->recv; +cmd 'split v'; my ($nodes, $focus) = get_ws_content($tmp); is($nodes->[1]->{focused}, 0, 'split container not focused'); -$i3->command('level up')->recv; +cmd 'level up'; ($nodes, $focus) = get_ws_content($tmp); is($nodes->[1]->{focused}, 1, 'split container focused after level up'); @@ -43,7 +41,7 @@ isnt(get_focused($tmp), $second, 'different container focused'); # when closing $second ############################################################## -$i3->command('kill')->recv; +cmd 'kill'; # TODO: this testcase sometimes has different outcomes when the # sleep is missing. why? sleep 0.25; @@ -55,20 +53,19 @@ is($nodes->[1]->{nodes}->[0]->{focused}, 1, 'second container focused'); # another case, using a slightly different layout (regression) ############################################################## -$tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +$tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -$i3->command('split v')->recv; +cmd 'split v'; $first = open_empty_con($i3); my $bottom = open_empty_con($i3); -$i3->command('prev v')->recv; -$i3->command('split h')->recv; +cmd 'prev v'; +cmd 'split h'; my $middle = open_empty_con($i3); my $right = open_empty_con($i3); -$i3->command('next v')->recv; +cmd 'next v'; # We have the following layout now (second is focused): # .----------------------------. @@ -82,7 +79,7 @@ $i3->command('next v')->recv; # `----------------------------' # Check if the focus is restored to $right when we close $second -$i3->command('kill')->recv; +cmd 'kill'; is(get_focused($tmp), $right, 'top right container focused (in focus stack)'); @@ -98,4 +95,4 @@ is($tr->{focused}, 1, 'top right container really has focus'); # TODO: add test code as soon as I can reproduce it -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/30-close-empty-split.t b/testcases/t/30-close-empty-split.t index 44aa3372..9ed0ae95 100644 --- a/testcases/t/30-close-empty-split.t +++ b/testcases/t/30-close-empty-split.t @@ -3,31 +3,29 @@ # # Check if empty split containers are automatically closed. # -use i3test tests => 8; -use Time::HiRes qw(sleep); +use i3test; my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); my $first = open_empty_con($i3); my $second = open_empty_con($i3); -$i3->command(qq|[con_id="$first"] focus|)->recv; +cmd qq|[con_id="$first"] focus|; -$i3->command('split v')->recv; +cmd 'split v'; ($nodes, $focus) = get_ws_content($tmp); is($nodes->[0]->{focused}, 0, 'split container not focused'); # focus the split container -$i3->command('level up')->recv; +cmd 'level up'; ($nodes, $focus) = get_ws_content($tmp); my $split = $focus->[0]; -$i3->command('level down')->recv; +cmd 'level down'; my $second = open_empty_con($i3); @@ -37,8 +35,8 @@ isnt($first, $second, 'different container focused'); # close both windows and see if the split container still exists ############################################################## -$i3->command('kill')->recv; -$i3->command('kill')->recv; +cmd 'kill'; +cmd 'kill'; ($nodes, $focus) = get_ws_content($tmp); isnt($nodes->[0]->{id}, $split, 'split container closed'); @@ -47,26 +45,25 @@ isnt($nodes->[0]->{id}, $split, 'split container closed'); # of killing them ############################################################## -$tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +$tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); $first = open_empty_con($i3); $second = open_empty_con($i3); -$i3->command(qq|[con_id="$first"] focus|)->recv; +cmd qq|[con_id="$first"] focus|; -$i3->command('split v')->recv; +cmd 'split v'; ($nodes, $focus) = get_ws_content($tmp); is($nodes->[0]->{focused}, 0, 'split container not focused'); # focus the split container -$i3->command('level up')->recv; +cmd 'level up'; ($nodes, $focus) = get_ws_content($tmp); my $split = $focus->[0]; -$i3->command('level down')->recv; +cmd 'level down'; my $second = open_empty_con($i3); @@ -77,9 +74,9 @@ isnt($first, $second, 'different container focused'); ############################################################## my $otmp = get_unused_workspace(); -$i3->command("move workspace $otmp")->recv; -$i3->command("move workspace $otmp")->recv; +cmd "move workspace $otmp"; +cmd "move workspace $otmp"; ($nodes, $focus) = get_ws_content($tmp); isnt($nodes->[0]->{id}, $split, 'split container closed'); -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/31-stacking-order.t b/testcases/t/31-stacking-order.t index 3335cba7..78a35cce 100644 --- a/testcases/t/31-stacking-order.t +++ b/testcases/t/31-stacking-order.t @@ -5,18 +5,16 @@ # the split mode (horizontal/vertical) of the underlying # container. # -use i3test tests => 7; -use Time::HiRes qw(sleep); +use i3test; my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); # Enforce vertical split mode -$i3->command('split v')->recv; +cmd 'split v'; my $first = open_empty_con($i3); my $second = open_empty_con($i3); @@ -27,28 +25,28 @@ isnt($first, $second, 'two different containers opened'); # change mode to stacking and cycle through the containers ############################################################## -$i3->command('layout stacking')->recv; +cmd 'layout stacking'; is(get_focused($tmp), $second, 'second container still focused'); -$i3->command('next v')->recv; +cmd 'next v'; is(get_focused($tmp), $first, 'first container focused'); -$i3->command('prev v')->recv; +cmd 'prev v'; is(get_focused($tmp), $second, 'second container focused again'); ############################################################## # now change the orientation to horizontal and cycle ############################################################## -$i3->command('level up')->recv; -$i3->command('split h')->recv; -$i3->command('level down')->recv; +cmd 'level up'; +cmd 'split h'; +cmd 'level down'; -$i3->command('next v')->recv; +cmd 'next v'; is(get_focused($tmp), $first, 'first container focused'); -$i3->command('prev v')->recv; +cmd 'prev v'; is(get_focused($tmp), $second, 'second container focused again'); -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/32-move-workspace.t b/testcases/t/32-move-workspace.t index 871be6da..affd18f2 100644 --- a/testcases/t/32-move-workspace.t +++ b/testcases/t/32-move-workspace.t @@ -3,8 +3,7 @@ # # Checks if the 'move workspace' command works correctly # -use i3test tests => 11; -use Time::HiRes qw(sleep); +use i3test; my $i3 = i3("/tmp/nestedcons"); @@ -60,4 +59,4 @@ $ws = get_ws($tmp2); is(@{$ws->{nodes}}, 0, 'no nodes on workspace'); is(@{$ws->{floating_nodes}}, 1, 'one floating node on workspace'); -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/33-size-hints.t b/testcases/t/33-size-hints.t index c4bd72af..a1464884 100644 --- a/testcases/t/33-size-hints.t +++ b/testcases/t/33-size-hints.t @@ -3,15 +3,13 @@ # # Checks if size hints are interpreted correctly. # -use i3test tests => 2; -use Time::HiRes qw(sleep); +use i3test; my $i3 = i3("/tmp/nestedcons"); my $x = X11::XCB::Connection->new; -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); @@ -41,4 +39,4 @@ my $ar = $rect->width / $rect->height; diag("Aspect ratio = $ar"); ok(($ar > 1.90) && ($ar < 2.10), 'Aspect ratio about 2.0'); -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/34-invalid-command.t b/testcases/t/34-invalid-command.t index cfb330a1..d58985e3 100644 --- a/testcases/t/34-invalid-command.t +++ b/testcases/t/34-invalid-command.t @@ -3,10 +3,10 @@ # # # -use i3test tests => 1; +use i3test; cmd 'blargh!'; does_i3_live; -diag( "Testing i3, Perl $], $^X" ); +done_testing; diff --git a/testcases/t/35-floating-focus.t b/testcases/t/35-floating-focus.t index 3b6cfdae..e07b91bd 100644 --- a/testcases/t/35-floating-focus.t +++ b/testcases/t/35-floating-focus.t @@ -1,27 +1,23 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 9; -use Time::HiRes qw(sleep); +use i3test; -my $i3 = i3("/tmp/nestedcons"); - -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; ############################################################################# # 1: see if focus stays the same when toggling tiling/floating mode ############################################################################# -$i3->command("open")->recv; -$i3->command("open")->recv; +cmd "open"; +cmd "open"; my @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 2, 'two containers opened'); cmp_ok($content[1]->{focused}, '==', 1, 'Second container focused'); -$i3->command("mode floating")->recv; -$i3->command("mode tiling")->recv; +cmd "mode floating"; +cmd "mode tiling"; @content = @{get_ws_content($tmp)}; cmp_ok($content[1]->{focused}, '==', 1, 'Second container still focused after mode toggle'); @@ -31,12 +27,11 @@ cmp_ok($content[1]->{focused}, '==', 1, 'Second container still focused after mo # (first to the next floating client, then to the last focused tiling client) ############################################################################# -$tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +$tmp = fresh_workspace; -$i3->command("open")->recv; -$i3->command("open")->recv; -$i3->command("open")->recv; +cmd "open"; +cmd "open"; +cmd "open"; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 3, 'two containers opened'); @@ -47,26 +42,28 @@ my $second_id = $content[1]->{id}; my $first_id = $content[0]->{id}; diag("last_id = $last_id, second_id = $second_id, first_id = $first_id"); -$i3->command(qq|[con_id="$second_id"] focus|)->recv; +cmd qq|[con_id="$second_id"] focus|; @content = @{get_ws_content($tmp)}; cmp_ok($content[1]->{focused}, '==', 1, 'Second container focused'); -$i3->command("mode floating")->recv; +cmd "mode floating"; -$i3->command(qq|[con_id="$last_id"] focus|)->recv; +cmd qq|[con_id="$last_id"] focus|; @content = @{get_ws_content($tmp)}; cmp_ok($content[1]->{focused}, '==', 1, 'Last container focused'); -$i3->command("mode floating")->recv; +cmd "mode floating"; diag("focused = " . get_focused($tmp)); -$i3->command("kill")->recv; +cmd "kill"; diag("focused = " . get_focused($tmp)); # TODO: this test result is actually not right. the focus reverts to where the mouse pointer is. cmp_ok(get_focused($tmp), '==', $second_id, 'Focus reverted to second floating container'); -$i3->command("kill")->recv; +cmd "kill"; @content = @{get_ws_content($tmp)}; cmp_ok($content[0]->{focused}, '==', 1, 'Focus reverted to tiling container'); + +done_testing; diff --git a/testcases/t/36-floating-ws-empty.t b/testcases/t/36-floating-ws-empty.t index 1d795e31..2ea16936 100644 --- a/testcases/t/36-floating-ws-empty.t +++ b/testcases/t/36-floating-ws-empty.t @@ -2,9 +2,8 @@ # vim:ts=4:sw=4:expandtab # Regression test: when only having a floating window on a workspace, it should not be deleted. -use i3test tests => 6; +use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); BEGIN { use_ok('X11::XCB::Window'); @@ -12,8 +11,7 @@ BEGIN { my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; ############################################################################# # 1: open a floating window, get it mapped @@ -47,8 +45,9 @@ ok($window->mapped, 'Window is mapped'); # switch to a different workspace, see if the window is still mapped? -my $otmp = get_unused_workspace(); -$i3->command("workspace $otmp")->recv; +my $otmp = fresh_workspace; ok(workspace_exists($otmp), "new workspace $otmp exists"); ok(workspace_exists($tmp), "old workspace $tmp still exists"); + +done_testing; diff --git a/testcases/t/37-floating-unmap.t b/testcases/t/37-floating-unmap.t index 8fc7c4c0..bf596ada 100644 --- a/testcases/t/37-floating-unmap.t +++ b/testcases/t/37-floating-unmap.t @@ -3,9 +3,8 @@ # Regression test: Floating windows were not correctly unmapped when switching # to a different workspace. -use i3test tests => 4; +use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); BEGIN { use_ok('X11::XCB::Window'); @@ -13,8 +12,7 @@ BEGIN { my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; ############################################################################# # 1: open a floating window, get it mapped @@ -41,11 +39,12 @@ ok($window->mapped, 'Window is mapped'); # switch to a different workspace, see if the window is still mapped? -my $otmp = get_unused_workspace(); -$i3->command("workspace $otmp")->recv; +my $otmp = fresh_workspace; sleep 0.25; ok(!$window->mapped, 'Window is not mapped after switching ws'); -$i3->command("nop testcase done")->recv; +cmd "nop testcase done"; + +done_testing; diff --git a/testcases/t/38-floating-attach.t b/testcases/t/38-floating-attach.t index 0a85ff6d..a1cf4d63 100644 --- a/testcases/t/38-floating-attach.t +++ b/testcases/t/38-floating-attach.t @@ -3,7 +3,7 @@ # Regression test: New windows were attached to the container of a floating window # if only a floating window is present on the workspace. -use i3test tests => 7; +use i3test; use X11::XCB qw(:all); use Time::HiRes qw(sleep); @@ -13,8 +13,7 @@ BEGIN { my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; ############################################################################# # 1: open a floating window, get it mapped @@ -61,3 +60,5 @@ sleep 0.25; ($nodes, $focus) = get_ws_content($tmp); is(@{$nodes}, 1, 'one tiling node'); + +done_testing; diff --git a/testcases/t/39-ws-numbers.t b/testcases/t/39-ws-numbers.t index e3f35722..bb688be3 100644 --- a/testcases/t/39-ws-numbers.t +++ b/testcases/t/39-ws-numbers.t @@ -2,9 +2,8 @@ # vim:ts=4:sw=4:expandtab # Check if numbered workspaces and named workspaces are sorted in the right way # in get_workspaces IPC output (necessary for i3bar etc.). -use i3test tests => 9; +use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); BEGIN { use_ok('X11::XCB::Window'); @@ -29,7 +28,7 @@ check_order('workspace order alright before testing'); # open a window to keep this ws open ############################################################################# -$i3->command("workspace 93")->recv; +cmd "workspace 93"; open_standard_window($x); @@ -38,22 +37,24 @@ my @f = grep { defined($_->{num}) && $_->{num} == 93 } @ws; is(@f, 1, 'ws 93 found by num'); check_order('workspace order alright after opening 93'); -$i3->command("workspace 92")->recv; +cmd "workspace 92"; open_standard_window($x); check_order('workspace order alright after opening 92'); -$i3->command("workspace 94")->recv; +cmd "workspace 94"; open_standard_window($x); check_order('workspace order alright after opening 94'); -$i3->command("workspace 96")->recv; +cmd "workspace 96"; open_standard_window($x); check_order('workspace order alright after opening 96'); -$i3->command("workspace foo")->recv; +cmd "workspace foo"; open_standard_window($x); check_order('workspace order alright after opening foo'); -$i3->command("workspace 91")->recv; +cmd "workspace 91"; open_standard_window($x); check_order('workspace order alright after opening 91'); + +done_testing; diff --git a/testcases/t/40-focus-lost.t b/testcases/t/40-focus-lost.t index 9b2db079..4158d1f9 100644 --- a/testcases/t/40-focus-lost.t +++ b/testcases/t/40-focus-lost.t @@ -2,7 +2,7 @@ # vim:ts=4:sw=4:expandtab # Regression: Check if the focus stays the same when switching the layout # bug introduced by 77d0d42ed2d7ac8cafe267c92b35a81c1b9491eb -use i3test tests => 4; +use i3test; use X11::XCB qw(:all); use Time::HiRes qw(sleep); @@ -23,8 +23,7 @@ sub check_order { cmp_deeply(\@nums, \@sorted, $msg); } -my $tmp = get_unused_workspace(); -$i3->command("workspace $tmp")->recv; +my $tmp = fresh_workspace; my $left = open_standard_window($x); sleep 0.25; @@ -37,10 +36,12 @@ diag("left = " . $left->id . ", mid = " . $mid->id . ", right = " . $right->id); is($x->input_focus, $right->id, 'Right window focused'); -$i3->command('prev h')->recv; +cmd 'prev h'; is($x->input_focus, $mid->id, 'Mid window focused'); -$i3->command('layout stacked')->recv; +cmd 'layout stacked'; is($x->input_focus, $mid->id, 'Mid window focused'); + +done_testing; diff --git a/testcases/t/41-resize.t b/testcases/t/41-resize.t index 050b92a6..1717e8f2 100644 --- a/testcases/t/41-resize.t +++ b/testcases/t/41-resize.t @@ -1,9 +1,8 @@ #!perl # vim:ts=4:sw=4:expandtab # Tests resizing tiling containers -use i3test tests => 6; +use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); BEGIN { use_ok('X11::XCB::Window'); @@ -11,8 +10,7 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $tmp = get_unused_workspace(); -cmd "workspace $tmp"; +my $tmp = fresh_workspace; cmd 'split v'; @@ -47,3 +45,5 @@ cmd 'split h'; is($nodes->[0]->{percent}, 0.25, 'top window got only 25%'); is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%'); + +done_testing; diff --git a/testcases/t/42-regress-move-floating.t b/testcases/t/42-regress-move-floating.t index 1c4fae13..6b2df806 100644 --- a/testcases/t/42-regress-move-floating.t +++ b/testcases/t/42-regress-move-floating.t @@ -3,15 +3,15 @@ # # Regression: move a floating window to a different workspace crashes i3 # -use i3test tests => 1; -use X11::XCB qw(:all); +use i3test; -my $tmp = get_unused_workspace(); +my $tmp = fresh_workspace; my $otmp = get_unused_workspace(); -cmd "workspace $tmp"; cmd 'open'; cmd 'mode toggle'; cmd "move workspace $otmp"; does_i3_live; + +done_testing; diff --git a/testcases/t/43-regress-floating-restart.t b/testcases/t/43-regress-floating-restart.t index 974c1ae1..babbb574 100644 --- a/testcases/t/43-regress-floating-restart.t +++ b/testcases/t/43-regress-floating-restart.t @@ -3,12 +3,9 @@ # # Regression: floating windows are tiling after restarting, closing them crashes i3 # -use i3test tests => 1; -use Time::HiRes qw(sleep); -use X11::XCB qw(:all); +use i3test; -my $tmp = get_unused_workspace(); -cmd "workspace $tmp"; +my $tmp = fresh_workspace; cmd 'open'; cmd 'mode toggle'; @@ -22,3 +19,5 @@ does_i3_live; my $ws = get_ws($tmp); diag('ws = ' . Dumper($ws)); + +done_testing; diff --git a/testcases/t/44-regress-floating-resize.t b/testcases/t/44-regress-floating-resize.t index d7102cec..de33eeb3 100644 --- a/testcases/t/44-regress-floating-resize.t +++ b/testcases/t/44-regress-floating-resize.t @@ -6,13 +6,10 @@ # workspace as if a tiling container was closed, leading to the containers # taking much more space than they possibly could. # -use i3test tests => 1; -use X11::XCB qw(:all); -use Time::HiRes qw(sleep); +use i3test; use List::Util qw(sum); -my $tmp = get_unused_workspace(); -cmd "workspace $tmp"; +my $tmp = fresh_workspace; cmd 'exec /usr/bin/urxvt'; sleep 0.5; @@ -36,3 +33,5 @@ sleep 0.5; my $new_sum = sum map { $_->{rect}->{width} } @{$nodes}; is($old_sum, $new_sum, 'combined container width is still equal'); + +done_testing; diff --git a/testcases/t/45-flattening.t b/testcases/t/45-flattening.t index c508ea7f..98b93ef1 100644 --- a/testcases/t/45-flattening.t +++ b/testcases/t/45-flattening.t @@ -11,12 +11,11 @@ # This testcase checks that the tree is properly flattened after moving. # use X11::XCB qw(:all); -use i3test tests => 2; +use i3test; my $x = X11::XCB::Connection->new; -my $tmp = get_unused_workspace; -cmd "workspace $tmp"; +my $tmp = fresh_workspace; my $left = open_standard_window($x); sleep 0.25; @@ -31,3 +30,5 @@ my $ws = get_ws($tmp); is($ws->{orientation}, 'horizontal', 'workspace orientation is horizontal'); is(@{$ws->{nodes}}, 3, 'all three windows on workspace level'); + +done_testing; diff --git a/testcases/t/46-floating-reinsert.t b/testcases/t/46-floating-reinsert.t index e24a37e0..b75c08e1 100644 --- a/testcases/t/46-floating-reinsert.t +++ b/testcases/t/46-floating-reinsert.t @@ -3,7 +3,7 @@ # use X11::XCB qw(:all); use Time::HiRes qw(sleep); -use i3test tests => 5; +use i3test; BEGIN { use_ok('X11::XCB::Window'); @@ -11,8 +11,7 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $tmp = get_unused_workspace; -cmd "workspace $tmp"; +my $tmp = fresh_workspace; my $left = open_standard_window($x); sleep 0.25; @@ -60,3 +59,5 @@ cmd 'mode toggle'; my ($nodes, $focus) = get_ws_content($tmp); is(@{$nodes->[1]->{nodes}}, 3, 'three windows in split con after mode toggle'); + +done_testing; diff --git a/testcases/t/47-regress-floatingmove.t b/testcases/t/47-regress-floatingmove.t index 946276db..6e04916d 100644 --- a/testcases/t/47-regress-floatingmove.t +++ b/testcases/t/47-regress-floatingmove.t @@ -6,7 +6,7 @@ # use X11::XCB qw(:all); use Time::HiRes qw(sleep); -use i3test tests => 3; +use i3test; BEGIN { use_ok('X11::XCB::Window'); @@ -14,8 +14,7 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $tmp = get_unused_workspace; -cmd "workspace $tmp"; +my $tmp = fresh_workspace; my $left = open_standard_window($x); sleep 0.25; @@ -44,3 +43,5 @@ cmd 'move before v'; sleep 0.25; does_i3_live; + +done_testing; diff --git a/testcases/t/48-regress-floatingmovews.t b/testcases/t/48-regress-floatingmovews.t index deebecd9..0bec5418 100644 --- a/testcases/t/48-regress-floatingmovews.t +++ b/testcases/t/48-regress-floatingmovews.t @@ -5,8 +5,7 @@ # another workspace. # use X11::XCB qw(:all); -use Time::HiRes qw(sleep); -use i3test tests => 2; +use i3test; BEGIN { use_ok('X11::XCB::Window'); @@ -14,8 +13,7 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $tmp = get_unused_workspace; -cmd "workspace $tmp"; +my $tmp = fresh_workspace; # open a tiling window on the first workspace open_standard_window($x); @@ -23,8 +21,7 @@ sleep 0.25; my $first = get_focused($tmp); # on a different ws, open a floating window -my $otmp = get_unused_workspace; -cmd "workspace $otmp"; +my $otmp = fresh_workspace; open_standard_window($x); sleep 0.25; my $float = get_focused($otmp); @@ -37,3 +34,5 @@ sleep 0.25; # switch to the first ws and check focus is(get_focused($tmp), $float, 'floating client correctly focused'); + +done_testing; diff --git a/testcases/t/50-regress-dock-restart.t b/testcases/t/50-regress-dock-restart.t index a753abe2..6d78e939 100644 --- a/testcases/t/50-regress-dock-restart.t +++ b/testcases/t/50-regress-dock-restart.t @@ -4,7 +4,6 @@ # Regression test for inplace restarting with dock clients # use X11::XCB qw(:all); -use Time::HiRes qw(sleep); use i3test; BEGIN { @@ -14,8 +13,7 @@ BEGIN { my $x = X11::XCB::Connection->new; my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace; -cmd "workspace $tmp"; +my $tmp = fresh_workspace; ##################################################################### # verify that there is no dock window yet diff --git a/testcases/t/51-regress-float-size.t b/testcases/t/51-regress-float-size.t index b3ff9a47..881ef8c1 100644 --- a/testcases/t/51-regress-float-size.t +++ b/testcases/t/51-regress-float-size.t @@ -3,12 +3,9 @@ # # Regression test for setting a window to floating, tiling and opening a new window # -use Time::HiRes qw(sleep); use i3test; -my $tmp = get_unused_workspace; -cmd "workspace $tmp"; - +fresh_workspace; cmd 'open'; cmd 'mode toggle'; diff --git a/testcases/t/52-regress-level-up.t b/testcases/t/52-regress-level-up.t index 19b0f85d..678cdd9c 100644 --- a/testcases/t/52-regress-level-up.t +++ b/testcases/t/52-regress-level-up.t @@ -4,12 +4,9 @@ # Regression test for using level-up to get to the 'content'-container and # toggle floating # -use Time::HiRes qw(sleep); use i3test; -my $tmp = get_unused_workspace; -cmd "workspace $tmp"; - +fresh_workspace; cmd 'open'; cmd 'level up'; diff --git a/testcases/t/53-floating-originalsize.t b/testcases/t/53-floating-originalsize.t index 65462e5f..16694593 100644 --- a/testcases/t/53-floating-originalsize.t +++ b/testcases/t/53-floating-originalsize.t @@ -3,12 +3,10 @@ # # Test if the requested width/height is set after making the window floating. # -use Time::HiRes qw(sleep); use X11::XCB qw(:all); use i3test; -my $tmp = get_unused_workspace; -cmd "workspace $tmp"; +my $tmp = fresh_workspace; my $x = X11::XCB::Connection->new; diff --git a/testcases/t/54-regress-multiple-dock.t b/testcases/t/54-regress-multiple-dock.t index 9e7353d4..61e953f3 100644 --- a/testcases/t/54-regress-multiple-dock.t +++ b/testcases/t/54-regress-multiple-dock.t @@ -4,7 +4,6 @@ # Regression test for closing one of multiple dock clients # use X11::XCB qw(:all); -use Time::HiRes qw(sleep); use i3test; BEGIN { @@ -14,8 +13,7 @@ BEGIN { my $x = X11::XCB::Connection->new; my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace; -cmd "workspace $tmp"; +my $tmp = fresh_workspace; ##################################################################### # verify that there is no dock window yet diff --git a/testcases/t/55-floating-split-size.t b/testcases/t/55-floating-split-size.t index 26671eb0..61ceae3c 100644 --- a/testcases/t/55-floating-split-size.t +++ b/testcases/t/55-floating-split-size.t @@ -5,7 +5,6 @@ # when setting the split container to floating # use X11::XCB qw(:all); -use Time::HiRes qw(sleep); use i3test; BEGIN { @@ -15,8 +14,7 @@ BEGIN { my $x = X11::XCB::Connection->new; my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace; -cmd "workspace $tmp"; +my $tmp = fresh_workspace; ##################################################################### # open a window with 200x80 diff --git a/testcases/t/56-fullscreen-focus.t b/testcases/t/56-fullscreen-focus.t index 7d8b16ca..2027cbe0 100644 --- a/testcases/t/56-fullscreen-focus.t +++ b/testcases/t/56-fullscreen-focus.t @@ -5,7 +5,6 @@ # the time of launching the new one. # use X11::XCB qw(:all); -use Time::HiRes qw(sleep); use i3test; BEGIN { @@ -15,8 +14,7 @@ BEGIN { my $x = X11::XCB::Connection->new; my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace; -cmd "workspace $tmp"; +my $tmp = fresh_workspace; ##################################################################### # open the left window diff --git a/testcases/t/57-regress-fullscreen-level-up.t b/testcases/t/57-regress-fullscreen-level-up.t index 124ebd44..6b8c7be0 100644 --- a/testcases/t/57-regress-fullscreen-level-up.t +++ b/testcases/t/57-regress-fullscreen-level-up.t @@ -4,7 +4,6 @@ # Regression test: level up should be a noop during fullscreen mode # use X11::XCB qw(:all); -use Time::HiRes qw(sleep); use i3test; BEGIN { @@ -14,8 +13,7 @@ BEGIN { my $x = X11::XCB::Connection->new; my $i3 = i3("/tmp/nestedcons"); -my $tmp = get_unused_workspace; -cmd "workspace $tmp"; +my $tmp = fresh_workspace; ##################################################################### # open a window, verify it’s not in fullscreen mode diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 2251e25f..4e6989fc 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -13,7 +13,7 @@ use Time::HiRes qw(sleep); use v5.10; use Exporter (); -our @EXPORT = qw(get_workspace_names get_unused_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window get_dock_clients cmd does_i3_live); +our @EXPORT = qw(get_workspace_names get_unused_workspace fresh_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window get_dock_clients cmd does_i3_live); my $tester = Test::Builder->new(); @@ -88,6 +88,12 @@ sub get_unused_workspace { $tmp } +sub fresh_workspace { + my $unused = get_unused_workspace; + cmd("workspace $unused"); + $unused +} + sub get_ws { my ($name) = @_; my $i3 = i3("/tmp/nestedcons"); From 6e4a2b0b96f2c70c0693292959b9e071d2264bf2 Mon Sep 17 00:00:00 2001 From: Simon Kampe Date: Wed, 9 Mar 2011 21:43:50 +0100 Subject: [PATCH 510/867] Fallback fonts for when requesting a erronous font with load_font (e.g. user have specified a font which does not exist in the config file). --- src/xcb.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/xcb.c b/src/xcb.c index d2aeaadd..9ec0df9c 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -37,7 +37,29 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern) { font_cookie = xcb_open_font_checked(conn, new->id, strlen(pattern), pattern); info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern); - check_error(conn, font_cookie, "Could not open font"); + /* Check for errors. If errors, fall back to default font. */ + xcb_generic_error_t *error = xcb_request_check(conn, font_cookie); + + /* If we fail to open font, fall back to 'fixed'. If opening 'fixed' fails fall back to '-misc-*' */ + if (error != NULL) { + ELOG("Could not open font %s (X error %d). Reverting to backup font.\n", pattern, error->error_code); + pattern = "fixed"; + font_cookie = xcb_open_font_checked(conn, new->id, strlen(pattern), pattern); + info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern); + + /* Check if we managed to open 'fixed' */ + xcb_generic_error_t *error = xcb_request_check(conn, font_cookie); + + /* Fall back to '-misc-*' if opening 'fixed' fails. */ + if (error != NULL) { + ELOG("Could not open fallback font '%s', trying with '-misc-*'\n",pattern); + pattern = "-misc-*"; + font_cookie = xcb_open_font_checked(conn, new->id, strlen(pattern), pattern); + info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern); + + check_error(conn, font_cookie, "Could open neither requested font nor fallback (fixed or -misc-*"); + } + } /* Get information (height/name) for this font */ xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL); From f162e7efaa4817c327d84c79854a5c3102a60105 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 10 Mar 2011 23:20:17 +0100 Subject: [PATCH 511/867] =?UTF-8?q?refactor=20font=20caching=20to=20just?= =?UTF-8?q?=20save=20the=20ID=20instead=20of=20mainting=20a=20cache=20with?= =?UTF-8?q?=20pattern=E2=86=92id-mapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/config.h | 2 +- include/data.h | 4 ---- include/xcb.h | 10 +++++----- src/cfgparse.y | 4 ++-- src/config.c | 5 ++++- src/floating.c | 3 +-- src/handlers.c | 3 +-- src/render.c | 3 +-- src/sighandler.c | 10 ++++------ src/x.c | 5 ++--- src/xcb.c | 42 ++++++++++++++---------------------------- 11 files changed, 35 insertions(+), 56 deletions(-) diff --git a/include/config.h b/include/config.h index 986f7aa2..32212e3d 100644 --- a/include/config.h +++ b/include/config.h @@ -86,7 +86,7 @@ struct Mode { */ struct Config { const char *terminal; - const char *font; + i3Font font; const char *ipc_socket_path; const char *restart_state_path; diff --git a/include/data.h b/include/data.h index 71347364..e10b8680 100644 --- a/include/data.h +++ b/include/data.h @@ -173,10 +173,6 @@ struct Autostart { * */ struct Font { - /** The name of the font, that is what the pattern resolves to */ - char *name; - /** A copy of the pattern to build a cache */ - char *pattern; /** The height of the font, built from font_ascent + font_descent */ int height; /** The xcb-id for the font */ diff --git a/include/xcb.h b/include/xcb.h index 1dc141e6..2d3f9cc1 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -71,11 +71,12 @@ enum { _NET_SUPPORTED = 0, extern unsigned int xcb_numlock_mask; /** - * Loads a font for usage, getting its height. This function is used very - * often, so it maintains a cache. + * Loads a font for usage, also getting its height. If fallback is true, + * i3 loads 'fixed' or '-misc-*' if the font cannot be found instead of + * exiting. * */ -i3Font *load_font(xcb_connection_t *conn, const char *pattern); +i3Font load_font(const char *pattern, bool fallback); /** * Returns the colorpixel to use for the given hex color (think of HTML). @@ -162,8 +163,7 @@ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) * real length (amount of glyphs) using the given font. * */ -int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *text, - int length); +int predict_text_width(char *text, int length); /** * Configures the given window to have the size/position specified by given rect diff --git a/src/cfgparse.y b/src/cfgparse.y index adb02122..5ee6dfd4 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -597,8 +597,8 @@ terminal: font: TOKFONT WHITESPACE STR { - config.font = $3; - printf("font %s\n", config.font); + config.font = load_font($3, true); + printf("font %s\n", $3); } ; diff --git a/src/config.c b/src/config.c index 8a394c1f..e7f4617b 100644 --- a/src/config.c +++ b/src/config.c @@ -332,7 +332,10 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, grab_all_keys(conn, false); } - REQUIRED_OPTION(font); + if (config.font.id == 0) { + ELOG("You did not specify required configuration option \"font\"\n"); + config.font = load_font("fixed", true); + } #if 0 /* Set an empty name for every workspace which got no name */ diff --git a/src/floating.c b/src/floating.c index 6aea7a91..fe878f75 100644 --- a/src/floating.c +++ b/src/floating.c @@ -94,8 +94,7 @@ void floating_enable(Con *con, bool automatic) { free(name); /* find the height for the decorations */ - i3Font *font = load_font(conn, config.font); - int deco_height = font->height + 5; + int deco_height = config.font.height + 5; DLOG("Original rect: (%d, %d) with %d x %d\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height); Rect zero = { 0, 0, 0, 0 }; diff --git a/src/handlers.c b/src/handlers.c index e8506843..9c584b8b 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -336,8 +336,7 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure DLOG("Configure request!\n"); if (con_is_floating(con) && con_is_leaf(con)) { /* find the height for the decorations */ - i3Font *font = load_font(conn, config.font); - int deco_height = font->height + 5; + int deco_height = config.font.height + 5; /* we actually need to apply the size/position changes to the *parent* * container */ Rect bsr = con_border_style_rect(con); diff --git a/src/render.c b/src/render.c index 37ee6e47..5bdc2e21 100644 --- a/src/render.c +++ b/src/render.c @@ -194,8 +194,7 @@ void render_con(Con *con, bool render_fullscreen) { } /* find the height for the decorations */ - i3Font *font = load_font(conn, config.font); - int deco_height = font->height + 5; + int deco_height = config.font.height + 5; /* precalculate the sizes to be able to correct rounding errors */ int sizes[children]; diff --git a/src/sighandler.c b/src/sighandler.c index 4b5fb103..d6128b5b 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -165,16 +165,14 @@ void handle_signal(int sig, siginfo_t *info, void *data) { xcb_event_handlers_init(conn, &sig_evenths); xcb_event_set_key_press_handler(&sig_evenths, sig_handle_key_press, NULL); - i3Font *font = load_font(conn, config.font); - /* width and height of the popup window, so that the text fits in */ int crash_text_num = sizeof(crash_text) / sizeof(char*); - int height = 13 + (crash_text_num * font->height); + int height = 13 + (crash_text_num * config.font.height); /* calculate width for longest text */ int text_len = strlen(crash_text[crash_text_longest]); char *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len); - int font_width = predict_text_width(conn, config.font, longest_text, text_len); + int font_width = predict_text_width(longest_text, text_len); int width = font_width + 20; /* Open a popup window on each virtual screen */ @@ -192,7 +190,7 @@ void handle_signal(int sig, siginfo_t *info, void *data) { xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); /* Create graphics context */ - xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font->id); + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, config.font.id); /* Grab the keyboard to get all input */ xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); @@ -201,7 +199,7 @@ void handle_signal(int sig, siginfo_t *info, void *data) { xcb_grab_pointer(conn, false, win, XCB_NONE, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, win, XCB_NONE, XCB_CURRENT_TIME); - sig_draw_window(win, width, height, font->height); + sig_draw_window(win, width, height, config.font.height); xcb_flush(conn); } diff --git a/src/x.c b/src/x.c index d4b95315..c055f649 100644 --- a/src/x.c +++ b/src/x.c @@ -311,11 +311,10 @@ void x_draw_decoration(Con *con) { con->deco_rect.y + con->deco_rect.height - 1); /* to_y */ /* 5: draw the title */ - i3Font *font = load_font(conn, config.font); uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; - uint32_t values[] = { color->text, color->background, font->id }; + uint32_t values[] = { color->text, color->background, config.font.id }; xcb_change_gc(conn, parent->gc, mask, values); - int text_offset_y = font->height + (con->deco_rect.height - font->height) / 2 - 1; + int text_offset_y = config.font.height + (con->deco_rect.height - config.font.height) / 2 - 1; struct Window *win = con->window; if (win == NULL || win->name_x == NULL) { diff --git a/src/xcb.c b/src/xcb.c index 9ec0df9c..aaad590d 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -17,24 +17,19 @@ TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached unsigned int xcb_numlock_mask; /* - * Loads a font for usage, getting its height. This function is used very often, so it - * maintains a cache. + * Loads a font for usage, also getting its height. If fallback is true, + * i3 loads 'fixed' or '-misc-*' if the font cannot be found instead of + * exiting. * */ -i3Font *load_font(xcb_connection_t *conn, const char *pattern) { - /* Check if we got the font cached */ - i3Font *font; - TAILQ_FOREACH(font, &cached_fonts, fonts) - if (strcmp(font->pattern, pattern) == 0) - return font; - - i3Font *new = smalloc(sizeof(i3Font)); +i3Font load_font(const char *pattern, bool fallback) { + i3Font new; xcb_void_cookie_t font_cookie; xcb_list_fonts_with_info_cookie_t info_cookie; /* Send all our requests first */ - new->id = xcb_generate_id(conn); - font_cookie = xcb_open_font_checked(conn, new->id, strlen(pattern), pattern); + new.id = xcb_generate_id(conn); + font_cookie = xcb_open_font_checked(conn, new.id, strlen(pattern), pattern); info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern); /* Check for errors. If errors, fall back to default font. */ @@ -44,7 +39,7 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern) { if (error != NULL) { ELOG("Could not open font %s (X error %d). Reverting to backup font.\n", pattern, error->error_code); pattern = "fixed"; - font_cookie = xcb_open_font_checked(conn, new->id, strlen(pattern), pattern); + font_cookie = xcb_open_font_checked(conn, new.id, strlen(pattern), pattern); info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern); /* Check if we managed to open 'fixed' */ @@ -54,7 +49,7 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern) { if (error != NULL) { ELOG("Could not open fallback font '%s', trying with '-misc-*'\n",pattern); pattern = "-misc-*"; - font_cookie = xcb_open_font_checked(conn, new->id, strlen(pattern), pattern); + font_cookie = xcb_open_font_checked(conn, new.id, strlen(pattern), pattern); info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern); check_error(conn, font_cookie, "Could open neither requested font nor fallback (fixed or -misc-*"); @@ -65,14 +60,7 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern) { xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL); exit_if_null(reply, "Could not load font \"%s\"\n", pattern); - if (asprintf(&(new->name), "%.*s", xcb_list_fonts_with_info_name_length(reply), - xcb_list_fonts_with_info_name(reply)) == -1) - die("asprintf() failed\n"); - new->pattern = sstrdup(pattern); - new->height = reply->font_ascent + reply->font_descent; - - /* Insert into cache */ - TAILQ_INSERT_TAIL(&cached_fonts, new, fonts); + new.height = reply->font_ascent + reply->font_descent; return new; } @@ -127,9 +115,9 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl values[0] = xcursor_get_cursor(cursor); xcb_change_window_attributes(conn, result, mask, values); } else { - i3Font *cursor_font = load_font(conn, "cursor"); + i3Font cursor_font = load_font("cursor", false); int xcb_cursor = xcursor_get_xcb_cursor(cursor); - xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id, + xcb_create_glyph_cursor(conn, cursor_id, cursor_font.id, cursor_font.id, xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535); xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id); xcb_free_cursor(conn, cursor_id); @@ -318,15 +306,13 @@ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) * length (amount of glyphs) using the given font. * */ -int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *text, int length) { - i3Font *font = load_font(conn, font_pattern); - +int predict_text_width(char *text, int length) { xcb_query_text_extents_cookie_t cookie; xcb_query_text_extents_reply_t *reply; xcb_generic_error_t *error; int width; - cookie = xcb_query_text_extents(conn, font->id, length, (xcb_char2b_t*)text); + cookie = xcb_query_text_extents(conn, config.font.id, length, (xcb_char2b_t*)text); if ((reply = xcb_query_text_extents_reply(conn, cookie, &error)) == NULL) { ELOG("Could not get text extents (X error code %d)\n", error->error_code); From cdeb49127fe25fded22d80a94ddd26cf46253b68 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Mar 2011 16:34:35 +0100 Subject: [PATCH 512/867] Bugfix: restore focus to the correct window when a non-focused window gets destroyed (+testcase) --- src/con.c | 18 ++++++++++++------ testcases/t/29-focus-after-close.t | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/con.c b/src/con.c index 91f11d24..0ed4436a 100644 --- a/src/con.c +++ b/src/con.c @@ -623,12 +623,18 @@ Con *con_next_focused(Con *con) { return con_descend_focused(output_get_content(con->parent->parent)); } - /* try to focus the next container on the same level as this one */ - next = TAILQ_NEXT(con, focused); - - /* if that was not possible, go up to its parent */ - if (next == TAILQ_END(&(parent->nodes_head))) - next = con->parent; + /* if 'con' is not the first entry in the focus stack, use the first one as + * it’s currently focused already */ + Con *first = TAILQ_FIRST(&(con->parent->focus_head)); + if (first != con) { + DLOG("Using first entry %p\n", first); + next = first; + } else { + /* try to focus the next container on the same level as this one or fall + * back to its parent */ + if (!(next = TAILQ_NEXT(con, focused))) + next = con->parent; + } /* now go down the focus stack as far as * possible, excluding the current container */ diff --git a/testcases/t/29-focus-after-close.t b/testcases/t/29-focus-after-close.t index d922578c..b34e6686 100644 --- a/testcases/t/29-focus-after-close.t +++ b/testcases/t/29-focus-after-close.t @@ -4,8 +4,11 @@ # Check if the focus is correctly restored after closing windows. # use i3test; +use X11::XCB qw(:all); use List::Util qw(first); +my $x = X11::XCB::Connection->new; + my $i3 = i3("/tmp/nestedcons"); my $tmp = fresh_workspace; @@ -87,6 +90,27 @@ is(get_focused($tmp), $right, 'top right container focused (in focus stack)'); my $tr = first { $_->{id} eq $right } @{$nodes->[0]->{nodes}}; is($tr->{focused}, 1, 'top right container really has focus'); +############################################################## +# check if focus is correct after closing an unfocused window +############################################################## + +$tmp = fresh_workspace; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + +$first = open_empty_con($i3); +$middle = open_empty_con($i3); +# XXX: the $right empty con will be filled with the x11 window we are creating afterwards +$right = open_empty_con($i3); +my $win = open_standard_window($x, '#00ff00'); + +cmd qq|[con_id="$middle"] focus|; +$win->destroy; + +sleep 0.25; + +is(get_focused($tmp), $middle, 'middle container focused'); + ############################################################## # and now for something completely different: # check if the pointer position is relevant when restoring focus From b8a716c370a107ed7186f95ce3bf77c253ede53a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sun, 13 Mar 2011 20:32:54 -0300 Subject: [PATCH 513/867] Reload the same config file specified in the command line. Fixes: #346 --- src/config.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/config.c b/src/config.c index e7f4617b..80071d21 100644 --- a/src/config.c +++ b/src/config.c @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * @@ -232,15 +232,22 @@ static char *get_config_path() { * */ static void parse_configuration(const char *override_configpath) { - if (override_configpath != NULL) { - parse_file(override_configpath); - return; - } + static const char *saved_configpath = NULL; - char *path = get_config_path(); - DLOG("Parsing configfile %s\n", path); - parse_file(path); - free(path); + if (override_configpath != NULL) { + saved_configpath = override_configpath; + parse_file(override_configpath); + return; + } + else if (saved_configpath != NULL) { + parse_file(saved_configpath); + return; + } + + char *path = get_config_path(); + DLOG("Parsing configfile %s\n", path); + parse_file(path); + free(path); } /* From b4e3dfd76b303f654fa8b6007138ad7657146670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sun, 13 Mar 2011 20:56:04 -0300 Subject: [PATCH 514/867] Add the "created" parameter to workspace_get. If created is not NULL, *created is set to whether or not the workspace has been just created. --- include/workspace.h | 5 ++++- src/cmdparse.y | 2 +- src/manage.c | 2 +- src/workspace.c | 11 ++++++++--- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/include/workspace.h b/include/workspace.h index d903ea82..ae43e2f1 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -22,8 +22,11 @@ * creating the workspace if necessary (by allocating the necessary amount of * memory and initializing the data structures correctly). * + * If created is not NULL, *created will be set to whether or not the + * workspace has just been created. + * */ -Con *workspace_get(const char *num); +Con *workspace_get(const char *num, bool *created); #if 0 /** diff --git a/src/cmdparse.y b/src/cmdparse.y index 0abcab08..f86d51ec 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -537,7 +537,7 @@ move: printf("should move window to workspace %s\n", $5); /* get the workspace */ - Con *ws = workspace_get($5); + Con *ws = workspace_get($5, NULL); free($5); /* check if the match is empty, not if the result is empty */ diff --git a/src/manage.c b/src/manage.c index 1465457e..c4d6172b 100644 --- a/src/manage.c +++ b/src/manage.c @@ -458,7 +458,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } DLOG("Changing container/workspace and unmapping the client\n"); - Workspace *t_ws = workspace_get(assign->workspace-1); + Workspace *t_ws = workspace_get(assign->workspace-1, NULL); workspace_initialize(t_ws, c_ws->output, false); new->container = t_ws->table[t_ws->current_col][t_ws->current_row]; diff --git a/src/workspace.c b/src/workspace.c index fa84e204..4d3c5218 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -17,7 +17,7 @@ * memory and initializing the data structures correctly). * */ -Con *workspace_get(const char *num) { +Con *workspace_get(const char *num, bool *created) { Con *output, *workspace = NULL, *child; /* TODO: could that look like this in the future? @@ -63,6 +63,11 @@ Con *workspace_get(const char *num) { con_attach(workspace, content, false); ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); + if (created != NULL) + *created = true; + } + else if (created != NULL) { + *created = false; } //ewmh_update_workarea(); @@ -201,7 +206,7 @@ static void workspace_reassign_sticky(Con *con) { void workspace_show(const char *num) { Con *workspace, *current, *old = NULL; - workspace = workspace_get(num); + workspace = workspace_get(num, NULL); /* disable fullscreen for the other workspaces and get the workspace we are * currently on. */ @@ -454,7 +459,7 @@ Workspace *get_first_workspace_for_output(Output *output) { int last_ws = 0; TAILQ_FOREACH(ws, workspaces, workspaces) last_ws = ws->num; - result = workspace_get(last_ws + 1); + result = workspace_get(last_ws + 1, NULL); } workspace_initialize(result, output, false); From c0563af3e27543d97eb3d201c2e197cc6285cb80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sun, 13 Mar 2011 21:09:32 -0300 Subject: [PATCH 515/867] Bring back some more EWMH support. --- Makefile | 2 +- include/all.h | 1 + include/ewmh.h | 2 +- src/ewmh.c | 119 +++++++++++++++++++++++++----------------------- src/randr.c | 2 +- src/workspace.c | 11 +++-- src/x.c | 1 + 7 files changed, 76 insertions(+), 62 deletions(-) diff --git a/Makefile b/Makefile index 4b2f240e..991fb49f 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c -FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c src/move.c src/output.c +FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c src/move.c src/output.c src/ewmh.c FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) diff --git a/include/all.h b/include/all.h index 23c7fe36..6251932c 100644 --- a/include/all.h +++ b/include/all.h @@ -56,5 +56,6 @@ #include "sighandler.h" #include "move.h" #include "output.h" +#include "ewmh.h" #endif diff --git a/include/ewmh.h b/include/ewmh.h index c73c4a45..2f2bf431 100644 --- a/include/ewmh.h +++ b/include/ewmh.h @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * diff --git a/src/ewmh.c b/src/ewmh.c index 6bfa3096..a8bc51c8 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * @@ -10,16 +10,8 @@ * ewmh.c: Functions to get/set certain EWMH properties easily. * */ -#include -#include -#include -#include "data.h" -#include "table.h" -#include "i3.h" -#include "xcb.h" -#include "util.h" -#include "log.h" +#include "all.h" /* * Updates _NET_CURRENT_DESKTOP with the current desktop number. @@ -29,10 +21,20 @@ * */ void ewmh_update_current_desktop() { - uint32_t current_desktop = c_ws->num; - xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root, - atoms[_NET_CURRENT_DESKTOP], CARDINAL, 32, 1, - ¤t_desktop); + Con *focused_ws = con_get_workspace(focused); + Con *output; + uint32_t idx = 0; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *ws; + TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { + if (ws == focused_ws) { + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, + atoms[_NET_CURRENT_DESKTOP], CARDINAL, 32, 1, &idx); + return; + } + ++idx; + } + } } /* @@ -43,8 +45,8 @@ void ewmh_update_current_desktop() { * */ void ewmh_update_active_window(xcb_window_t window) { - xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root, - atoms[_NET_ACTIVE_WINDOW], WINDOW, 32, 1, &window); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, + atoms[_NET_ACTIVE_WINDOW], WINDOW, 32, 1, &window); } /* @@ -56,48 +58,53 @@ void ewmh_update_active_window(xcb_window_t window) { * */ void ewmh_update_workarea() { - Workspace *ws; - int num_workspaces = 0, count = 0; - Rect last_rect = {0, 0, 0, 0}; + int num_workspaces = 0, count = 0; + Rect last_rect = {0, 0, 0, 0}; + Con *output; - /* Get the number of workspaces */ - TAILQ_FOREACH(ws, workspaces, workspaces) { - /* Check if we need to initialize last_rect. The case that the - * first workspace is all-zero may happen when the user - * assigned workspace 2 for his first screen, for example. Thus - * we need an initialized last_rect in the very first run of - * the following loop. */ - if (last_rect.width == 0 && last_rect.height == 0 && + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *ws; + TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { + /* Check if we need to initialize last_rect. The case that the + * first workspace is all-zero may happen when the user + * assigned workspace 2 for his first screen, for example. Thus + * we need an initialized last_rect in the very first run of + * the following loop. */ + if (last_rect.width == 0 && last_rect.height == 0 && ws->rect.width != 0 && ws->rect.height != 0) { - memcpy(&last_rect, &(ws->rect), sizeof(Rect)); - } - num_workspaces++; - } - - DLOG("Got %d workspaces\n", num_workspaces); - uint8_t *workarea = smalloc(sizeof(Rect) * num_workspaces); - TAILQ_FOREACH(ws, workspaces, workspaces) { - DLOG("storing %d: %dx%d with %d x %d\n", count, ws->rect.x, - ws->rect.y, ws->rect.width, ws->rect.height); - /* If a workspace is not yet initialized and thus its - * dimensions are zero, we will instead put the dimensions - * of the last workspace in the list. For example firefox - * intersects all workspaces and does not cope so well with - * an all-zero workspace. */ - if (ws->rect.width == 0 || ws->rect.height == 0) { - DLOG("re-using last_rect (%dx%d, %d, %d)\n", - last_rect.x, last_rect.y, last_rect.width, - last_rect.height); - memcpy(workarea + (sizeof(Rect) * count++), &last_rect, sizeof(Rect)); - continue; - } - memcpy(workarea + (sizeof(Rect) * count++), &(ws->rect), sizeof(Rect)); memcpy(&last_rect, &(ws->rect), sizeof(Rect)); + } + num_workspaces++; } - xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root, - atoms[_NET_WORKAREA], CARDINAL, 32, - num_workspaces * (sizeof(Rect) / sizeof(uint32_t)), - workarea); - free(workarea); - xcb_flush(global_conn); + } + + DLOG("Got %d workspaces\n", num_workspaces); + uint8_t *workarea = smalloc(sizeof(Rect) * num_workspaces); + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *ws; + TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { + DLOG("storing %d: %dx%d with %d x %d\n", count, ws->rect.x, + ws->rect.y, ws->rect.width, ws->rect.height); + /* If a workspace is not yet initialized and thus its + * dimensions are zero, we will instead put the dimensions + * of the last workspace in the list. For example firefox + * intersects all workspaces and does not cope so well with + * an all-zero workspace. */ + if (ws->rect.width == 0 || ws->rect.height == 0) { + DLOG("re-using last_rect (%dx%d, %d, %d)\n", + last_rect.x, last_rect.y, last_rect.width, + last_rect.height); + memcpy(workarea + (sizeof(Rect) * count++), &last_rect, sizeof(Rect)); + continue; + } + memcpy(workarea + (sizeof(Rect) * count++), &(ws->rect), sizeof(Rect)); + memcpy(&last_rect, &(ws->rect), sizeof(Rect)); + } + } + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, + atoms[_NET_WORKAREA], CARDINAL, 32, + num_workspaces * (sizeof(Rect) / sizeof(uint32_t)), + workarea); + free(workarea); + xcb_flush(conn); } diff --git a/src/randr.c b/src/randr.c index d4dc770b..acac36ca 100644 --- a/src/randr.c +++ b/src/randr.c @@ -665,7 +665,7 @@ void randr_query_outputs() { disable_randr(conn); } - //ewmh_update_workarea(); + ewmh_update_workarea(); #if 0 /* Just go through each active output and associate one workspace */ diff --git a/src/workspace.c b/src/workspace.c index 4d3c5218..77b5ceb2 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -70,8 +70,6 @@ Con *workspace_get(const char *num, bool *created) { *created = false; } - //ewmh_update_workarea(); - return workspace; } @@ -206,7 +204,8 @@ static void workspace_reassign_sticky(Con *con) { void workspace_show(const char *num) { Con *workspace, *current, *old = NULL; - workspace = workspace_get(num, NULL); + bool changed_num_workspaces; + workspace = workspace_get(num, &changed_num_workspaces); /* disable fullscreen for the other workspaces and get the workspace we are * currently on. */ @@ -241,6 +240,7 @@ void workspace_show(const char *num) { LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); tree_close(old, false, false); ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}"); + changed_num_workspaces = true; } } @@ -248,6 +248,11 @@ void workspace_show(const char *num) { workspace->fullscreen_mode = CF_OUTPUT; LOG("focused now = %p / %s\n", focused, focused->name); + /* Update the EWMH hints */ + if (changed_num_workspaces) + ewmh_update_workarea(); + ewmh_update_current_desktop(); + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); #if 0 diff --git a/src/x.c b/src/x.c index c055f649..64201ead 100644 --- a/src/x.c +++ b/src/x.c @@ -616,6 +616,7 @@ void x_push_changes(Con *con) { } else { DLOG("Updating focus (focused: %p / %s)\n", focused, focused->name); xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME); + ewmh_update_active_window(to_focus); focused_id = to_focus; } } From 77db9f937f5212dd8117cb6deadf4549203a0bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sun, 13 Mar 2011 20:44:16 -0300 Subject: [PATCH 516/867] Make it easier to use other atoms. --- include/i3.h | 5 ++--- include/xcb.h | 46 ++++++++++++++++++++++++---------------------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/include/i3.h b/include/i3.h index ed8cacb6..3c238c46 100644 --- a/include/i3.h +++ b/include/i3.h @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * @@ -17,12 +17,11 @@ #include "queue.h" #include "data.h" +#include "xcb.h" #ifndef _I3_H #define _I3_H -#define NUM_ATOMS 21 - extern xcb_connection_t *conn; extern xcb_key_symbols_t *keysyms; extern char **start_argv; diff --git a/include/xcb.h b/include/xcb.h index 2d3f9cc1..8b531a34 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * @@ -45,27 +45,29 @@ XCB_EVENT_MASK_ENTER_WINDOW) /* …user moves cursor inside our window */ -enum { _NET_SUPPORTED = 0, - _NET_SUPPORTING_WM_CHECK, - _NET_WM_NAME, - _NET_WM_STATE_FULLSCREEN, - _NET_WM_STATE, - _NET_WM_WINDOW_TYPE, - _NET_WM_WINDOW_TYPE_DOCK, - _NET_WM_WINDOW_TYPE_DIALOG, - _NET_WM_WINDOW_TYPE_UTILITY, - _NET_WM_WINDOW_TYPE_TOOLBAR, - _NET_WM_WINDOW_TYPE_SPLASH, - _NET_WM_DESKTOP, - _NET_WM_STRUT_PARTIAL, - WM_PROTOCOLS, - WM_DELETE_WINDOW, - UTF8_STRING, - WM_STATE, - WM_CLIENT_LEADER, - _NET_CURRENT_DESKTOP, - _NET_ACTIVE_WINDOW, - _NET_WORKAREA +enum { + _NET_SUPPORTED = 0, + _NET_SUPPORTING_WM_CHECK, + _NET_WM_NAME, + _NET_WM_STATE_FULLSCREEN, + _NET_WM_STATE, + _NET_WM_WINDOW_TYPE, + _NET_WM_WINDOW_TYPE_DOCK, + _NET_WM_WINDOW_TYPE_DIALOG, + _NET_WM_WINDOW_TYPE_UTILITY, + _NET_WM_WINDOW_TYPE_TOOLBAR, + _NET_WM_WINDOW_TYPE_SPLASH, + _NET_WM_DESKTOP, + _NET_WM_STRUT_PARTIAL, + WM_PROTOCOLS, + WM_DELETE_WINDOW, + UTF8_STRING, + WM_STATE, + WM_CLIENT_LEADER, + _NET_CURRENT_DESKTOP, + _NET_ACTIVE_WINDOW, + _NET_WORKAREA, + NUM_ATOMS }; extern unsigned int xcb_numlock_mask; From fb9978b97533c5987f9af428ce58f6f0733e5ff5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Mar 2011 17:15:04 +0100 Subject: [PATCH 517/867] ewmh: add comment to describe why we count --- src/ewmh.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ewmh.c b/src/ewmh.c index a8bc51c8..64ab096f 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -24,6 +24,8 @@ void ewmh_update_current_desktop() { Con *focused_ws = con_get_workspace(focused); Con *output; uint32_t idx = 0; + /* We count to get the index of this workspace because named workspaces + * don’t have the ->num property */ TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { Con *ws; TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { From 01365edb300436e478779857c3131c2ed09e8534 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Mar 2011 17:15:18 +0100 Subject: [PATCH 518/867] ewmh: bump copyright --- src/ewmh.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ewmh.c b/src/ewmh.c index 64ab096f..67daa022 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2011 Michael Stapelberg and contributors * * See file LICENSE for license information. * From 89ef41dadf516b12f5d4c8ff09d95b425fcc1174 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Mar 2011 17:20:04 +0100 Subject: [PATCH 519/867] re-implement support for MappingNotifys --- include/handlers.h | 2 +- src/handlers.c | 28 +++++++++++++--------------- src/main.c | 3 +++ 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/include/handlers.h b/include/handlers.h index 2d592e64..76ba3eea 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -41,7 +41,6 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notify_event_t *event); -#if 0 /** * Called when the keyboard mapping changes (for example by using Xmodmap), * we need to update our key bindings then (re-translate symbols). @@ -49,6 +48,7 @@ int handle_motion_notify(void *ignored, xcb_connection_t *conn, */ int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_notify_event_t *event); +#if 0 /** * Checks if the button press was on a stack window, handles focus setting and diff --git a/src/handlers.c b/src/handlers.c index 9c584b8b..6e6626a9 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -251,30 +251,28 @@ int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notif return 1; } -#if 0 /* * Called when the keyboard mapping changes (for example by using Xmodmap), * we need to update our key bindings then (re-translate symbols). * */ int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_notify_event_t *event) { - if (event->request != XCB_MAPPING_KEYBOARD && - event->request != XCB_MAPPING_MODIFIER) - return 0; - - DLOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n"); - xcb_refresh_keyboard_mapping(keysyms, event); - - xcb_get_numlock_mask(conn); - - ungrab_all_keys(conn); - translate_keysyms(); - grab_all_keys(conn, false); - + if (event->request != XCB_MAPPING_KEYBOARD && + event->request != XCB_MAPPING_MODIFIER) return 0; + + DLOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n"); + xcb_refresh_keyboard_mapping(keysyms, event); + + xcb_get_numlock_mask(conn); + + ungrab_all_keys(conn); + translate_keysyms(); + grab_all_keys(conn, false); + + return 0; } -#endif /* * A new window appeared on the screen (=was mapped), so let’s manage it. * diff --git a/src/main.c b/src/main.c index c497ab9e..eb173221 100644 --- a/src/main.c +++ b/src/main.c @@ -417,6 +417,9 @@ int main(int argc, char *argv[]) { /* Watch WM_TRANSIENT_FOR property (to which client this popup window belongs) */ xcb_property_set_handler(&prophs, WM_TRANSIENT_FOR, UINT_MAX, handle_transient_for, NULL); + /* Mapping notify = keyboard mapping changed (Xmodmap), re-grab bindings */ + xcb_event_set_mapping_notify_handler(&evenths, handle_mapping_notify, NULL); + /* Set up the atoms we support */ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 7, atoms); /* Set up the window manager’s name */ From 18215445f83cc8564d00de25836eca63ca21cb1e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Mar 2011 22:28:55 +0100 Subject: [PATCH 520/867] remove unused current_bindings (left-over from cfgparse.y) --- src/cmdparse.y | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index f86d51ec..bab36952 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -22,7 +22,6 @@ extern int cmdyyparse(void); extern FILE *cmdyyin; YY_BUFFER_STATE cmdyy_scan_string(const char *); -static struct bindings_head *current_bindings; static struct context *context; static Match current_match; From 8928823e07352615165f7e7399728143002f2563 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Mar 2011 22:29:07 +0100 Subject: [PATCH 521/867] remove usless 'operation' token, already handled by 'operation optwhitespace' --- src/cmdparse.y | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index bab36952..be402f9e 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -277,7 +277,6 @@ criteria: ; operations: - operation | operation optwhitespace | operations ',' optwhitespace operation ; From 627683c053dc0cfbb75f8a19d77e62e3ff014744 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Mar 2011 23:03:13 +0100 Subject: [PATCH 522/867] =?UTF-8?q?cmdparse:=20don=E2=80=99t=20allow=20emp?= =?UTF-8?q?ty=20commands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cmdparse.y | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index be402f9e..5eb4a0b1 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -153,8 +153,8 @@ char *parse_cmd(const char *new) { %% -commands: /* empty */ - | commands optwhitespace ';' optwhitespace command +commands: + commands optwhitespace ';' optwhitespace command | command { owindow *current; From f900fab453e394aad28923e236694f185ba4e949 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Mar 2011 23:03:25 +0100 Subject: [PATCH 523/867] =?UTF-8?q?cmdparse:=20don=E2=80=99t=20allow=20emp?= =?UTF-8?q?ty=20operations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cmdparse.y | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index 5eb4a0b1..14fbbf90 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -277,7 +277,7 @@ criteria: ; operations: - | operation optwhitespace + operation optwhitespace | operations ',' optwhitespace operation ; From b21137b2c059c9e0c21832f309ada7c35ffbdeb1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Mar 2011 23:08:33 +0100 Subject: [PATCH 524/867] cmdparse: expect 4 shift/reduce conflicts --- src/cmdparse.y | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cmdparse.y b/src/cmdparse.y index 14fbbf90..476f7977 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -90,6 +90,7 @@ char *parse_cmd(const char *new) { %} +%expect 4 %error-verbose %lex-param { struct context *context } From 7100d32971e84dbbef19da0043135b0c14595576 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Mar 2011 23:14:40 +0100 Subject: [PATCH 525/867] cmdparse: correctly parse con_id/id (fixes warning) --- src/cmdparse.y | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index 476f7977..37605bb8 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -13,6 +13,7 @@ #include #include #include +#include #include "all.h" @@ -259,16 +260,32 @@ criteria: | TOK_CON_ID '=' STR { printf("criteria: id = %s\n", $3); - /* TODO: correctly parse number */ - current_match.con_id = (Con*)atoi($3); - printf("id as int = %p\n", current_match.con_id); + char *end; + long parsed = strtol($3, &end, 10); + if (parsed == LONG_MIN || + parsed == LONG_MAX || + parsed < 0 || + (end && *end != '\0')) { + ELOG("Could not parse con id \"%s\"\n", $3); + } else { + current_match.con_id = (Con*)parsed; + printf("id as int = %p\n", current_match.con_id); + } } | TOK_ID '=' STR { printf("criteria: window id = %s\n", $3); - /* TODO: correctly parse number */ - current_match.id = atoi($3); - printf("window id as int = %d\n", current_match.id); + char *end; + long parsed = strtol($3, &end, 10); + if (parsed == LONG_MIN || + parsed == LONG_MAX || + parsed < 0 || + (end && *end != '\0')) { + ELOG("Could not parse window id \"%s\"\n", $3); + } else { + current_match.id = parsed; + printf("window id as int = %d\n", current_match.id); + } } | TOK_MARK '=' STR { From 76e978bfb3f0c1b0c6c9ca8cda215e3a761ca421 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Mar 2011 23:17:52 +0100 Subject: [PATCH 526/867] fix small warnings when compiling with DEBUG=0 --- include/data.h | 2 -- src/main.c | 2 +- src/move.c | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/include/data.h b/include/data.h index e10b8680..7cbf3dd6 100644 --- a/include/data.h +++ b/include/data.h @@ -177,8 +177,6 @@ struct Font { int height; /** The xcb-id for the font */ xcb_font_t id; - - TAILQ_ENTRY(Font) fonts; }; diff --git a/src/main.c b/src/main.c index eb173221..9ab578d1 100644 --- a/src/main.c +++ b/src/main.c @@ -145,7 +145,7 @@ int main(int argc, char *argv[]) { char *override_configpath = NULL; bool autostart = true; char *layout_path = NULL; - bool delete_layout_path; + bool delete_layout_path = false; bool only_check_config = false; bool force_xinerama = false; bool disable_signalhandler = false; diff --git a/src/move.c b/src/move.c index 955cb6b0..caa23eaf 100644 --- a/src/move.c +++ b/src/move.c @@ -153,7 +153,7 @@ void tree_move(int direction) { if (direction == TOK_UP || direction == TOK_LEFT) { position = BEFORE; next = TAILQ_PREV(above, nodes_head, nodes); - } else if (direction == TOK_DOWN || direction == TOK_RIGHT) { + } else { position = AFTER; next = TAILQ_NEXT(above, nodes); } From b484ed5f9d4214ff6bdf205d0beec2207d3ed272 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 14 Mar 2011 23:50:29 +0100 Subject: [PATCH 527/867] When making floating cons tiling, re-insert next to the next focused *tiling* con (Thanks mseed) Fixes: #337 and #350 --- include/con.h | 10 ++++++++++ src/con.c | 26 ++++++++++++++++++++++++++ src/floating.c | 3 ++- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/include/con.h b/include/con.h index 8ffa7ce2..1dd65a6e 100644 --- a/include/con.h +++ b/include/con.h @@ -167,6 +167,16 @@ Con *con_get_next(Con *con, char way, orientation_t orientation); */ Con *con_descend_focused(Con *con); +/** + * Returns the focused con inside this client, descending the tree as far as + * possible. This comes in handy when attaching a con to a workspace at the + * currently focused position, for example. + * + * Works like con_descend_focused but considers only tiling cons. + * + */ +Con *con_descend_tiling_focused(Con *con); + /** * Returns a "relative" Rect which contains the amount of pixels that need to * be added to the original Rect to get the final position (obviously the diff --git a/src/con.c b/src/con.c index 0ed4436a..04db250b 100644 --- a/src/con.c +++ b/src/con.c @@ -694,6 +694,32 @@ Con *con_descend_focused(Con *con) { return next; } +/* + * Returns the focused con inside this client, descending the tree as far as + * possible. This comes in handy when attaching a con to a workspace at the + * currently focused position, for example. + * + * Works like con_descend_focused but considers only tiling cons. + * + */ +Con *con_descend_tiling_focused(Con *con) { + Con *next = con; + Con *before; + Con *child; + do { + before = next; + TAILQ_FOREACH(child, &(next->focus_head), focused) { + if (child->type == CT_FLOATING_CON) + continue; + + next = child; + break; + } + } while (before != next); + return next; +} + + /* * Returns a "relative" Rect which contains the amount of pixels that need to * be added to the original Rect to get the final position (obviously the diff --git a/src/floating.c b/src/floating.c index fe878f75..ef500bf6 100644 --- a/src/floating.c +++ b/src/floating.c @@ -175,7 +175,8 @@ void floating_disable(Con *con, bool automatic) { /* 3: re-attach to the parent of the currently focused con on the workspace * this floating con was on */ - Con *focused = con_descend_focused(con_get_workspace(con)); + Con *focused = con_descend_tiling_focused(con_get_workspace(con)); + /* if there is no other container on this workspace, focused will be the * workspace itself */ if (focused->type == CT_WORKSPACE) From 2f992f5c0ed75452a61b19d6c118e5f5f3ba67e9 Mon Sep 17 00:00:00 2001 From: Simon Kampe Date: Wed, 16 Mar 2011 11:56:51 +0100 Subject: [PATCH 528/867] Added config key for default orientation of containers (new_container_orientation) and added support in randr.c for automatically changing the orientation when user does a xrandr rotate. --- i3.config | 6 ++++++ include/config.h | 3 +++ src/cfgparse.l | 4 ++++ src/cfgparse.y | 19 +++++++++++++++++++ src/con.c | 6 +++++- src/config.c | 2 ++ src/randr.c | 36 +++++++++++++++++++++++++++++++++++- src/workspace.c | 13 ++++++++++++- 8 files changed, 86 insertions(+), 3 deletions(-) diff --git a/i3.config b/i3.config index 71511a50..b4c584fa 100644 --- a/i3.config +++ b/i3.config @@ -19,7 +19,13 @@ bindsym Mod1+Return exec /usr/bin/urxvt # Start dmenu (Mod1+p) bindsym Mod1+p exec /usr/bin/dmenu_run +# Default orientation +new_container_orientation horizontal + +# Horizontal orientation bindsym Mod1+h split h + +# Vertical orientation bindsym Mod1+v split v # Fullscreen (Mod1+f) diff --git a/include/config.h b/include/config.h index 32212e3d..f639df36 100644 --- a/include/config.h +++ b/include/config.h @@ -95,6 +95,9 @@ struct Config { int container_stack_limit; int container_stack_limit_value; + /** Default orientation for new containers */ + int default_orientation; + /** By default, focus follows mouse. If the user explicitly wants to * turn this off (and instead rely only on the keyboard for changing * focus), we allow him to do this with this relatively special option. diff --git a/src/cfgparse.l b/src/cfgparse.l index b8b1d73d..4776527b 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -92,6 +92,10 @@ set[^\n]* { return TOKCOMMENT; } ipc-socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; } ipc_socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; } restart_state { BEGIN(BIND_AWS_COND); return TOKRESTARTSTATE; } +new_container_orientation { return TOK_ORIENTATION; } +horizontal { return TOK_HORIZ; } +vertical { return TOK_VERT; } +auto { return TOK_AUTO; } new_container { return TOKNEWCONTAINER; } new_window { return TOKNEWWINDOW; } normal { return TOK_NORMAL; } diff --git a/src/cfgparse.y b/src/cfgparse.y index 5ee6dfd4..50058680 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -224,6 +224,10 @@ void parse_file(const char *f) { %token TOKCOLOR %token TOKARROW "→" %token TOKMODE "mode" +%token TOK_ORIENTATION "new_container_orientation" +%token TOK_HORIZ "horizontal" +%token TOK_VERT "vertical" +%token TOK_AUTO "auto" %token TOKNEWCONTAINER "new_container" %token TOKNEWWINDOW "new_window" %token TOK_NORMAL "normal" @@ -249,6 +253,7 @@ line: bindline | mode | floating_modifier + | orientation | new_container | new_window | focus_follows_mouse @@ -373,6 +378,20 @@ floating_modifier: } ; +orientation: + TOK_ORIENTATION WHITESPACE direction + { + DLOG("New containers should start with split direction %d\n", $3); + config.default_orientation = $3; + } + ; + +direction: + TOK_HORIZ { $$ = HORIZ; } + | TOK_VERT { $$ = VERT; } + | TOK_AUTO { $$ = NO_ORIENTATION; } + ; + new_container: TOKNEWCONTAINER WHITESPACE TOKCONTAINERMODE { diff --git a/src/con.c b/src/con.c index 04db250b..e1359069 100644 --- a/src/con.c +++ b/src/con.c @@ -794,7 +794,11 @@ void con_set_layout(Con *con, int layout) { /* 3: While the layout is irrelevant in stacked/tabbed mode, it needs * to be set. Otherwise, this con will not be interpreted as a split * container. */ - new->orientation = HORIZ; + if (config.default_orientation == NO_ORIENTATION) { + new->orientation = (con->rect.height > con->rect.width) ? VERT : HORIZ; + } else { + new->orientation = config.default_orientation; + } Con *old_focused = TAILQ_FIRST(&(con->focus_head)); if (old_focused == TAILQ_END(&(con->focus_head))) diff --git a/src/config.c b/src/config.c index 80071d21..8f8790eb 100644 --- a/src/config.c +++ b/src/config.c @@ -331,6 +331,8 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff"); config.default_border = BS_NORMAL; + /* Set default_orientation to NO_ORIENTATION for auto orientation. */ + config.default_orientation = NO_ORIENTATION; parse_configuration(override_configpath); diff --git a/src/randr.c b/src/randr.c index acac36ca..14b9085d 100644 --- a/src/randr.c +++ b/src/randr.c @@ -361,7 +361,17 @@ void output_init_con(Output *output) { free(name); ws->fullscreen_mode = CF_OUTPUT; - ws->orientation = HORIZ; + + /* If default_orientation is set to NO_ORIENTATION we determine + * orientation depending on output resolution. */ + if (config.default_orientation == NO_ORIENTATION) { + ws->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ; + DLOG("Auto orientation. Workspace size set to (%d,%d), setting orientation to %d.\n", + output->rect.width, output->rect.height, ws->orientation); + } else { + ws->orientation = config.default_orientation; + } + /* TODO: Set focus in main.c */ con_focus(ws); @@ -384,6 +394,30 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { DLOG("Output mode changed, updating rect\n"); assert(output->con != NULL); output->con->rect = output->rect; + + Con *current,*workspace,*child; + + /* Point current to the container of the workspaces */ + current = output->con->nodes_head.tqh_first->nodes.tqe_next; + + /* If default_orientation is NO_ORIENTATION, we change the orientation of + * the workspaces and their childs depending on output resolution. This is + * only done for workspaces with maximum one child. */ + if (config.default_orientation == NO_ORIENTATION) { + TAILQ_FOREACH(workspace, &(current->nodes_head), nodes) { + + /* Check if this workspace has <= 1 childs. */ + child = workspace->nodes_head.tqh_first; + if (child != NULL) + if (child->nodes.tqe_next == NULL) { + workspace->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ; + DLOG("Setting workspace [%d,%s]'s orientation to %d.\n", workspace->num, workspace->name, workspace->orientation); + child->orientation = workspace->orientation; + DLOG("Setting child [%d,%s]'s orientation to %d.\n", child->num, child->name, child->orientation); + } + } + } + #if 0 Rect bar_rect = {output->rect.x, output->rect.y + output->rect.height - (font->height + 6), diff --git a/src/workspace.c b/src/workspace.c index 77b5ceb2..4e93b92e 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -59,7 +59,18 @@ Con *workspace_get(const char *num, bool *created) { workspace->num = -1; else workspace->num = parsed_num; LOG("num = %d\n", workspace->num); - workspace->orientation = HORIZ; + + /* If default_orientation is set to NO_ORIENTATION we + * determine workspace orientation from workspace size. + * Otherwise we just set the orientation to default_orientation. */ + if (config.default_orientation == NO_ORIENTATION) { + workspace->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ; + DLOG("Auto orientation. Output resolution set to (%d,%d), setting orientation to %d.\n", + workspace->rect.width, workspace->rect.height, workspace->orientation); + } else { + workspace->orientation = config.default_orientation; + } + con_attach(workspace, content, false); ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); From eeb5bdd66fa73cba447c2e284ec743eb9301fff4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 17 Mar 2011 17:53:56 +0100 Subject: [PATCH 529/867] cleanup code of workspace rotation on output changes --- src/randr.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/randr.c b/src/randr.c index 14b9085d..7033e4c4 100644 --- a/src/randr.c +++ b/src/randr.c @@ -395,26 +395,27 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { assert(output->con != NULL); output->con->rect = output->rect; - Con *current,*workspace,*child; + Con *content, *workspace, *child; - /* Point current to the container of the workspaces */ - current = output->con->nodes_head.tqh_first->nodes.tqe_next; + /* Point content to the container of the workspaces */ + content = output_get_content(output->con); /* If default_orientation is NO_ORIENTATION, we change the orientation of * the workspaces and their childs depending on output resolution. This is * only done for workspaces with maximum one child. */ if (config.default_orientation == NO_ORIENTATION) { - TAILQ_FOREACH(workspace, &(current->nodes_head), nodes) { + TAILQ_FOREACH(workspace, &(content->nodes_head), nodes) { + /* Workspaces with more than one child are left untouched because + * we do not want to change an existing layout. */ + if (con_num_children(workspace) > 1) + continue; - /* Check if this workspace has <= 1 childs. */ - child = workspace->nodes_head.tqh_first; - if (child != NULL) - if (child->nodes.tqe_next == NULL) { - workspace->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ; - DLOG("Setting workspace [%d,%s]'s orientation to %d.\n", workspace->num, workspace->name, workspace->orientation); - child->orientation = workspace->orientation; - DLOG("Setting child [%d,%s]'s orientation to %d.\n", child->num, child->name, child->orientation); - } + workspace->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ; + DLOG("Setting workspace [%d,%s]'s orientation to %d.\n", workspace->num, workspace->name, workspace->orientation); + if ((child = TAILQ_FIRST(&(workspace->nodes_head)))) { + child->orientation = workspace->orientation; + DLOG("Setting child [%d,%s]'s orientation to %d.\n", child->num, child->name, child->orientation); + } } } From 65b05169d3f258fb7b4fab31fffa26fefaaf1103 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 17 Mar 2011 17:55:53 +0100 Subject: [PATCH 530/867] change the config parser to use default_orientation instead of new_container_orientation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s a shorter and probably more meaningful description as it is not immediately clear what a container exactly is when first installing i3. --- src/cfgparse.l | 2 +- src/cfgparse.y | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfgparse.l b/src/cfgparse.l index 4776527b..cc0dd320 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -92,7 +92,7 @@ set[^\n]* { return TOKCOMMENT; } ipc-socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; } ipc_socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; } restart_state { BEGIN(BIND_AWS_COND); return TOKRESTARTSTATE; } -new_container_orientation { return TOK_ORIENTATION; } +default_orientation { return TOK_ORIENTATION; } horizontal { return TOK_HORIZ; } vertical { return TOK_VERT; } auto { return TOK_AUTO; } diff --git a/src/cfgparse.y b/src/cfgparse.y index 50058680..68678b80 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -224,7 +224,7 @@ void parse_file(const char *f) { %token TOKCOLOR %token TOKARROW "→" %token TOKMODE "mode" -%token TOK_ORIENTATION "new_container_orientation" +%token TOK_ORIENTATION "default_orientation" %token TOK_HORIZ "horizontal" %token TOK_VERT "vertical" %token TOK_AUTO "auto" From c1a9e1593dff57bf61325cf94e7096c668c0aa0b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 17 Mar 2011 21:52:12 +0100 Subject: [PATCH 531/867] remove orientation and fall back to default behaviour --- i3.config | 3 --- 1 file changed, 3 deletions(-) diff --git a/i3.config b/i3.config index b4c584fa..b0cbc926 100644 --- a/i3.config +++ b/i3.config @@ -19,9 +19,6 @@ bindsym Mod1+Return exec /usr/bin/urxvt # Start dmenu (Mod1+p) bindsym Mod1+p exec /usr/bin/dmenu_run -# Default orientation -new_container_orientation horizontal - # Horizontal orientation bindsym Mod1+h split h From 36664c628993ac04ba1edca41686019fef58ddcc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 17 Mar 2011 22:27:59 +0100 Subject: [PATCH 532/867] Send WM_TAKE_FOCUS to clients when setting focus (fixes java swing problems) --- include/xcb.h | 1 + src/main.c | 2 ++ src/x.c | 16 ++++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/include/xcb.h b/include/xcb.h index 8b531a34..47eec76b 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -67,6 +67,7 @@ enum { _NET_CURRENT_DESKTOP, _NET_ACTIVE_WINDOW, _NET_WORKAREA, + WM_TAKE_FOCUS, NUM_ATOMS }; diff --git a/src/main.c b/src/main.c index 9ab578d1..002eb6a5 100644 --- a/src/main.c +++ b/src/main.c @@ -295,6 +295,7 @@ int main(int argc, char *argv[]) { REQUEST_ATOM(_NET_CURRENT_DESKTOP); REQUEST_ATOM(_NET_ACTIVE_WINDOW); REQUEST_ATOM(_NET_WORKAREA); + REQUEST_ATOM(WM_TAKE_FOCUS); /* Initialize the Xlib connection */ xlibdpy = xkbdpy = XOpenDisplay(NULL); @@ -398,6 +399,7 @@ int main(int argc, char *argv[]) { GET_ATOM(_NET_CURRENT_DESKTOP); GET_ATOM(_NET_ACTIVE_WINDOW); GET_ATOM(_NET_WORKAREA); + GET_ATOM(WM_TAKE_FOCUS); /* Watch _NET_WM_NAME (title of the window encoded in UTF-8) */ xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL); diff --git a/src/x.c b/src/x.c index 64201ead..7a3385e5 100644 --- a/src/x.c +++ b/src/x.c @@ -616,6 +616,22 @@ void x_push_changes(Con *con) { } else { DLOG("Updating focus (focused: %p / %s)\n", focused, focused->name); xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME); + + /* TODO: check if that client acccepts WM_TAKE_FOCUS at all */ + xcb_client_message_event_t ev; + + memset(&ev, 0, sizeof(xcb_client_message_event_t)); + + ev.response_type = XCB_CLIENT_MESSAGE; + ev.window = to_focus; + ev.type = atoms[WM_PROTOCOLS]; + ev.format = 32; + ev.data.data32[0] = atoms[WM_TAKE_FOCUS]; + ev.data.data32[1] = XCB_CURRENT_TIME; + + DLOG("Sending WM_TAKE_FOCUS to the client\n"); + xcb_send_event(conn, false, to_focus, XCB_EVENT_MASK_NO_EVENT, (char*)&ev); + ewmh_update_active_window(to_focus); focused_id = to_focus; } From 0639a7d95ba29229db7d545ad758ca6946ad293d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 18 Mar 2011 14:36:36 +0100 Subject: [PATCH 533/867] Make i3 compatible with the very latest xcb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This involves: • Compiling with xcb-util instead of xcb-{atom,aux} (they merged the libraries) • Not using xcb-{event,property} anymore (code removed upstream) • Not using the predefined WINDOW, CARDINEL, … atoms (removed upstream) • Using the new xcb_icccm_* data types/functions instead of just xcb_* (for example xcb_icccm_get_wm_hints instead of xcb_get_wm_hints) Also I refactored the atoms to use x-macros. --- common.mk | 15 +++- include/all.h | 6 +- include/atoms.xmacro | 31 ++++++++ include/data.h | 1 - include/handlers.h | 15 ++++ include/i3.h | 6 -- include/manage.h | 1 - include/util.h | 1 - include/workspace.h | 1 - include/xcb.h | 29 +------ include/xcb_compat.h | 24 ++++++ src/con.c | 4 +- src/ewmh.c | 6 +- src/floating.c | 18 ++--- src/handlers.c | 176 ++++++++++++++++++++++++++++++++++++++----- src/main.c | 144 ++++++++--------------------------- src/manage.c | 33 ++++---- src/sighandler.c | 24 +++--- src/window.c | 2 +- src/x.c | 28 +++---- 20 files changed, 336 insertions(+), 229 deletions(-) create mode 100644 include/atoms.xmacro create mode 100644 include/xcb_compat.h diff --git a/common.mk b/common.mk index a9bab084..db887cac 100644 --- a/common.mk +++ b/common.mk @@ -11,6 +11,10 @@ endif GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f .git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' .git/HEAD || echo 'unknown'))" VERSION:=$(shell git describe --tags --abbrev=0) +ifeq ($(shell which pkg-config 2>/dev/null 1>/dev/null || echo 1),1) +$(error "pkg-config was not found") +endif + # An easier way to get CFLAGS and LDFLAGS falling back in case there's # no pkg-config support for certain libraries cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1)) @@ -24,11 +28,14 @@ CFLAGS += -Wall CFLAGS += -Wunused-value CFLAGS += -Iinclude CFLAGS += -I/usr/local/include -CFLAGS += $(call cflags_for_lib, xcb-event) -CFLAGS += $(call cflags_for_lib, xcb-property) CFLAGS += $(call cflags_for_lib, xcb-keysyms) +ifeq ($(shell pkg-config --exists xcb-util || echo 1),1) +CFLAGS += -DXCB_COMPAT CFLAGS += $(call cflags_for_lib, xcb-atom) CFLAGS += $(call cflags_for_lib, xcb-aux) +else +CFLAGS += $(call cflags_for_lib, xcb-util) +endif CFLAGS += $(call cflags_for_lib, xcb-icccm) CFLAGS += $(call cflags_for_lib, xcb-xinerama) CFLAGS += $(call cflags_for_lib, xcb-randr) @@ -44,8 +51,12 @@ LDFLAGS += -lm LDFLAGS += $(call ldflags_for_lib, xcb-event, xcb-event) LDFLAGS += $(call ldflags_for_lib, xcb-property, xcb-property) LDFLAGS += $(call ldflags_for_lib, xcb-keysyms, xcb-keysyms) +ifeq ($(shell pkg-config --exists xcb-util || echo 1),1) LDFLAGS += $(call ldflags_for_lib, xcb-atom, xcb-atom) LDFLAGS += $(call ldflags_for_lib, xcb-aux, xcb-aux) +else +LDFLAGS += $(call ldflags_for_lib, xcb-util) +endif LDFLAGS += $(call ldflags_for_lib, xcb-icccm, xcb-icccm) LDFLAGS += $(call ldflags_for_lib, xcb-xinerama, xcb-xinerama) LDFLAGS += $(call ldflags_for_lib, xcb-randr, xcb-randr) diff --git a/include/all.h b/include/all.h index 6251932c..9d13bb95 100644 --- a/include/all.h +++ b/include/all.h @@ -26,10 +26,14 @@ #include #include -#include #include #include +/* Contains compatibility definitions for old libxcb versions */ +#ifdef XCB_COMPAT +#include "xcb_compat.h" +#endif + #include "util.h" #include "ipc.h" #include "tree.h" diff --git a/include/atoms.xmacro b/include/atoms.xmacro new file mode 100644 index 00000000..2543ffd2 --- /dev/null +++ b/include/atoms.xmacro @@ -0,0 +1,31 @@ +xmacro(_NET_SUPPORTED) +xmacro(_NET_SUPPORTING_WM_CHECK) +xmacro(_NET_WM_NAME) +xmacro(_NET_WM_STATE_FULLSCREEN) +xmacro(_NET_WM_STATE) +xmacro(_NET_WM_WINDOW_TYPE) +xmacro(_NET_WM_WINDOW_TYPE_DOCK) +xmacro(_NET_WM_WINDOW_TYPE_DIALOG) +xmacro(_NET_WM_WINDOW_TYPE_UTILITY) +xmacro(_NET_WM_WINDOW_TYPE_TOOLBAR) +xmacro(_NET_WM_WINDOW_TYPE_SPLASH) +xmacro(_NET_WM_DESKTOP) +xmacro(_NET_WM_STRUT_PARTIAL) +xmacro(WM_PROTOCOLS) +xmacro(WM_DELETE_WINDOW) +xmacro(UTF8_STRING) +xmacro(WM_STATE) +xmacro(WM_CLIENT_LEADER) +xmacro(_NET_CURRENT_DESKTOP) +xmacro(_NET_ACTIVE_WINDOW) +xmacro(_NET_WORKAREA) +xmacro(WM_TAKE_FOCUS) +xmacro(WM_HINTS) +xmacro(WM_NORMAL_HINTS) +xmacro(WM_TRANSIENT_FOR) +xmacro(ATOM) +xmacro(WINDOW) +xmacro(WM_NAME) +xmacro(WM_CLASS) +xmacro(STRING) +xmacro(CARDINAL) diff --git a/include/data.h b/include/data.h index 7cbf3dd6..8beb91ab 100644 --- a/include/data.h +++ b/include/data.h @@ -7,7 +7,6 @@ * include/data.h: This file defines all data structures used by i3 * */ -#include #include #include #include diff --git a/include/handlers.h b/include/handlers.h index 76ba3eea..1c8aa283 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -13,9 +13,24 @@ #include +extern int randr_base; void add_ignore_event(const int sequence); +/** + * Takes an xcb_generic_event_t and calls the appropriate handler, based on the + * event type. + * + */ +void handle_event(int type, xcb_generic_event_t *event); + +/** + * Sets the appropriate atoms for the property handlers after the atoms were + * received from X11 + * + */ +void property_handlers_init(); + /** * There was a key press. We compare this key code with our bindings table and * pass the bound action to parse_command(). diff --git a/include/i3.h b/include/i3.h index 3c238c46..060a0cf8 100644 --- a/include/i3.h +++ b/include/i3.h @@ -8,9 +8,6 @@ * See file LICENSE for license information. * */ -#include -#include -#include #include #include @@ -31,11 +28,8 @@ extern TAILQ_HEAD(bindings_head, Binding) *bindings; extern TAILQ_HEAD(autostarts_head, Autostart) autostarts; extern TAILQ_HEAD(assignments_head, Assignment) assignments; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; -extern xcb_event_handlers_t evenths; -extern xcb_property_handlers_t prophs; extern uint8_t root_depth; extern bool xcursor_supported, xkb_supported; -extern xcb_atom_t atoms[NUM_ATOMS]; extern xcb_window_t root; #endif diff --git a/include/manage.h b/include/manage.h index 555cefbe..e23eccf3 100644 --- a/include/manage.h +++ b/include/manage.h @@ -8,7 +8,6 @@ * See file LICENSE for license information. * */ -#include #include "data.h" diff --git a/include/util.h b/include/util.h index 064a4792..514e10bd 100644 --- a/include/util.h +++ b/include/util.h @@ -8,7 +8,6 @@ * See file LICENSE for license information. * */ -#include #include #include "data.h" diff --git a/include/workspace.h b/include/workspace.h index ae43e2f1..2132a792 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -8,7 +8,6 @@ * See file LICENSE for license information. * */ -#include #include "data.h" #include "tree.h" diff --git a/include/xcb.h b/include/xcb.h index 47eec76b..bec06b06 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -44,32 +44,9 @@ XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | /* …subwindows get notifies */ \ XCB_EVENT_MASK_ENTER_WINDOW) /* …user moves cursor inside our window */ - -enum { - _NET_SUPPORTED = 0, - _NET_SUPPORTING_WM_CHECK, - _NET_WM_NAME, - _NET_WM_STATE_FULLSCREEN, - _NET_WM_STATE, - _NET_WM_WINDOW_TYPE, - _NET_WM_WINDOW_TYPE_DOCK, - _NET_WM_WINDOW_TYPE_DIALOG, - _NET_WM_WINDOW_TYPE_UTILITY, - _NET_WM_WINDOW_TYPE_TOOLBAR, - _NET_WM_WINDOW_TYPE_SPLASH, - _NET_WM_DESKTOP, - _NET_WM_STRUT_PARTIAL, - WM_PROTOCOLS, - WM_DELETE_WINDOW, - UTF8_STRING, - WM_STATE, - WM_CLIENT_LEADER, - _NET_CURRENT_DESKTOP, - _NET_ACTIVE_WINDOW, - _NET_WORKAREA, - WM_TAKE_FOCUS, - NUM_ATOMS -}; +#define xmacro(atom) xcb_atom_t A_ ## atom; +#include "atoms.xmacro" +#undef xmacro extern unsigned int xcb_numlock_mask; diff --git a/include/xcb_compat.h b/include/xcb_compat.h new file mode 100644 index 00000000..736cbb99 --- /dev/null +++ b/include/xcb_compat.h @@ -0,0 +1,24 @@ +#ifndef _XCB_COMPAT_H +#define _XCB_COMPAT_H + +#define xcb_icccm_get_wm_protocols_reply_t xcb_get_wm_protocols_reply_t +#define xcb_icccm_get_wm_protocols_unchecked xcb_get_wm_protocols_unchecked +#define xcb_icccm_get_wm_protocols_reply xcb_get_wm_protocols_reply +#define xcb_icccm_get_wm_protocols_reply_wipe xcb_get_wm_protocols_reply_wipe +#define XCB_ICCCM_WM_STATE_NORMAL XCB_WM_STATE_NORMAL +#define XCB_ICCCM_WM_STATE_WITHDRAWN XCB_WM_STATE_WITHDRAWN +#define xcb_icccm_get_wm_size_hints_from_reply xcb_get_wm_size_hints_from_reply +#define xcb_icccm_get_wm_normal_hints_reply xcb_get_wm_normal_hints_reply +#define xcb_icccm_get_wm_normal_hints_unchecked xcb_get_wm_normal_hints_unchecked +#define XCB_ICCCM_SIZE_HINT_P_MIN_SIZE XCB_SIZE_HINT_P_MIN_SIZE +#define XCB_ICCCM_SIZE_HINT_P_RESIZE_INC XCB_SIZE_HINT_P_RESIZE_INC +#define XCB_ICCCM_SIZE_HINT_BASE_SIZE XCB_SIZE_HINT_BASE_SIZE +#define XCB_ICCCM_SIZE_HINT_P_ASPECT XCB_SIZE_HINT_P_ASPECT +#define xcb_icccm_wm_hints_t xcb_wm_hints_t +#define xcb_icccm_get_wm_hints_from_reply xcb_get_wm_hints_from_reply +#define xcb_icccm_get_wm_hints_reply xcb_get_wm_hints_reply +#define xcb_icccm_get_wm_hints_unchecked xcb_get_wm_hints_unchecked +#define xcb_icccm_wm_hints_get_urgency xcb_wm_hints_get_urgency +#define xcb_icccm_get_wm_transient_for_from_reply xcb_get_wm_transient_for_from_reply + +#endif diff --git a/src/con.c b/src/con.c index e1359069..8c0a8303 100644 --- a/src/con.c +++ b/src/con.c @@ -504,10 +504,10 @@ void con_toggle_fullscreen(Con *con) { unsigned int num = 0; if (con->fullscreen_mode != CF_NONE) - values[num++] = atoms[_NET_WM_STATE_FULLSCREEN]; + values[num++] = A__NET_WM_STATE_FULLSCREEN; xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, - atoms[_NET_WM_STATE], ATOM, 32, num, values); + A__NET_WM_STATE, A_ATOM, 32, num, values); } /* diff --git a/src/ewmh.c b/src/ewmh.c index 67daa022..7b2cc3e4 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -31,7 +31,7 @@ void ewmh_update_current_desktop() { TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { if (ws == focused_ws) { xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, - atoms[_NET_CURRENT_DESKTOP], CARDINAL, 32, 1, &idx); + A__NET_CURRENT_DESKTOP, A_CARDINAL, 32, 1, &idx); return; } ++idx; @@ -48,7 +48,7 @@ void ewmh_update_current_desktop() { */ void ewmh_update_active_window(xcb_window_t window) { xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, - atoms[_NET_ACTIVE_WINDOW], WINDOW, 32, 1, &window); + A__NET_ACTIVE_WINDOW, A_WINDOW, 32, 1, &window); } /* @@ -104,7 +104,7 @@ void ewmh_update_workarea() { } } xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, - atoms[_NET_WORKAREA], CARDINAL, 32, + A__NET_WORKAREA, A_CARDINAL, 32, num_workspaces * (sizeof(Rect) / sizeof(uint32_t)), workarea); free(workarea); diff --git a/src/floating.c b/src/floating.c index ef500bf6..9d4e1cf8 100644 --- a/src/floating.c +++ b/src/floating.c @@ -374,19 +374,15 @@ void drag_pointer(Con *con, xcb_button_press_event_t *event, xcb_window_t while ((inside_event = xcb_wait_for_event(conn))) { /* We now handle all events we can get using xcb_poll_for_event */ do { - /* Same as get_event_handler in xcb */ - int nr = inside_event->response_type; - if (nr == 0) { - /* An error occured */ - //handle_event(NULL, conn, inside_event); + /* skip x11 errors */ + if (inside_event->response_type == 0) { free(inside_event); continue; } - assert(nr < 256); - nr &= XCB_EVENT_RESPONSE_TYPE_MASK; - assert(nr >= 2); + /* Strip off the highest bit (set if the event is generated) */ + int type = (inside_event->response_type & 0x7F); - switch (nr) { + switch (type) { case XCB_BUTTON_RELEASE: goto done; @@ -398,13 +394,13 @@ void drag_pointer(Con *con, xcb_button_press_event_t *event, xcb_window_t case XCB_UNMAP_NOTIFY: DLOG("Unmap-notify, aborting\n"); - xcb_event_handle(&evenths, inside_event); + handle_event(type, inside_event); goto done; default: DLOG("Passing to original handler\n"); /* Use original handler */ - xcb_event_handle(&evenths, inside_event); + handle_event(type, inside_event); break; } if (last_motion_notify != inside_event) diff --git a/src/handlers.c b/src/handlers.c index 6e6626a9..111d7c24 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -6,14 +6,19 @@ * */ #include +#include -#include #include #include #include "all.h" +int randr_base = -1; + +/* forward declaration for property_notify */ +static int property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom); + /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it, since it’d trigger an infinite loop of switching between the different windows when changing workspaces */ @@ -59,6 +64,143 @@ static bool event_is_ignored(const int sequence) { return false; } +/* + * Takes an xcb_generic_event_t and calls the appropriate handler, based on the + * event type. + * + */ +void handle_event(int type, xcb_generic_event_t *event) { + /* XXX: remove the NULL and conn parameters as soon as this version of libxcb is required */ + + if (randr_base > -1 && + type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { + handle_screen_change(NULL, conn, event); + return; + } + + switch (type) { + case XCB_KEY_PRESS: + handle_key_press(NULL, conn, (xcb_key_press_event_t*)event); + break; + + case XCB_BUTTON_PRESS: + handle_button_press(NULL, conn, (xcb_button_press_event_t*)event); + break; + + case XCB_MAP_REQUEST: + handle_map_request(NULL, conn, (xcb_map_request_event_t*)event); + break; + + case XCB_UNMAP_NOTIFY: + handle_unmap_notify_event(NULL, conn, (xcb_unmap_notify_event_t*)event); + break; + + case XCB_DESTROY_NOTIFY: + handle_destroy_notify_event(NULL, conn, (xcb_destroy_notify_event_t*)event); + break; + + case XCB_EXPOSE: + handle_expose_event(NULL, conn, (xcb_expose_event_t*)event); + break; + + case XCB_MOTION_NOTIFY: + handle_motion_notify(NULL, conn, (xcb_motion_notify_event_t*)event); + break; + + /* Enter window = user moved his mouse over the window */ + case XCB_ENTER_NOTIFY: + handle_enter_notify(NULL, conn, (xcb_enter_notify_event_t*)event); + break; + + /* Client message are sent to the root window. The only interesting + * client message for us is _NET_WM_STATE, we honour + * _NET_WM_STATE_FULLSCREEN */ + case XCB_CLIENT_MESSAGE: + handle_client_message(NULL, conn, (xcb_client_message_event_t*)event); + break; + + /* Configure request = window tried to change size on its own */ + case XCB_CONFIGURE_REQUEST: + handle_configure_request(NULL, conn, (xcb_configure_request_event_t*)event); + break; + + /* Mapping notify = keyboard mapping changed (Xmodmap), re-grab bindings */ + case XCB_MAPPING_NOTIFY: + handle_mapping_notify(NULL, conn, (xcb_mapping_notify_event_t*)event); + break; + + case XCB_PROPERTY_NOTIFY: + DLOG("Property notify\n"); + xcb_property_notify_event_t *e = (xcb_property_notify_event_t*)event; + property_notify(e->state, e->window, e->atom); + break; + + default: + DLOG("Unhandled event of type %d\n", type); + break; + } +} + +typedef int (*cb_property_handler_t)(void *data, xcb_connection_t *c, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property); + +struct property_handler_t { + xcb_atom_t atom; + uint32_t long_len; + cb_property_handler_t cb; +}; + +static struct property_handler_t property_handlers[] = { + { 0, 128, handle_windowname_change }, + { 0, UINT_MAX, handle_hints }, + { 0, 128, handle_windowname_change_legacy }, + { 0, UINT_MAX, handle_normal_hints }, + { 0, UINT_MAX, handle_clientleader_change }, + { 0, UINT_MAX, handle_transient_for } +}; +#define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t)) + +/* + * Sets the appropriate atoms for the property handlers after the atoms were + * received from X11 + * + */ +void property_handlers_init() { + property_handlers[0].atom = A__NET_WM_NAME; + property_handlers[1].atom = A_WM_HINTS; + property_handlers[2].atom = A_WM_NAME; + property_handlers[3].atom = A_WM_NORMAL_HINTS; + property_handlers[4].atom = A_WM_CLIENT_LEADER; + property_handlers[5].atom = A_WM_TRANSIENT_FOR; +} + +static int property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) { + struct property_handler_t *handler = NULL; + xcb_get_property_reply_t *propr = NULL; + int ret; + + for (int c = 0; c < sizeof(property_handlers) / sizeof(struct property_handler_t); c++) { + if (property_handlers[c].atom != atom) + continue; + + handler = &property_handlers[c]; + break; + } + + if (handler == NULL) { + DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom); + return 0; + } + + if (state != XCB_PROPERTY_DELETE) { + xcb_get_property_cookie_t cookie = xcb_get_property(conn, 0, window, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, handler->long_len); + propr = xcb_get_property_reply(conn, cookie, 0); + } + + ret = handler->cb(NULL, conn, state, window, atom, propr); + FREE(propr); + return ret; +} + /* * There was a key press. We compare this key code with our bindings table and pass * the bound action to parse_command(). @@ -627,10 +769,10 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * */ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message_event_t *event) { LOG("ClientMessage for window 0x%08x\n", event->window); - if (event->type == atoms[_NET_WM_STATE]) { - if (event->format != 32 || event->data.data32[1] != atoms[_NET_WM_STATE_FULLSCREEN]) { + if (event->type == A__NET_WM_STATE) { + if (event->format != 32 || event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN) { DLOG("atom in clientmessage is %d, fullscreen is %d\n", - event->data.data32[1], atoms[_NET_WM_STATE_FULLSCREEN]); + event->data.data32[1], A__NET_WM_STATE_FULLSCREEN); DLOG("not about fullscreen atom\n"); return 0; } @@ -693,17 +835,17 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w /* If the hints were already in this event, use them, if not, request them */ if (reply != NULL) - xcb_get_wm_size_hints_from_reply(&size_hints, reply); + xcb_icccm_get_wm_size_hints_from_reply(&size_hints, reply); else - xcb_get_wm_normal_hints_reply(conn, xcb_get_wm_normal_hints_unchecked(conn, con->window->id), &size_hints, NULL); + xcb_icccm_get_wm_normal_hints_reply(conn, xcb_icccm_get_wm_normal_hints_unchecked(conn, con->window->id), &size_hints, NULL); - if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) { + if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)) { // TODO: Minimum size is not yet implemented DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height); } bool changed = false; - if ((size_hints.flags & XCB_SIZE_HINT_P_RESIZE_INC)) { + if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) { if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) if (con->width_increment != size_hints.width_inc) { con->width_increment = size_hints.width_inc; @@ -724,10 +866,10 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w /* base_width/height are the desired size of the window. We check if either the program-specified size or the program-specified min-size is available */ - if (size_hints.flags & XCB_SIZE_HINT_BASE_SIZE) { + if (size_hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) { base_width = size_hints.base_width; base_height = size_hints.base_height; - } else if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) { + } else if (size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) { /* TODO: is this right? icccm says not */ base_width = size_hints.min_width; base_height = size_hints.min_height; @@ -742,7 +884,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w } /* If no aspect ratio was set or if it was invalid, we ignore the hints */ - if (!(size_hints.flags & XCB_SIZE_HINT_P_ASPECT) || + if (!(size_hints.flags & XCB_ICCCM_SIZE_HINT_P_ASPECT) || (size_hints.min_aspect_num <= 0) || (size_hints.min_aspect_den <= 0)) { goto render_and_return; @@ -788,13 +930,13 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t return 1; } - xcb_wm_hints_t hints; + xcb_icccm_wm_hints_t hints; if (reply != NULL) { - if (!xcb_get_wm_hints_from_reply(&hints, reply)) + if (!xcb_icccm_get_wm_hints_from_reply(&hints, reply)) return 1; } else { - if (!xcb_get_wm_hints_reply(conn, xcb_get_wm_hints_unchecked(conn, con->window->id), &hints, NULL)) + if (!xcb_icccm_get_wm_hints_reply(conn, xcb_icccm_get_wm_hints_unchecked(conn, con->window->id), &hints, NULL)) return 1; } @@ -804,7 +946,7 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t } /* Update the flag on the client directly */ - con->urgent = (xcb_wm_hints_get_urgency(&hints) != 0); + con->urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0); //CLIENT_LOG(con); LOG("Urgency flag changed to %d\n", con->urgent); @@ -841,7 +983,7 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_ if (prop == NULL) { prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, - false, window, WM_TRANSIENT_FOR, WINDOW, 0, 32), NULL); + false, window, A_WM_TRANSIENT_FOR, A_WINDOW, 0, 32), NULL); if (prop == NULL) return 1; } @@ -872,7 +1014,7 @@ int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state if (prop == NULL) { prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, - false, window, WM_CLIENT_LEADER, WINDOW, 0, 32), NULL); + false, window, A_WM_CLIENT_LEADER, A_WINDOW, 0, 32), NULL); if (prop == NULL) return 1; } diff --git a/src/main.c b/src/main.c index 002eb6a5..16df027b 100644 --- a/src/main.c +++ b/src/main.c @@ -15,9 +15,6 @@ extern Con *focused; char **start_argv; xcb_connection_t *conn; -xcb_event_handlers_t evenths; -xcb_property_handlers_t prophs; -xcb_atom_t atoms[NUM_ATOMS]; xcb_window_t root; uint8_t root_depth; @@ -66,8 +63,17 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { xcb_generic_event_t *event; while ((event = xcb_poll_for_event(conn)) != NULL) { - xcb_event_handle(&evenths, event); - free(event); + if (event->response_type == 0) { + ELOG("X11 Error received! sequence %x\n", event->sequence); + continue; + } + + /* Strip off the highest bit (set if the event is generated) */ + int type = (event->response_type & 0x7F); + + handle_event(type, event); + + free(event); } } @@ -149,7 +155,6 @@ int main(int argc, char *argv[]) { bool only_check_config = false; bool force_xinerama = false; bool disable_signalhandler = false; - xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS]; static struct option long_options[] = { {"no-autostart", no_argument, 0, 'a'}, {"config", required_argument, 0, 'c'}, @@ -272,30 +277,10 @@ int main(int argc, char *argv[]) { check_error(conn, cookie, "Another window manager seems to be running"); /* Place requests for the atoms we need as soon as possible */ - #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name); - - REQUEST_ATOM(_NET_SUPPORTED); - REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN); - REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK); - REQUEST_ATOM(_NET_WM_NAME); - REQUEST_ATOM(_NET_WM_STATE); - REQUEST_ATOM(_NET_WM_WINDOW_TYPE); - REQUEST_ATOM(_NET_WM_DESKTOP); - REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK); - REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DIALOG); - REQUEST_ATOM(_NET_WM_WINDOW_TYPE_UTILITY); - REQUEST_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR); - REQUEST_ATOM(_NET_WM_WINDOW_TYPE_SPLASH); - REQUEST_ATOM(_NET_WM_STRUT_PARTIAL); - REQUEST_ATOM(WM_PROTOCOLS); - REQUEST_ATOM(WM_DELETE_WINDOW); - REQUEST_ATOM(UTF8_STRING); - REQUEST_ATOM(WM_STATE); - REQUEST_ATOM(WM_CLIENT_LEADER); - REQUEST_ATOM(_NET_CURRENT_DESKTOP); - REQUEST_ATOM(_NET_ACTIVE_WINDOW); - REQUEST_ATOM(_NET_WORKAREA); - REQUEST_ATOM(WM_TAKE_FOCUS); + #define xmacro(atom) \ + xcb_intern_atom_cookie_t atom ## _cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom); + #include "atoms.xmacro" + #undef xmacro /* Initialize the Xlib connection */ xlibdpy = xkbdpy = XOpenDisplay(NULL); @@ -338,95 +323,32 @@ int main(int argc, char *argv[]) { } } - memset(&evenths, 0, sizeof(xcb_event_handlers_t)); - memset(&prophs, 0, sizeof(xcb_property_handlers_t)); - - xcb_event_handlers_init(conn, &evenths); - xcb_property_handlers_init(&prophs, &evenths); - xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL); - - xcb_event_set_button_press_handler(&evenths, handle_button_press, NULL); - - xcb_event_set_map_request_handler(&evenths, handle_map_request, NULL); - - xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL); - xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL); - - xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL); - - xcb_event_set_motion_notify_handler(&evenths, handle_motion_notify, NULL); - - /* Enter window = user moved his mouse over the window */ - xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL); - - /* Client message are sent to the root window. The only interesting client message - for us is _NET_WM_STATE, we honour _NET_WM_STATE_FULLSCREEN */ - xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL); - - /* Configure request = window tried to change size on its own */ - xcb_event_set_configure_request_handler(&evenths, handle_configure_request, NULL); - /* Setup NetWM atoms */ - #define GET_ATOM(name) \ + #define xmacro(name) \ do { \ - xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \ + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name ## _cookie, NULL); \ if (!reply) { \ ELOG("Could not get atom " #name "\n"); \ exit(-1); \ } \ - atoms[name] = reply->atom; \ + A_ ## name = reply->atom; \ free(reply); \ - } while (0) + } while (0); + #include "atoms.xmacro" + #undef xmacro - GET_ATOM(_NET_SUPPORTED); - GET_ATOM(_NET_WM_STATE_FULLSCREEN); - GET_ATOM(_NET_SUPPORTING_WM_CHECK); - GET_ATOM(_NET_WM_NAME); - GET_ATOM(_NET_WM_STATE); - GET_ATOM(_NET_WM_WINDOW_TYPE); - GET_ATOM(_NET_WM_DESKTOP); - GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK); - GET_ATOM(_NET_WM_WINDOW_TYPE_DIALOG); - GET_ATOM(_NET_WM_WINDOW_TYPE_UTILITY); - GET_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR); - GET_ATOM(_NET_WM_WINDOW_TYPE_SPLASH); - GET_ATOM(_NET_WM_STRUT_PARTIAL); - GET_ATOM(WM_PROTOCOLS); - GET_ATOM(WM_DELETE_WINDOW); - GET_ATOM(UTF8_STRING); - GET_ATOM(WM_STATE); - GET_ATOM(WM_CLIENT_LEADER); - GET_ATOM(_NET_CURRENT_DESKTOP); - GET_ATOM(_NET_ACTIVE_WINDOW); - GET_ATOM(_NET_WORKAREA); - GET_ATOM(WM_TAKE_FOCUS); - - /* Watch _NET_WM_NAME (title of the window encoded in UTF-8) */ - xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL); - - /* Watch WM_HINTS (contains the urgent property) */ - xcb_property_set_handler(&prophs, WM_HINTS, UINT_MAX, handle_hints, NULL); - - /* Watch WM_NAME (title of the window encoded in COMPOUND_TEXT) */ - xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL); - - /* Watch WM_NORMAL_HINTS (aspect ratio, size increments, …) */ - xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL); - - /* Watch WM_CLIENT_LEADER (= logical parent window for toolbars etc.) */ - xcb_property_set_handler(&prophs, atoms[WM_CLIENT_LEADER], UINT_MAX, handle_clientleader_change, NULL); - - /* Watch WM_TRANSIENT_FOR property (to which client this popup window belongs) */ - xcb_property_set_handler(&prophs, WM_TRANSIENT_FOR, UINT_MAX, handle_transient_for, NULL); - - /* Mapping notify = keyboard mapping changed (Xmodmap), re-grab bindings */ - xcb_event_set_mapping_notify_handler(&evenths, handle_mapping_notify, NULL); + property_handlers_init(); /* Set up the atoms we support */ - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 7, atoms); + xcb_atom_t supported_atoms[] = { +#define xmacro(atom) A_ ## atom, +#include "atoms.xmacro" +#undef xmacro + }; + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, A_ATOM, 32, 7, supported_atoms); /* Set up the window manager’s name */ - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root); - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3"); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTING_WM_CHECK, A_WINDOW, 32, 1, &root); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3"); keysyms = xcb_key_symbols_alloc(conn); @@ -446,17 +368,11 @@ int main(int argc, char *argv[]) { if (needs_tree_init) tree_init(); - int randr_base; if (force_xinerama) { xinerama_init(); } else { DLOG("Checking for XRandR...\n"); randr_init(&randr_base); - - xcb_event_set_handler(&evenths, - randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY, - handle_screen_change, - NULL); } tree_render(); diff --git a/src/manage.c b/src/manage.c index c4d6172b..4821e28f 100644 --- a/src/manage.c +++ b/src/manage.c @@ -85,15 +85,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki utf8_title_cookie, title_cookie, class_cookie, leader_cookie, transient_cookie; - wm_type_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX); - strut_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX); - state_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STATE], UINT32_MAX); - utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_NAME], 128); - leader_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[WM_CLIENT_LEADER], UINT32_MAX); - transient_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_TRANSIENT_FOR, UINT32_MAX); - title_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_NAME, 128); - class_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_CLASS, 128); - /* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */ geomc = xcb_get_geometry(conn, d); @@ -126,6 +117,18 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki goto out; } +#define GET_PROPERTY(atom, len) xcb_get_property_unchecked(conn, false, window, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, len) + + wm_type_cookie = GET_PROPERTY(A__NET_WM_WINDOW_TYPE, UINT32_MAX); + strut_cookie = GET_PROPERTY(A__NET_WM_STRUT_PARTIAL, UINT32_MAX); + state_cookie = GET_PROPERTY(A__NET_WM_STATE, UINT32_MAX); + utf8_title_cookie = GET_PROPERTY(A__NET_WM_NAME, 128); + leader_cookie = GET_PROPERTY(A_WM_CLIENT_LEADER, UINT32_MAX); + transient_cookie = GET_PROPERTY(A_WM_TRANSIENT_FOR, UINT32_MAX); + title_cookie = GET_PROPERTY(A_WM_NAME, 128); + class_cookie = GET_PROPERTY(A_WM_CLASS, 128); + /* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */ + DLOG("reparenting!\n"); uint32_t mask = 0; uint32_t values[1]; @@ -157,7 +160,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki Con *search_at = croot; xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, wm_type_cookie, NULL); - if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DOCK])) { + if (xcb_reply_contains_atom(reply, A__NET_WM_WINDOW_TYPE_DOCK)) { LOG("This window is of type dock\n"); Output *output = get_output_containing(geom->x, geom->y); if (output != NULL) { @@ -254,10 +257,10 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* set floating if necessary */ bool want_floating = false; - if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DIALOG]) || - xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_UTILITY]) || - xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_TOOLBAR]) || - xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_SPLASH])) { + if (xcb_reply_contains_atom(reply, A__NET_WM_WINDOW_TYPE_DIALOG) || + xcb_reply_contains_atom(reply, A__NET_WM_WINDOW_TYPE_UTILITY) || + xcb_reply_contains_atom(reply, A__NET_WM_WINDOW_TYPE_TOOLBAR) || + xcb_reply_contains_atom(reply, A__NET_WM_WINDOW_TYPE_SPLASH)) { LOG("This window is a dialog window, setting floating\n"); want_floating = true; } @@ -308,7 +311,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki xcb_change_window_attributes(conn, window, mask, values); reply = xcb_get_property_reply(conn, state_cookie, NULL); - if (xcb_reply_contains_atom(reply, atoms[_NET_WM_STATE_FULLSCREEN])) + if (xcb_reply_contains_atom(reply, A__NET_WM_STATE_FULLSCREEN)) con_toggle_fullscreen(nc); /* Put the client inside the save set. Upon termination (whether killed or diff --git a/src/sighandler.c b/src/sighandler.c index d6128b5b..1d3e4ab7 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -26,12 +26,7 @@ #include -#include "i3.h" -#include "util.h" -#include "xcb.h" -#include "log.h" -#include "config.h" -#include "randr.h" +#include "all.h" static xcb_gcontext_t pixmap_gc; static xcb_pixmap_t pixmap; @@ -159,12 +154,6 @@ void handle_signal(int sig, siginfo_t *info, void *data) { sigaction(sig, &action, NULL); raised_signal = sig; - /* setup event handler for key presses */ - xcb_event_handlers_t sig_evenths; - memset(&sig_evenths, 0, sizeof(xcb_event_handlers_t)); - xcb_event_handlers_init(conn, &sig_evenths); - xcb_event_set_key_press_handler(&sig_evenths, sig_handle_key_press, NULL); - /* width and height of the popup window, so that the text fits in */ int crash_text_num = sizeof(crash_text) / sizeof(char*); int height = 13 + (crash_text_num * config.font.height); @@ -203,7 +192,16 @@ void handle_signal(int sig, siginfo_t *info, void *data) { xcb_flush(conn); } - xcb_event_wait_for_event_loop(&sig_evenths); + xcb_generic_event_t *event; + /* Yay, more own eventhandlers… */ + while ((event = xcb_wait_for_event(conn))) { + /* Strip off the highest bit (set if the event is generated) */ + int type = (event->response_type & 0x7F); + if (type == XCB_KEY_PRESS) { + sig_handle_key_press(NULL, conn, (xcb_key_press_event_t*)event); + } + free(event); + } } /* diff --git a/src/window.c b/src/window.c index 11be7c6f..ffc73cd0 100644 --- a/src/window.c +++ b/src/window.c @@ -136,7 +136,7 @@ void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop) } xcb_window_t transient_for; - if (!xcb_get_wm_transient_for_from_reply(&transient_for, prop)) + if (!xcb_icccm_get_wm_transient_for_from_reply(&transient_for, prop)) return; DLOG("Transient for changed to %08x\n", transient_for); diff --git a/src/x.c b/src/x.c index 7a3385e5..374fd162 100644 --- a/src/x.c +++ b/src/x.c @@ -180,11 +180,11 @@ void x_con_kill(Con *con) { */ static bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom) { xcb_get_property_cookie_t cookie; - xcb_get_wm_protocols_reply_t protocols; + xcb_icccm_get_wm_protocols_reply_t protocols; bool result = false; - cookie = xcb_get_wm_protocols_unchecked(conn, window, atoms[WM_PROTOCOLS]); - if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1) + cookie = xcb_icccm_get_wm_protocols_unchecked(conn, window, A_WM_PROTOCOLS); + if (xcb_icccm_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1) return false; /* Check if the client’s protocols have the requested atom set */ @@ -192,7 +192,7 @@ static bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom) { if (protocols.atoms[i] == atom) result = true; - xcb_get_wm_protocols_reply_wipe(&protocols); + xcb_icccm_get_wm_protocols_reply_wipe(&protocols); return result; } @@ -203,7 +203,7 @@ static bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom) { */ void x_window_kill(xcb_window_t window) { /* if this window does not support WM_DELETE_WINDOW, we kill it the hard way */ - if (!window_supports_protocol(window, atoms[WM_DELETE_WINDOW])) { + if (!window_supports_protocol(window, A_WM_DELETE_WINDOW)) { LOG("Killing window the hard way\n"); xcb_kill_client(conn, window); return; @@ -215,9 +215,9 @@ void x_window_kill(xcb_window_t window) { ev.response_type = XCB_CLIENT_MESSAGE; ev.window = window; - ev.type = atoms[WM_PROTOCOLS]; + ev.type = A_WM_PROTOCOLS; ev.format = 32; - ev.data.data32[0] = atoms[WM_DELETE_WINDOW]; + ev.data.data32[0] = A_WM_DELETE_WINDOW; ev.data.data32[1] = XCB_CURRENT_TIME; LOG("Sending WM_DELETE to the client\n"); @@ -389,7 +389,7 @@ static void x_push_node(Con *con) { DLOG("pushing name %s for con %p\n", state->name, con); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->frame, - WM_NAME, STRING, 8, strlen(state->name), state->name); + A_WM_NAME, A_STRING, 8, strlen(state->name), state->name); FREE(state->name); } @@ -468,9 +468,9 @@ static void x_push_node(Con *con) { if (con->window != NULL) { /* Set WM_STATE_NORMAL because GTK applications don’t want to * drag & drop if we don’t. Also, xprop(1) needs it. */ - long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE }; + long data[] = { XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE }; xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, - atoms[WM_STATE], atoms[WM_STATE], 32, 2, data); + A_WM_STATE, A_WM_STATE, 32, 2, data); } if (!state->child_mapped && con->window != NULL) { @@ -528,9 +528,9 @@ static void x_push_node_unmaps(Con *con) { xcb_void_cookie_t cookie; if (con->window != NULL) { /* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */ - long data[] = { XCB_WM_STATE_WITHDRAWN, XCB_NONE }; + long data[] = { XCB_ICCCM_WM_STATE_WITHDRAWN, XCB_NONE }; xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, - atoms[WM_STATE], atoms[WM_STATE], 32, 2, data); + A_WM_STATE, A_WM_STATE, 32, 2, data); } cookie = xcb_unmap_window(conn, con->frame); @@ -624,9 +624,9 @@ void x_push_changes(Con *con) { ev.response_type = XCB_CLIENT_MESSAGE; ev.window = to_focus; - ev.type = atoms[WM_PROTOCOLS]; + ev.type = A_WM_PROTOCOLS; ev.format = 32; - ev.data.data32[0] = atoms[WM_TAKE_FOCUS]; + ev.data.data32[0] = A_WM_TAKE_FOCUS; ev.data.data32[1] = XCB_CURRENT_TIME; DLOG("Sending WM_TAKE_FOCUS to the client\n"); From 82e286ed7c20f269e1a225c2ce082cf6a33a140b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 18 Mar 2011 17:07:56 +0100 Subject: [PATCH 534/867] Only send WM_TAKE_FOCUS when the client supports it in the protocols atom Fixes opening xterm, for example --- include/data.h | 3 +++ include/x.h | 6 ++++++ include/xcb.h | 6 ++++++ src/manage.c | 3 +++ src/x.c | 19 +++++-------------- src/xcb.c | 20 ++++++++++++++++++++ 6 files changed, 43 insertions(+), 14 deletions(-) diff --git a/include/data.h b/include/data.h index 8beb91ab..088561c4 100644 --- a/include/data.h +++ b/include/data.h @@ -246,6 +246,9 @@ struct Window { /** Whether the application used _NET_WM_NAME */ bool uses_net_wm_name; + /** Whether the application needs to receive WM_TAKE_FOCUS */ + bool needs_take_focus; + /** Whether the window says it is a dock window */ enum { W_NODOCK = 0, W_DOCK_TOP = 1, W_DOCK_BOTTOM = 2 } dock; diff --git a/include/x.h b/include/x.h index 43b0814b..da4147cc 100644 --- a/include/x.h +++ b/include/x.h @@ -39,6 +39,12 @@ void x_reinit(Con *con); */ void x_con_kill(Con *con); +/** + * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW) + * + */ +bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom); + /** * Kills the given X11 window using WM_DELETE_WINDOW (if supported). * diff --git a/include/xcb.h b/include/xcb.h index bec06b06..4a09766f 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -116,6 +116,12 @@ void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window); */ void fake_absolute_configure_notify(Con *con); +/** + * Sends the WM_TAKE_FOCUS ClientMessage to the given window + * + */ +void send_take_focus(xcb_window_t window); + /** * Finds out which modifier mask is the one for numlock, as the user may * change this. diff --git a/src/manage.c b/src/manage.c index 4821e28f..ac199468 100644 --- a/src/manage.c +++ b/src/manage.c @@ -156,6 +156,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL)); window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL)); + /* check if the window needs WM_TAKE_FOCUS */ + cwindow->needs_take_focus = window_supports_protocol(cwindow->id, A_WM_TAKE_FOCUS); + /* Where to start searching for a container that swallows the new one? */ Con *search_at = croot; diff --git a/src/x.c b/src/x.c index 374fd162..21e9b14e 100644 --- a/src/x.c +++ b/src/x.c @@ -178,7 +178,7 @@ void x_con_kill(Con *con) { * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW) * */ -static bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom) { +bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom) { xcb_get_property_cookie_t cookie; xcb_icccm_get_wm_protocols_reply_t protocols; bool result = false; @@ -618,19 +618,10 @@ void x_push_changes(Con *con) { xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME); /* TODO: check if that client acccepts WM_TAKE_FOCUS at all */ - xcb_client_message_event_t ev; - - memset(&ev, 0, sizeof(xcb_client_message_event_t)); - - ev.response_type = XCB_CLIENT_MESSAGE; - ev.window = to_focus; - ev.type = A_WM_PROTOCOLS; - ev.format = 32; - ev.data.data32[0] = A_WM_TAKE_FOCUS; - ev.data.data32[1] = XCB_CURRENT_TIME; - - DLOG("Sending WM_TAKE_FOCUS to the client\n"); - xcb_send_event(conn, false, to_focus, XCB_EVENT_MASK_NO_EVENT, (char*)&ev); + if (focused->window != NULL && + focused->window->needs_take_focus) { + send_take_focus(to_focus); + } ewmh_update_active_window(to_focus); focused_id = to_focus; diff --git a/src/xcb.c b/src/xcb.c index aaad590d..7382cc61 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -206,6 +206,26 @@ void fake_absolute_configure_notify(Con *con) { fake_configure_notify(conn, absolute, con->window->id); } +/* + * Sends the WM_TAKE_FOCUS ClientMessage to the given window + * + */ +void send_take_focus(xcb_window_t window) { + xcb_client_message_event_t ev; + + memset(&ev, 0, sizeof(xcb_client_message_event_t)); + + ev.response_type = XCB_CLIENT_MESSAGE; + ev.window = window; + ev.type = A_WM_PROTOCOLS; + ev.format = 32; + ev.data.data32[0] = A_WM_TAKE_FOCUS; + ev.data.data32[1] = XCB_CURRENT_TIME; + + DLOG("Sending WM_TAKE_FOCUS to the client\n"); + xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char*)&ev); +} + /* * Finds out which modifier mask is the one for numlock, as the user may change this. * From 3282bb406944118329dcc308bb4c7da17c6dfdd0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 18 Mar 2011 17:08:48 +0100 Subject: [PATCH 535/867] remove obsolete comment --- src/x.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/x.c b/src/x.c index 21e9b14e..b164d022 100644 --- a/src/x.c +++ b/src/x.c @@ -617,7 +617,6 @@ void x_push_changes(Con *con) { DLOG("Updating focus (focused: %p / %s)\n", focused, focused->name); xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME); - /* TODO: check if that client acccepts WM_TAKE_FOCUS at all */ if (focused->window != NULL && focused->window->needs_take_focus) { send_take_focus(to_focus); From cd0b7282e092dc7d6802dd3821b2b361ebb0bcd9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 18 Mar 2011 17:32:37 +0100 Subject: [PATCH 536/867] i3-input: remove dependency on xcb-event --- i3-input/main.c | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/i3-input/main.c b/i3-input/main.c index 6efbbdec..ea1e6132 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -306,14 +306,6 @@ int main(int argc, char *argv[]) { xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); root = root_screen->root; - /* Set up event handlers for key press and key release */ - xcb_event_handlers_t evenths; - memset(&evenths, 0, sizeof(xcb_event_handlers_t)); - xcb_event_handlers_init(conn, &evenths); - xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL); - xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL); - xcb_event_set_expose_handler(&evenths, handle_expose, NULL); - modeswitchmask = get_mod_mask(conn, XK_Mode_switch); numlockmask = get_mod_mask(conn, XK_Num_Lock); symbols = xcb_key_symbols_alloc(conn); @@ -359,7 +351,32 @@ int main(int argc, char *argv[]) { xcb_flush(conn); - xcb_event_wait_for_event_loop(&evenths); + xcb_generic_event_t *event; + while ((event = xcb_wait_for_event(conn)) != NULL) { + if (event->response_type == 0) { + fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence); + continue; + } + + /* Strip off the highest bit (set if the event is generated) */ + int type = (event->response_type & 0x7F); + + switch (type) { + case XCB_KEY_PRESS: + handle_key_press(NULL, conn, (xcb_key_press_event_t*)event); + break; + + case XCB_KEY_RELEASE: + handle_key_release(NULL, conn, (xcb_key_release_event_t*)event); + break; + + case XCB_EXPOSE: + handle_expose(NULL, conn, (xcb_expose_event_t*)event); + break; + } + + free(event); + } return 0; } From 696d3cb88f0536c1ab48963e4f583808bbebf95e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 18 Mar 2011 18:07:04 +0100 Subject: [PATCH 537/867] remove obsolete xcb-property line (Thanks SardemFF7) --- common.mk | 1 - 1 file changed, 1 deletion(-) diff --git a/common.mk b/common.mk index db887cac..445ba44e 100644 --- a/common.mk +++ b/common.mk @@ -49,7 +49,6 @@ CFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" LDFLAGS += -lm LDFLAGS += $(call ldflags_for_lib, xcb-event, xcb-event) -LDFLAGS += $(call ldflags_for_lib, xcb-property, xcb-property) LDFLAGS += $(call ldflags_for_lib, xcb-keysyms, xcb-keysyms) ifeq ($(shell pkg-config --exists xcb-util || echo 1),1) LDFLAGS += $(call ldflags_for_lib, xcb-atom, xcb-atom) From a2e87f69ac52ac5a0b65171fb4251f05309f172a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 18 Mar 2011 18:11:02 +0100 Subject: [PATCH 538/867] remove hard-coded paths since we now use pkg-config for all the dependencies --- common.mk | 2 -- 1 file changed, 2 deletions(-) diff --git a/common.mk b/common.mk index 445ba44e..182c12e5 100644 --- a/common.mk +++ b/common.mk @@ -27,7 +27,6 @@ CFLAGS += -Wall # We don’t want unused-parameter because of the use of many callbacks CFLAGS += -Wunused-value CFLAGS += -Iinclude -CFLAGS += -I/usr/local/include CFLAGS += $(call cflags_for_lib, xcb-keysyms) ifeq ($(shell pkg-config --exists xcb-util || echo 1),1) CFLAGS += -DXCB_COMPAT @@ -64,7 +63,6 @@ LDFLAGS += $(call ldflags_for_lib, xcursor, Xcursor) LDFLAGS += $(call ldflags_for_lib, x11, X11) LDFLAGS += $(call ldflags_for_lib, yajl, yajl) LDFLAGS += $(call ldflags_for_lib, libev, ev) -LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib ifeq ($(UNAME),NetBSD) # We need -idirafter instead of -I to prefer the system’s iconv over GNU libiconv From fd7e4b08f37ce4195186c4d5ff8a7071bb64beca Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 18 Mar 2011 20:47:59 +0100 Subject: [PATCH 539/867] rendering: correctly draw background rect (Thanks phnom) Fixes #347 --- src/x.c | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/x.c b/src/x.c index b164d022..b63e99e6 100644 --- a/src/x.c +++ b/src/x.c @@ -258,13 +258,37 @@ void x_draw_decoration(Con *con) { Con *parent = con->parent; int border_style = con_border_style(con); - /* 2: draw a rectangle in border color around the client */ + /* 2: draw the client.background, but only for the parts around the client_rect */ + Rect *r = &(con->rect); + Rect *w = &(con->window_rect); + + xcb_rectangle_t background[] = { + /* top area */ + { 0, con->deco_rect.height, r->width, w->y }, + /* bottom area */ + { 0, (w->y + w->height), r->width, r->height - (w->y + w->height) }, + /* right area */ + { w->width, con->deco_rect.height, r->width - (w->x + w->width), r->height } + }; +#if 0 + for (int i = 0; i < 3; i++) + DLOG("rect is (%d, %d) with %d x %d\n", + background[i].x, + background[i].y, + background[i].width, + background[i].height + ); +#endif + + xcb_change_gc_single(conn, con->gc, XCB_GC_FOREGROUND, config.client.background); + xcb_poly_fill_rectangle(conn, con->frame, con->gc, sizeof(background) / sizeof(xcb_rectangle_t), background); + + /* 3: draw a rectangle in border color around the client */ if (border_style != BS_NONE && con_is_leaf(con)) { Rect br = con_border_style_rect(con); - Rect *r = &(con->rect); #if 0 DLOG("con->rect spans %d x %d\n", con->rect.width, con->rect.height); - DLOG("border_rect spans (%d, %d) with %d x %d\n", border_rect.x, border_rect.y, border_rect.width, border_rect.height); + DLOG("border_rect spans (%d, %d) with %d x %d\n", br.x, br.y, br.width, br.height); DLOG("window_rect spans (%d, %d) with %d x %d\n", con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height); #endif @@ -293,12 +317,12 @@ void x_draw_decoration(Con *con) { return; } - /* 3: paint the bar */ + /* 4: paint the bar */ xcb_change_gc_single(conn, parent->gc, XCB_GC_FOREGROUND, color->background); xcb_rectangle_t drect = { con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height }; xcb_poly_fill_rectangle(conn, parent->frame, parent->gc, 1, &drect); - /* 4: draw the two lines in border color */ + /* 5: draw the two lines in border color */ xcb_draw_line(conn, parent->frame, parent->gc, color->border, con->deco_rect.x, /* x */ con->deco_rect.y, /* y */ @@ -310,7 +334,7 @@ void x_draw_decoration(Con *con) { con->deco_rect.x + con->deco_rect.width, /* to_x */ con->deco_rect.y + con->deco_rect.height - 1); /* to_y */ - /* 5: draw the title */ + /* 6: draw the title */ uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; uint32_t values[] = { color->text, color->background, config.font.id }; xcb_change_gc(conn, parent->gc, mask, values); From 9723366eff1f9123cb9d78fe117edcc436ced7af Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Mar 2011 02:21:46 +0100 Subject: [PATCH 540/867] tests: add testcase for WM_TAKE_FOCUS requires the very latest checkout of X11::XCB --- testcases/t/58-wm_take_focus.t | 111 +++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 testcases/t/58-wm_take_focus.t diff --git a/testcases/t/58-wm_take_focus.t b/testcases/t/58-wm_take_focus.t new file mode 100644 index 00000000..a7552c24 --- /dev/null +++ b/testcases/t/58-wm_take_focus.t @@ -0,0 +1,111 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Tests if the WM_TAKE_FOCUS protocol is correctly handled by i3 +# +use X11::XCB qw(:all); +use EV; +use AnyEvent; +use i3test; +use v5.10; + +BEGIN { + use_ok('X11::XCB::Window'); + use_ok('X11::XCB::Event::Generic'); + use_ok('X11::XCB::Event::MapNotify'); + use_ok('X11::XCB::Event::ClientMessage'); +} + +my $x = X11::XCB::Connection->new; +my $i3 = i3("/tmp/nestedcons"); + +subtest 'Window without WM_TAKE_FOCUS', sub { + + my $tmp = fresh_workspace; + + my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#00ff00', + event_mask => [ 'structure_notify' ], + ); + + $window->name('Window 1'); + $window->map; + + my $cv = AE::cv; + + my $prep = EV::prepare sub { + $x->flush; + }; + + my $check = EV::check sub { + while (defined(my $event = $x->poll_for_event)) { + if ($event->response_type == 161) { + # clientmessage + $cv->send(0); + } + } + }; + + my $w = EV::io $x->get_file_descriptor, EV::READ, sub { + # do nothing, we only need this watcher so that EV picks up the events + }; + + # Trigger timeout after 1 second + my $t = AE::timer 1, 0, sub { + $cv->send(1); + }; + + my $result = $cv->recv; + ok($result, 'cv result'); +}; + +subtest 'Window with WM_TAKE_FOCUS', sub { + + my $tmp = fresh_workspace; + + my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#00ff00', + event_mask => [ 'structure_notify' ], + protocols => [ $x->atom(name => 'WM_TAKE_FOCUS') ], + ); + + $window->name('Window 1'); + $window->map; + + my $cv = AE::cv; + + my $prep = EV::prepare sub { + $x->flush; + }; + + my $check = EV::check sub { + while (defined(my $event = $x->poll_for_event)) { + if ($event->response_type == 161) { + $cv->send($event->data); + } + } + }; + + my $w = EV::io $x->get_file_descriptor, EV::READ, sub { + # do nothing, we only need this watcher so that EV picks up the events + }; + + my $t = AE::timer 1, 0, sub { + say "timer!"; + $cv->send(undef); + }; + + my $result = $cv->recv; + ok(defined($result), 'got a ClientMessage'); + if (defined($result)) { + my ($data, $time) = unpack("L2", $result); + is($data, $x->atom(name => 'WM_TAKE_FOCUS')->id, 'first uint32_t contains WM_TAKE_FOCUS atom'); + } +}; + + +done_testing; From 8b9aedd2bfbd30e078ca68176c4646e562e1ac66 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Mar 2011 20:43:06 +0100 Subject: [PATCH 541/867] =?UTF-8?q?Bugfix:=20When=20there=E2=80=99s=20noth?= =?UTF-8?q?ing=20to=20focus,=20focus=20the=20root=20window=20(Thanks=20fer?= =?UTF-8?q?nandotcl,=20ThePub)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/x.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/x.c b/src/x.c index b63e99e6..188be5fb 100644 --- a/src/x.c +++ b/src/x.c @@ -651,6 +651,12 @@ void x_push_changes(Con *con) { } } + if (focused_id == XCB_NONE) { + DLOG("Still no window focused, better set focus to the root window\n"); + xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME); + focused_id = root; + } + xcb_flush(conn); DLOG("\n\n ENDING CHANGES\n\n"); From 65a3259b3cddfaf02666c94946668c3238643da6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Mar 2011 21:20:38 +0100 Subject: [PATCH 542/867] Set the I3_SOCKET_PATH and I3_CONFIG_PATH atoms on the X11 root window --- include/atoms.xmacro | 2 ++ include/config.h | 1 + src/config.c | 2 +- src/main.c | 4 ++++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/atoms.xmacro b/include/atoms.xmacro index 2543ffd2..2114bc3e 100644 --- a/include/atoms.xmacro +++ b/include/atoms.xmacro @@ -29,3 +29,5 @@ xmacro(WM_NAME) xmacro(WM_CLASS) xmacro(STRING) xmacro(CARDINAL) +xmacro(I3_SOCKET_PATH) +xmacro(I3_CONFIG_PATH) diff --git a/include/config.h b/include/config.h index f639df36..57970e28 100644 --- a/include/config.h +++ b/include/config.h @@ -22,6 +22,7 @@ #include "i3.h" typedef struct Config Config; +extern const char *saved_configpath; extern Config config; extern SLIST_HEAD(modes_head, Mode) modes; diff --git a/src/config.c b/src/config.c index 8f8790eb..e4a4cac9 100644 --- a/src/config.c +++ b/src/config.c @@ -19,6 +19,7 @@ #include "all.h" +const char *saved_configpath = NULL; Config config; struct modes_head modes; @@ -232,7 +233,6 @@ static char *get_config_path() { * */ static void parse_configuration(const char *override_configpath) { - static const char *saved_configpath = NULL; if (override_configpath != NULL) { saved_configpath = override_configpath; diff --git a/src/main.c b/src/main.c index 16df027b..e3b2613b 100644 --- a/src/main.c +++ b/src/main.c @@ -350,6 +350,10 @@ int main(int argc, char *argv[]) { xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTING_WM_CHECK, A_WINDOW, 32, 1, &root); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3"); + /* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */ + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_SOCKET_PATH, A_UTF8_STRING, 8, strlen(config.ipc_socket_path), config.ipc_socket_path); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8, strlen(saved_configpath), saved_configpath); + keysyms = xcb_key_symbols_alloc(conn); xcb_get_numlock_mask(conn); From 307a036d5cdff4c191fd9134be153e4c2d03300e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Mar 2011 21:23:55 +0100 Subject: [PATCH 543/867] i3-msg, i3-input: get the I3_SOCKET_PATH atoms if socket path was not specified --- i3-input/main.c | 49 ++++++++++++++++++++++++++++++++++++++++---- i3-msg/main.c | 54 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 95 insertions(+), 8 deletions(-) diff --git a/i3-input/main.c b/i3-input/main.c index ea1e6132..fb2635a2 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -34,6 +35,7 @@ #include "i3-input.h" +static char *socket_path; static int sockfd; static xcb_key_symbols_t *symbols; static int modeswitchmask; @@ -52,6 +54,42 @@ static int prompt_len; static int limit; xcb_window_t root; +/* + * Try to get the socket path from X11 and return NULL if it doesn’t work. + * As i3-msg is a short-running tool, we don’t bother with cleaning up the + * connection and leave it up to the operating system on exit. + * + */ +static char *socket_path_from_x11() { + xcb_connection_t *conn; + int screen; + if ((conn = xcb_connect(NULL, &screen)) == NULL || + xcb_connection_has_error(conn)) + return NULL; + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen); + xcb_window_t root = root_screen->root; + + xcb_intern_atom_cookie_t atom_cookie; + xcb_intern_atom_reply_t *atom_reply; + + atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH"); + atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL); + if (atom_reply == NULL) + return NULL; + + xcb_get_property_cookie_t prop_cookie; + xcb_get_property_reply_t *prop_reply; + prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX); + prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0) + return NULL; + if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply), + (char*)xcb_get_property_value(prop_reply)) == -1) + return NULL; + return socket_path; +} + /* * Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for * rendering it (UCS-2) or sending it to i3 (UTF-8). @@ -242,10 +280,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press } int main(int argc, char *argv[]) { - char *socket_path; - if ((socket_path = getenv("I3SOCK")) == NULL) { - socket_path = "/tmp/i3-ipc.sock"; - } + socket_path = getenv("I3SOCK"); char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; int o, option_index = 0; @@ -293,6 +328,12 @@ int main(int argc, char *argv[]) { } } + if (socket_path == NULL) + socket_path = socket_path_from_x11(); + + if (socket_path == NULL) + socket_path = "/tmp/i3-ipc.sock"; + sockfd = connect_ipc(socket_path); if (prompt != NULL) diff --git a/i3-msg/main.c b/i3-msg/main.c index c6862fae..ee4de078 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -27,9 +27,51 @@ #include #include #include +#include + +#include +#include #include +static char *socket_path; + +/* + * Try to get the socket path from X11 and return NULL if it doesn’t work. + * As i3-msg is a short-running tool, we don’t bother with cleaning up the + * connection and leave it up to the operating system on exit. + * + */ +static char *socket_path_from_x11() { + xcb_connection_t *conn; + int screen; + if ((conn = xcb_connect(NULL, &screen)) == NULL || + xcb_connection_has_error(conn)) + return NULL; + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen); + xcb_window_t root = root_screen->root; + + xcb_intern_atom_cookie_t atom_cookie; + xcb_intern_atom_reply_t *atom_reply; + + atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH"); + atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL); + if (atom_reply == NULL) + return NULL; + + xcb_get_property_cookie_t prop_cookie; + xcb_get_property_reply_t *prop_reply; + prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX); + prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0) + return NULL; + if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply), + (char*)xcb_get_property_value(prop_reply)) == -1) + return NULL; + return socket_path; +} + /* * Formats a message (payload) of the given size and type and sends it to i3 via * the given socket file descriptor. @@ -107,10 +149,7 @@ static void ipc_recv_message(int sockfd, uint32_t message_type, } int main(int argc, char *argv[]) { - char *socket_path; - if ((socket_path = getenv("I3SOCK")) == NULL) { - socket_path = strdup("/tmp/i3-ipc.sock"); - } + socket_path = getenv("I3SOCK"); int o, option_index = 0; int message_type = I3_IPC_MESSAGE_TYPE_COMMAND; char *payload = ""; @@ -158,6 +197,13 @@ int main(int argc, char *argv[]) { } } + if (socket_path == NULL) + socket_path = socket_path_from_x11(); + + /* Fall back to the default socket path */ + if (socket_path == NULL) + socket_path = strdup("/tmp/i3-ipc.sock"); + if (optind < argc) payload = argv[optind]; From 9344b9790c9d50f303c5001428fb47ab79a76e8e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Mar 2011 21:37:27 +0100 Subject: [PATCH 544/867] Bugfix: fix null-pointer dereference when IPC is disabled (Thanks Merovius) --- src/main.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main.c b/src/main.c index e3b2613b..bde10aac 100644 --- a/src/main.c +++ b/src/main.c @@ -351,8 +351,11 @@ int main(int argc, char *argv[]) { xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3"); /* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */ - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_SOCKET_PATH, A_UTF8_STRING, 8, strlen(config.ipc_socket_path), config.ipc_socket_path); - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8, strlen(saved_configpath), saved_configpath); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_SOCKET_PATH, A_UTF8_STRING, 8, + (config.ipc_socket_path != NULL ? strlen(config.ipc_socket_path) : 0), + config.ipc_socket_path); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8, + strlen(saved_configpath), saved_configpath); keysyms = xcb_key_symbols_alloc(conn); From 626c65b0d81683b38b07a4106158a99781ce72e0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Mar 2011 21:50:13 +0100 Subject: [PATCH 545/867] Bugfix: correctly store the config path for using it for I3_CONFIG_PATH later --- include/config.h | 2 +- src/config.c | 6 +++++- src/main.c | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/include/config.h b/include/config.h index 57970e28..79343ab0 100644 --- a/include/config.h +++ b/include/config.h @@ -22,7 +22,7 @@ #include "i3.h" typedef struct Config Config; -extern const char *saved_configpath; +extern const char *current_configpath; extern Config config; extern SLIST_HEAD(modes_head, Mode) modes; diff --git a/src/config.c b/src/config.c index e4a4cac9..20b077b7 100644 --- a/src/config.c +++ b/src/config.c @@ -19,7 +19,7 @@ #include "all.h" -const char *saved_configpath = NULL; +const char *current_configpath = NULL; Config config; struct modes_head modes; @@ -233,19 +233,23 @@ static char *get_config_path() { * */ static void parse_configuration(const char *override_configpath) { + static const char *saved_configpath = NULL; if (override_configpath != NULL) { saved_configpath = override_configpath; + current_configpath = override_configpath; parse_file(override_configpath); return; } else if (saved_configpath != NULL) { + current_configpath = saved_configpath; parse_file(saved_configpath); return; } char *path = get_config_path(); DLOG("Parsing configfile %s\n", path); + current_configpath = path; parse_file(path); free(path); } diff --git a/src/main.c b/src/main.c index bde10aac..309fdaac 100644 --- a/src/main.c +++ b/src/main.c @@ -355,7 +355,7 @@ int main(int argc, char *argv[]) { (config.ipc_socket_path != NULL ? strlen(config.ipc_socket_path) : 0), config.ipc_socket_path); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8, - strlen(saved_configpath), saved_configpath); + strlen(current_configpath), current_configpath); keysyms = xcb_key_symbols_alloc(conn); From b342d387a82ac2b99c54225eeaaad5a311b5f126 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Mar 2011 22:26:15 +0100 Subject: [PATCH 546/867] Handle saved_configpath in get_config_path, fix memleak in current_configpath handling, update atoms after reloading (Thanks fernandotcl) --- include/config.h | 2 +- include/x.h | 6 +++ src/cmdparse.y | 1 + src/config.c | 118 +++++++++++++++++++++++------------------------ src/main.c | 6 +-- src/x.c | 12 +++++ 6 files changed, 79 insertions(+), 66 deletions(-) diff --git a/include/config.h b/include/config.h index 79343ab0..55d597eb 100644 --- a/include/config.h +++ b/include/config.h @@ -22,7 +22,7 @@ #include "i3.h" typedef struct Config Config; -extern const char *current_configpath; +extern char *current_configpath; extern Config config; extern SLIST_HEAD(modes_head, Mode) modes; diff --git a/include/x.h b/include/x.h index da4147cc..6b0bae0e 100644 --- a/include/x.h +++ b/include/x.h @@ -79,4 +79,10 @@ void x_raise_con(Con *con); */ void x_set_name(Con *con, const char *name); +/** + * Sets up i3 specific atoms (I3_SOCKET_PATH and I3_CONFIG_PATH) + * + */ +void x_set_i3_atoms(); + #endif diff --git a/src/cmdparse.y b/src/cmdparse.y index 37605bb8..e15410a4 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -346,6 +346,7 @@ reload: { printf("reloading\n"); load_configuration(conn, NULL, true); + x_set_i3_atoms(); /* Send an IPC event just in case the ws names have changed */ ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}"); } diff --git a/src/config.c b/src/config.c index 20b077b7..572b4ab9 100644 --- a/src/config.c +++ b/src/config.c @@ -19,7 +19,7 @@ #include "all.h" -const char *current_configpath = NULL; +char *current_configpath = NULL; Config config; struct modes_head modes; @@ -170,60 +170,72 @@ void switch_mode(xcb_connection_t *conn, const char *new_mode) { } /* - * Get the path of the first configuration file found. 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) + * 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() { - char *xdg_config_home, *xdg_config_dirs, *config_path; +static char *get_config_path(const char *override_configpath) { + char *xdg_config_home, *xdg_config_dirs, *config_path; - /* 1: check the traditional path under the home directory */ - config_path = resolve_tilde("~/.i3/config"); - if (path_exists(config_path)) - return config_path; + static const char *saved_configpath = NULL; - /* 2: check for $XDG_CONFIG_HOME/i3/config */ - if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) - xdg_config_home = "~/.config"; + if (override_configpath != NULL) { + saved_configpath = override_configpath; + return sstrdup(saved_configpath); + } - xdg_config_home = resolve_tilde(xdg_config_home); - if (asprintf(&config_path, "%s/i3/config", xdg_config_home) == -1) - die("asprintf() failed"); - free(xdg_config_home); + if (saved_configpath != NULL) + return sstrdup(saved_configpath); - if (path_exists(config_path)) - return config_path; - free(config_path); + /* 1: check the traditional path under the home directory */ + config_path = resolve_tilde("~/.i3/config"); + if (path_exists(config_path)) + return config_path; - /* 3: check the traditional path under /etc */ - config_path = SYSCONFDIR "/i3/config"; - if (path_exists(config_path)) - return sstrdup(config_path); + /* 2: check for $XDG_CONFIG_HOME/i3/config */ + if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) + xdg_config_home = "~/.config"; - /* 4: check for $XDG_CONFIG_DIRS/i3/config */ - if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL) - xdg_config_dirs = "/etc/xdg"; + xdg_config_home = resolve_tilde(xdg_config_home); + if (asprintf(&config_path, "%s/i3/config", xdg_config_home) == -1) + die("asprintf() failed"); + free(xdg_config_home); - char *buf = sstrdup(xdg_config_dirs); - char *tok = strtok(buf, ":"); - while (tok != NULL) { - tok = resolve_tilde(tok); - if (asprintf(&config_path, "%s/i3/config", tok) == -1) - die("asprintf() failed"); - free(tok); - if (path_exists(config_path)) { - free(buf); - return config_path; - } - free(config_path); - tok = strtok(NULL, ":"); + 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); + if (asprintf(&config_path, "%s/i3/config", tok) == -1) + die("asprintf() failed"); + free(tok); + if (path_exists(config_path)) { + free(buf); + return config_path; } - free(buf); + 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)"); + 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)"); } /* @@ -233,25 +245,11 @@ static char *get_config_path() { * */ static void parse_configuration(const char *override_configpath) { - static const char *saved_configpath = NULL; - - if (override_configpath != NULL) { - saved_configpath = override_configpath; - current_configpath = override_configpath; - parse_file(override_configpath); - return; - } - else if (saved_configpath != NULL) { - current_configpath = saved_configpath; - parse_file(saved_configpath); - return; - } - - char *path = get_config_path(); + char *path = get_config_path(override_configpath); DLOG("Parsing configfile %s\n", path); + FREE(current_configpath); current_configpath = path; parse_file(path); - free(path); } /* diff --git a/src/main.c b/src/main.c index 309fdaac..2f632dd6 100644 --- a/src/main.c +++ b/src/main.c @@ -351,11 +351,7 @@ int main(int argc, char *argv[]) { xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3"); /* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */ - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_SOCKET_PATH, A_UTF8_STRING, 8, - (config.ipc_socket_path != NULL ? strlen(config.ipc_socket_path) : 0), - config.ipc_socket_path); - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8, - strlen(current_configpath), current_configpath); + x_set_i3_atoms(); keysyms = xcb_key_symbols_alloc(conn); diff --git a/src/x.c b/src/x.c index 188be5fb..8af2e62e 100644 --- a/src/x.c +++ b/src/x.c @@ -703,3 +703,15 @@ void x_set_name(Con *con, const char *name) { FREE(state->name); state->name = sstrdup(name); } + +/* + * Sets up i3 specific atoms (I3_SOCKET_PATH and I3_CONFIG_PATH) + * + */ +void x_set_i3_atoms() { + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_SOCKET_PATH, A_UTF8_STRING, 8, + (config.ipc_socket_path != NULL ? strlen(config.ipc_socket_path) : 0), + config.ipc_socket_path); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8, + strlen(current_configpath), current_configpath); +} From 03ea7cea285126d864f4c34ca205461563275763 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Mar 2011 22:54:53 +0100 Subject: [PATCH 547/867] Bugfix: also invalidate focused_id when the to_focus window is not mapped This fixes a bug where focus might not be set correctly when changing workspaces --- src/x.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/x.c b/src/x.c index 8af2e62e..444b39ac 100644 --- a/src/x.c +++ b/src/x.c @@ -637,6 +637,8 @@ void x_push_changes(Con *con) { if (focused_id != to_focus) { if (!focused->mapped) { DLOG("Not updating focus (to %p / %s), focused window is not mapped.\n", focused, focused->name); + /* Invalidate focused_id to correctly focus new windows with the same ID */ + focused_id = XCB_NONE; } else { DLOG("Updating focus (focused: %p / %s)\n", focused, focused->name); xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME); From e9a9a46795a1b55cffec0793c4879581fa91bad7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Mar 2011 23:19:42 +0100 Subject: [PATCH 548/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20use=20the=20w?= =?UTF-8?q?orkspace=20where=20focus=20is=20for=20deleting=20workspaces=20w?= =?UTF-8?q?hen=20switching=20(Thanks=20mseed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: #353 --- src/workspace.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/workspace.c b/src/workspace.c index 4e93b92e..d09f0277 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -227,13 +227,6 @@ void workspace_show(const char *num) { } assert(old != NULL); - /* Check if the the currently focused con is on the same Output as the - * workspace we chose as 'old'. If not, use the workspace of the currently - * focused con */ - Con *ws = con_get_workspace(focused); - if (ws && ws->parent != old->parent) - old = ws; - /* enable fullscreen for the target workspace. If it happens to be the * same one we are currently on anyways, we can stop here. */ workspace->fullscreen_mode = CF_OUTPUT; From e835888a9ed1365eb151678e4e2f2ec068e06b18 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Mar 2011 01:07:21 +0100 Subject: [PATCH 549/867] Bugfix: Actually re-attach dock clients when outputs get disabled (Thanks phnom) Fixes: #348 --- src/randr.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/randr.c b/src/randr.c index 7033e4c4..0c702db0 100644 --- a/src/randr.c +++ b/src/randr.c @@ -643,6 +643,9 @@ void randr_query_outputs() { if ((first = get_first_output()) == NULL) die("No usable outputs available\n"); + /* TODO: refactor the following code into a nice function. maybe + * use an on_destroy callback which is implement differently for + * different container types (CT_CONTENT vs. CT_DOCKAREA)? */ Con *first_content = output_get_content(first->con); if (output->con != NULL) { @@ -673,6 +676,26 @@ void randr_query_outputs() { con_focus(next); } + /* 3: move the dock clients to the first output */ + Con *child; + TAILQ_FOREACH(child, &(output->con->nodes_head), nodes) { + if (child->type != CT_DOCKAREA) + continue; + DLOG("Handling dock con %p\n", child); + Con *dock; + while (!TAILQ_EMPTY(&(child->nodes_head))) { + dock = TAILQ_FIRST(&(child->nodes_head)); + Con *nc; + Match *match; + nc = con_for_window(first->con, dock->window, &match); + DLOG("Moving dock client %p to nc %p\n", dock, nc); + con_detach(dock); + DLOG("Re-attaching\n"); + con_attach(dock, nc, false); + DLOG("Done\n"); + } + } + DLOG("destroying disappearing con %p\n", output->con); tree_close(output->con, false, true); DLOG("Done. Should be fine now\n"); From b25477b15e18c13cc0a649c39ed781e750a60057 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Mar 2011 14:07:16 +0100 Subject: [PATCH 550/867] Re-implement rendering to pixmaps (double-buffering) and caching decorations --- include/data.h | 55 ++++++++++++++------- include/xcb.h | 9 ---- src/tree.c | 1 + src/window.c | 2 + src/x.c | 131 +++++++++++++++++++++++++++++++++++-------------- src/xcb.c | 32 ------------ 6 files changed, 135 insertions(+), 95 deletions(-) diff --git a/include/data.h b/include/data.h index 088561c4..58856c38 100644 --- a/include/data.h +++ b/include/data.h @@ -86,6 +86,33 @@ struct reservedpx { uint32_t bottom; }; +/** + * Stores a width/height pair, used as part of deco_render_params to check + * whether the rects width/height have changed. + * + */ +struct width_height { + uint32_t w; + uint32_t h; +}; + +/** + * Stores the parameters for rendering a window decoration. This structure is + * cached in every Con and no re-rendering will be done if the parameters have + * not changed (only the pixmaps will be copied). + * + */ +struct deco_render_params { + struct Colortriple *color; + int border_style; + struct width_height con_rect; + struct width_height con_window_rect; + struct width_height con_deco_rect; + uint32_t background; + bool con_is_leaf; + xcb_font_t font; +}; + /** * Used for the cache of colorpixels. * @@ -96,22 +123,6 @@ struct Colorpixel { SLIST_ENTRY(Colorpixel) colorpixels; }; -struct Cached_Pixmap { - xcb_pixmap_t id; - - /* We’re going to paint on it, so a graphics context will be needed */ - xcb_gcontext_t gc; - - /* The rect with which the pixmap was created */ - Rect rect; - - /* The rect of the object to which this pixmap belongs. Necessary to - * find out when we need to re-create the pixmap. */ - Rect *referred_rect; - - xcb_drawable_t referred_drawable; -}; - struct Ignore_Event { int sequence; time_t added; @@ -236,6 +247,9 @@ struct Window { * application supports _NET_WM_NAME, in COMPOUND_TEXT otherwise). */ char *name_x; + /** Flag to force re-rendering the decoration upon changes */ + bool name_x_changed; + /** The name of the window as used in JSON (in UTF-8 if the application * supports _NET_WM_NAME, in COMPOUND_TEXT otherwise) */ char *name_json; @@ -353,9 +367,14 @@ struct Con { * inside this container (if any) sets the urgency hint, for example. */ bool urgent; - /* ids/gc for the frame window */ + /* ids/pixmap/graphics context for the frame window */ xcb_window_t frame; - xcb_gcontext_t gc; + xcb_pixmap_t pixmap; + xcb_gcontext_t pm_gc; + bool pixmap_recreated; + + /** Cache for the decoration rendering */ + struct deco_render_params *deco_render_params; /* Only workspace-containers can have floating clients */ TAILQ_HEAD(floating_head, Con) floating_head; diff --git a/include/xcb.h b/include/xcb.h index 4a09766f..673b5b39 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -135,15 +135,6 @@ void xcb_get_numlock_mask(xcb_connection_t *conn); */ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window); -/** - * - * Prepares the given Cached_Pixmap for usage (checks whether the size of the - * object this pixmap is related to (e.g. a window) has changed and re-creates - * the pixmap if so). - * - */ -void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap); - /** * Calculate the width of the given text (16-bit characters, UCS) with given * real length (amount of glyphs) using the given font. diff --git a/src/tree.c b/src/tree.c index a66e0f49..b68af36b 100644 --- a/src/tree.c +++ b/src/tree.c @@ -167,6 +167,7 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { } free(con->name); + FREE(con->deco_render_params); TAILQ_REMOVE(&all_cons, con, all_cons); free(con); diff --git a/src/window.c b/src/window.c index ffc73cd0..cd96890c 100644 --- a/src/window.c +++ b/src/window.c @@ -66,6 +66,7 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop) { win->name_json = new_name; win->name_x = ucs2_name; win->name_len = len; + win->name_x_changed = true; LOG("_NET_WM_NAME changed to \"%s\"\n", win->name_json); win->uses_net_wm_name = true; @@ -104,6 +105,7 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop) { win->name_x = new_name; win->name_json = sstrdup(new_name); win->name_len = strlen(new_name); + win->name_x_changed = true; } /* diff --git a/src/x.c b/src/x.c index 444b39ac..0fcb74ee 100644 --- a/src/x.c +++ b/src/x.c @@ -84,8 +84,6 @@ void x_con_init(Con *con) { Rect dims = { -15, -15, 10, 10 }; con->frame = create_window(conn, dims, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER, false, mask, values); - con->gc = xcb_generate_id(conn); - xcb_create_gc(conn, con->gc, con->frame, 0, 0); struct con_state *state = scalloc(sizeof(struct con_state)); state->id = con->frame; @@ -230,6 +228,7 @@ void x_window_kill(xcb_window_t window) { * */ void x_draw_decoration(Con *con) { + const Con *parent = con->parent; /* This code needs to run for: * • leaf containers * • non-leaf containers which are in a stacked/tabbed container @@ -238,30 +237,61 @@ void x_draw_decoration(Con *con) { * • floating containers (they don’t have a decoration) */ if ((!con_is_leaf(con) && - con->parent->layout != L_STACKED && - con->parent->layout != L_TABBED) || + parent->layout != L_STACKED && + parent->layout != L_TABBED) || con->type == CT_FLOATING_CON) return; DLOG("decoration should be rendered for con %p\n", con); - /* 1: find out which colors to use */ - struct Colortriple *color; + /* Skip containers whose height is 0 (for example empty dockareas) */ + if (con->rect.height == 0) { + DLOG("height == 0, not rendering\n"); + return; + } + + /* 1: build deco_params and compare with cache */ + struct deco_render_params *p = scalloc(sizeof(struct deco_render_params)); + + /* find out which colors to use */ if (con->urgent) - color = &config.client.urgent; + p->color = &config.client.urgent; else if (con == focused) - color = &config.client.focused; - else if (con == TAILQ_FIRST(&(con->parent->focus_head))) - color = &config.client.focused_inactive; + p->color = &config.client.focused; + else if (con == TAILQ_FIRST(&(parent->focus_head))) + p->color = &config.client.focused_inactive; else - color = &config.client.unfocused; + p->color = &config.client.unfocused; - Con *parent = con->parent; - int border_style = con_border_style(con); + p->border_style = con_border_style(con); - /* 2: draw the client.background, but only for the parts around the client_rect */ Rect *r = &(con->rect); Rect *w = &(con->window_rect); + p->con_rect = (struct width_height){ r->width, r->height }; + p->con_window_rect = (struct width_height){ w->width, w->height }; + p->con_deco_rect = (struct width_height){ con->deco_rect.width, con->deco_rect.height }; + p->background = config.client.background; + p->con_is_leaf = con_is_leaf(con); + p->font = config.font.id; + if (con->deco_render_params != NULL && + (con->window == NULL || !con->window->name_x_changed) && + !con->parent->pixmap_recreated && + memcmp(p, con->deco_render_params, sizeof(struct deco_render_params)) == 0) { + DLOG("CACHE HIT, copying existing pixmaps\n"); + free(p); + goto copy_pixmaps; + } + + DLOG("CACHE MISS\n"); + FREE(con->deco_render_params); + con->deco_render_params = p; + + if (con->window != NULL && con->window->name_x_changed) + con->window->name_x_changed = false; + + con->parent->pixmap_recreated = false; + + /* 2: draw the client.background, but only for the parts around the client_rect */ xcb_rectangle_t background[] = { /* top area */ { 0, con->deco_rect.height, r->width, w->y }, @@ -280,11 +310,11 @@ void x_draw_decoration(Con *con) { ); #endif - xcb_change_gc_single(conn, con->gc, XCB_GC_FOREGROUND, config.client.background); - xcb_poly_fill_rectangle(conn, con->frame, con->gc, sizeof(background) / sizeof(xcb_rectangle_t), background); + xcb_change_gc_single(conn, con->pm_gc, XCB_GC_FOREGROUND, config.client.background); + xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, sizeof(background) / sizeof(xcb_rectangle_t), background); /* 3: draw a rectangle in border color around the client */ - if (border_style != BS_NONE && con_is_leaf(con)) { + if (p->border_style != BS_NONE && p->con_is_leaf) { Rect br = con_border_style_rect(con); #if 0 DLOG("con->rect spans %d x %d\n", con->rect.width, con->rect.height); @@ -296,48 +326,53 @@ void x_draw_decoration(Con *con) { * (left, bottom and right part). We don’t just fill the whole * rectangle because some childs are not freely resizable and we want * their background color to "shine through". */ - xcb_change_gc_single(conn, con->gc, XCB_GC_FOREGROUND, color->background); + xcb_change_gc_single(conn, con->pm_gc, XCB_GC_FOREGROUND, p->color->background); xcb_rectangle_t borders[] = { { 0, 0, br.x, r->height }, { 0, r->height + br.height + br.y, r->width, r->height }, { r->width + br.width + br.x, 0, r->width, r->height } }; - xcb_poly_fill_rectangle(conn, con->frame, con->gc, 3, borders); + xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 3, borders); /* 1pixel border needs an additional line at the top */ - if (border_style == BS_1PIXEL) { + if (p->border_style == BS_1PIXEL) { xcb_rectangle_t topline = { br.x, 0, con->rect.width + br.width + br.x, br.y }; - xcb_poly_fill_rectangle(conn, con->frame, con->gc, 1, &topline); + xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &topline); } } /* if this is a borderless/1pixel window, we don’t * need to render the * decoration. */ - if (border_style != BS_NORMAL) { + if (p->border_style != BS_NORMAL) { DLOG("border style not BS_NORMAL, aborting rendering of decoration\n"); - return; + goto copy_pixmaps; } /* 4: paint the bar */ - xcb_change_gc_single(conn, parent->gc, XCB_GC_FOREGROUND, color->background); + xcb_change_gc_single(conn, parent->pm_gc, XCB_GC_FOREGROUND, p->color->background); + free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL)); xcb_rectangle_t drect = { con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height }; - xcb_poly_fill_rectangle(conn, parent->frame, parent->gc, 1, &drect); + free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL)); + xcb_poly_fill_rectangle(conn, parent->pixmap, parent->pm_gc, 1, &drect); + free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL)); /* 5: draw the two lines in border color */ - xcb_draw_line(conn, parent->frame, parent->gc, color->border, + xcb_draw_line(conn, parent->pixmap, parent->pm_gc, p->color->border, con->deco_rect.x, /* x */ con->deco_rect.y, /* y */ con->deco_rect.x + con->deco_rect.width, /* to_x */ con->deco_rect.y); /* to_y */ - xcb_draw_line(conn, parent->frame, parent->gc, color->border, + free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL)); + xcb_draw_line(conn, parent->pixmap, parent->pm_gc, p->color->border, con->deco_rect.x, /* x */ con->deco_rect.y + con->deco_rect.height - 1, /* y */ con->deco_rect.x + con->deco_rect.width, /* to_x */ con->deco_rect.y + con->deco_rect.height - 1); /* to_y */ + free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL)); /* 6: draw the title */ uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; - uint32_t values[] = { color->text, color->background, config.font.id }; - xcb_change_gc(conn, parent->gc, mask, values); + uint32_t values[] = { p->color->text, p->color->background, config.font.id }; + xcb_change_gc(conn, parent->pm_gc, mask, values); int text_offset_y = config.font.height + (con->deco_rect.height - config.font.height) / 2 - 1; struct Window *win = con->window; @@ -347,13 +382,14 @@ void x_draw_decoration(Con *con) { xcb_image_text_8( conn, strlen("another container"), - parent->frame, - parent->gc, + parent->pixmap, + parent->pm_gc, con->deco_rect.x + 2, con->deco_rect.y + text_offset_y, "another container" ); - return; + + goto copy_pixmaps; } int indent_level = 0, @@ -377,8 +413,8 @@ void x_draw_decoration(Con *con) { xcb_image_text_16( conn, win->name_len, - parent->frame, - parent->gc, + parent->pixmap, + parent->pm_gc, con->deco_rect.x + 2 + indent_px, con->deco_rect.y + text_offset_y, (xcb_char2b_t*)win->name_x @@ -387,12 +423,16 @@ void x_draw_decoration(Con *con) { xcb_image_text_8( conn, win->name_len, - parent->frame, - parent->gc, + parent->pixmap, + parent->pm_gc, con->deco_rect.x + 2 + indent_px, con->deco_rect.y + text_offset_y, win->name_x ); + +copy_pixmaps: + xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); + xcb_copy_area(conn, parent->pixmap, parent->frame, parent->pm_gc, 0, 0, 0, 0, parent->rect.width, parent->rect.height); } /* @@ -467,6 +507,25 @@ static void x_push_node(Con *con) { if (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0) { DLOG("setting rect (%d, %d, %d, %d)\n", rect.x, rect.y, rect.width, rect.height); xcb_set_window_rect(conn, con->frame, rect); + + /* As the pixmap only depends on the size and not on the position, it + * is enough to check if width/height have changed */ + if (state->rect.width != rect.width || + state->rect.height != rect.height) { + DLOG("CACHE: creating new pixmap\n"); + if (con->pixmap == 0) { + con->pixmap = xcb_generate_id(conn); + con->pm_gc = xcb_generate_id(conn); + } else { + xcb_free_pixmap(conn, con->pixmap); + xcb_free_gc(conn, con->pm_gc); + } + xcb_create_pixmap(conn, root_depth, con->pixmap, con->frame, rect.width, rect.height); + free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL)); + xcb_create_gc(conn, con->pm_gc, con->pixmap, 0, 0); + free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL)); + con->pixmap_recreated = true; + } memcpy(&(state->rect), &rect, sizeof(Rect)); fake_notify = true; } diff --git a/src/xcb.c b/src/xcb.c index 7382cc61..a7758ad3 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -289,38 +289,6 @@ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) { xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values); } -/* - * - * Prepares the given Cached_Pixmap for usage (checks whether the size of the - * object this pixmap is related to (e.g. a window) has changed and re-creates - * the pixmap if so). - * - */ -void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) { - DLOG("preparing pixmap\n"); - - /* If the Rect did not change, the pixmap does not need to be recreated */ - if (memcmp(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)) == 0) - return; - - memcpy(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)); - - if (pixmap->id == 0 || pixmap->gc == 0) { - DLOG("Creating new pixmap...\n"); - pixmap->id = xcb_generate_id(conn); - pixmap->gc = xcb_generate_id(conn); - } else { - DLOG("Re-creating this pixmap...\n"); - xcb_free_gc(conn, pixmap->gc); - xcb_free_pixmap(conn, pixmap->id); - } - - xcb_create_pixmap(conn, root_depth, pixmap->id, - pixmap->referred_drawable, pixmap->rect.width, pixmap->rect.height); - - xcb_create_gc(conn, pixmap->gc, pixmap->id, 0, 0); -} - /* * Query the width of the given text (16-bit characters, UCS) with given real * length (amount of glyphs) using the given font. From 144deaaaf6f626a0a95a51cb5d36d6eb59e1a936 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Mar 2011 14:29:48 +0100 Subject: [PATCH 551/867] Remove debugging syncs --- src/x.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/x.c b/src/x.c index 0fcb74ee..22912b9d 100644 --- a/src/x.c +++ b/src/x.c @@ -349,11 +349,8 @@ void x_draw_decoration(Con *con) { /* 4: paint the bar */ xcb_change_gc_single(conn, parent->pm_gc, XCB_GC_FOREGROUND, p->color->background); - free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL)); xcb_rectangle_t drect = { con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height }; - free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL)); xcb_poly_fill_rectangle(conn, parent->pixmap, parent->pm_gc, 1, &drect); - free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL)); /* 5: draw the two lines in border color */ xcb_draw_line(conn, parent->pixmap, parent->pm_gc, p->color->border, @@ -361,13 +358,11 @@ void x_draw_decoration(Con *con) { con->deco_rect.y, /* y */ con->deco_rect.x + con->deco_rect.width, /* to_x */ con->deco_rect.y); /* to_y */ - free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL)); xcb_draw_line(conn, parent->pixmap, parent->pm_gc, p->color->border, con->deco_rect.x, /* x */ con->deco_rect.y + con->deco_rect.height - 1, /* y */ con->deco_rect.x + con->deco_rect.width, /* to_x */ con->deco_rect.y + con->deco_rect.height - 1); /* to_y */ - free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL)); /* 6: draw the title */ uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; @@ -521,9 +516,7 @@ static void x_push_node(Con *con) { xcb_free_gc(conn, con->pm_gc); } xcb_create_pixmap(conn, root_depth, con->pixmap, con->frame, rect.width, rect.height); - free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL)); xcb_create_gc(conn, con->pm_gc, con->pixmap, 0, 0); - free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL)); con->pixmap_recreated = true; } memcpy(&(state->rect), &rect, sizeof(Rect)); From c130cefa93a048323586963b7ffce928d848fbb0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Mar 2011 16:26:36 +0100 Subject: [PATCH 552/867] Handle FocusIn events generated by clients and update decoration accordingly (Thanks mseed) --- include/handlers.h | 1 + include/x.h | 3 +++ include/xcb.h | 5 +++-- src/handlers.c | 35 +++++++++++++++++++++++++++++++++++ src/x.c | 15 ++++++++++++++- 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/include/handlers.h b/include/handlers.h index 1c8aa283..e36d6605 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -215,4 +215,5 @@ int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *prop); +int handle_focus_in(void *data, xcb_connection_t *conn, xcb_focus_in_event_t *event); #endif diff --git a/include/x.h b/include/x.h index 6b0bae0e..15d37420 100644 --- a/include/x.h +++ b/include/x.h @@ -5,6 +5,9 @@ #ifndef _X_H #define _X_H +/** Stores the X11 window ID of the currently focused window */ +extern xcb_window_t focused_id; + /** * Initializes the X11 part for the given container. Called exactly once for * every container from con_new(). diff --git a/include/xcb.h b/include/xcb.h index 673b5b39..13fafb0b 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * (c) 2009 Michael Stapelberg and contributors + * © 2009-2011 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -32,7 +32,8 @@ while rendering the layout) */ /** The XCB_CW_EVENT_MASK for the child (= real window) */ #define CHILD_EVENT_MASK (XCB_EVENT_MASK_PROPERTY_CHANGE | \ - XCB_EVENT_MASK_STRUCTURE_NOTIFY) + XCB_EVENT_MASK_STRUCTURE_NOTIFY | \ + XCB_EVENT_MASK_FOCUS_CHANGE) /** The XCB_CW_EVENT_MASK for its frame */ #define FRAME_EVENT_MASK (XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed/released */ \ diff --git a/src/handlers.c b/src/handlers.c index 111d7c24..619b36bb 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -129,6 +129,10 @@ void handle_event(int type, xcb_generic_event_t *event) { handle_mapping_notify(NULL, conn, (xcb_mapping_notify_event_t*)event); break; + case XCB_FOCUS_IN: + handle_focus_in(NULL, conn, (xcb_focus_in_event_t*)event); + break; + case XCB_PROPERTY_NOTIFY: DLOG("Property notify\n"); xcb_property_notify_event_t *e = (xcb_property_notify_event_t*)event; @@ -1023,3 +1027,34 @@ int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state return 1; } + +/* + * Handles FocusIn events which are generated by clients (i3’s focus changes + * don’t generate FocusIn events due to a different EventMask) and updates the + * decorations accordingly. + * + */ +int handle_focus_in(void *data, xcb_connection_t *conn, xcb_focus_in_event_t *event) { + DLOG("focus change in, for window 0x%08x\n", event->event); + Con *con; + if ((con = con_by_window_id(event->event)) == NULL || con->window == NULL) + return 1; + DLOG("That is con %p / %s\n", con, con->name); + + if (event->detail == XCB_NOTIFY_DETAIL_POINTER) { + DLOG("notify detail is pointer, ignoring this event\n"); + return 1; + } + + if (focused_id == event->event) { + DLOG("focus matches the currently focused window, not doing anything\n"); + return 1; + } + + DLOG("focus is different, updating decorations\n"); + con_focus(con); + /* We update focused_id because we don’t need to set focus again */ + focused_id = event->event; + x_push_changes(croot); + return 1; +} diff --git a/src/x.c b/src/x.c index 22912b9d..0c8a7ead 100644 --- a/src/x.c +++ b/src/x.c @@ -5,7 +5,7 @@ #include "all.h" /* Stores the X11 window ID of the currently focused window */ -static xcb_window_t focused_id = XCB_NONE; +xcb_window_t focused_id = XCB_NONE; /* * Describes the X11 state we may modify (map state, position, window stack). @@ -693,7 +693,18 @@ void x_push_changes(Con *con) { focused_id = XCB_NONE; } else { DLOG("Updating focus (focused: %p / %s)\n", focused, focused->name); + /* We remove XCB_EVENT_MASK_FOCUS_CHANGE from the event mask to get + * no focus change events for our own focus changes. We only want + * these generated by the clients. */ + if (focused->window != NULL) { + values[0] = CHILD_EVENT_MASK & ~(XCB_EVENT_MASK_FOCUS_CHANGE); + xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values); + } xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME); + if (focused->window != NULL) { + values[0] = CHILD_EVENT_MASK; + xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values); + } if (focused->window != NULL && focused->window->needs_take_focus) { @@ -724,6 +735,8 @@ void x_push_changes(Con *con) { CIRCLEQ_FOREACH(state, &old_state_head, old_state) { DLOG("old stack: 0x%08x\n", state->id); } + + xcb_flush(conn); } /* From 5e42863c7d84379f8eef1489210daf94576b4ad3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Mar 2011 16:35:08 +0100 Subject: [PATCH 553/867] remove unused struct Colorpixel --- include/data.h | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/include/data.h b/include/data.h index 58856c38..33f1d0d1 100644 --- a/include/data.h +++ b/include/data.h @@ -113,16 +113,6 @@ struct deco_render_params { xcb_font_t font; }; -/** - * Used for the cache of colorpixels. - * - */ -struct Colorpixel { - uint32_t pixel; - char *hex; - SLIST_ENTRY(Colorpixel) colorpixels; -}; - struct Ignore_Event { int sequence; time_t added; From e913e519f2d36300b8099fa9507461770319a365 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Mar 2011 16:53:12 +0100 Subject: [PATCH 554/867] refactor handlers.{c,h}: declare the handlers static, remove unnecessary parameters --- include/click.h | 14 +- include/handlers.h | 166 ----------------------- src/click.c | 2 +- src/handlers.c | 324 ++++++++++++++++++++++----------------------- 4 files changed, 171 insertions(+), 335 deletions(-) diff --git a/include/click.h b/include/click.h index de977ea4..2de32d04 100644 --- a/include/click.h +++ b/include/click.h @@ -1,9 +1,9 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2011 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -11,6 +11,14 @@ #ifndef _CLICK_H #define _CLICK_H -int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event); +/** + * The button press X callback. This function determines whether the floating + * modifier is pressed and where the user clicked (decoration, border, inside + * the window). + * + * Then, route_click is called on the appropriate con. + * + */ +int handle_button_press(xcb_button_press_event_t *event); #endif diff --git a/include/handlers.h b/include/handlers.h index e36d6605..ff0883d5 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -31,54 +31,6 @@ void handle_event(int type, xcb_generic_event_t *event); */ void property_handlers_init(); -/** - * There was a key press. We compare this key code with our bindings table and - * pass the bound action to parse_command(). - * - */ -int handle_key_press(void *ignored, xcb_connection_t *conn, - xcb_key_press_event_t *event); - -/** - * When the user moves the mouse pointer onto a window, this callback gets - * called. - * - */ -int handle_enter_notify(void *ignored, xcb_connection_t *conn, - xcb_enter_notify_event_t *event); - -/** - * When the user moves the mouse but does not change the active window - * (e.g. when having no windows opened but moving mouse on the root screen - * and crossing virtual screen boundaries), this callback gets called. - * - */ -int handle_motion_notify(void *ignored, xcb_connection_t *conn, - xcb_motion_notify_event_t *event); - -/** - * Called when the keyboard mapping changes (for example by using Xmodmap), - * we need to update our key bindings then (re-translate symbols). - * - */ -int handle_mapping_notify(void *ignored, xcb_connection_t *conn, - xcb_mapping_notify_event_t *event); -#if 0 - -/** - * Checks if the button press was on a stack window, handles focus setting and - * returns true if so, or false otherwise. - * - */ -int handle_button_press(void *ignored, xcb_connection_t *conn, - xcb_button_press_event_t *event); -#endif -/** - * A new window appeared on the screen (=was mapped), so let’s manage it. - * - */ -int handle_map_request(void *prophs, xcb_connection_t *conn, - xcb_map_request_event_t *event); #if 0 /** * Configuration notifies are only handled because we need to set up ignore @@ -88,84 +40,6 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event); #endif -/** - * Gets triggered upon a RandR screen change event, that is when the user - * changes the screen configuration in any way (mode, position, …) - * - */ -int handle_screen_change(void *prophs, xcb_connection_t *conn, - xcb_generic_event_t *e); - -/** - * Configure requests are received when the application wants to resize - * windows on their own. - * - * We generate a synthethic configure notify event to signalize the client its - * "new" position. - * - */ -int handle_configure_request(void *prophs, xcb_connection_t *conn, - xcb_configure_request_event_t *event); - -/** - * Our window decorations were unmapped. That means, the window will be killed - * now, so we better clean up before. - * - */ -int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event); - -/** - * A destroy notify event is sent when the window is not unmapped, but - * immediately destroyed (for example when starting a window and immediately - * killing the program which started it). - * - * We just pass on the event to the unmap notify handler (by copying the - * important fields in the event data structure). - * - */ -int handle_destroy_notify_event(void *data, xcb_connection_t *conn, - xcb_destroy_notify_event_t *event); - -/** - * Called when a window changes its title - * - */ -int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, - xcb_window_t window, xcb_atom_t atom, - xcb_get_property_reply_t *prop); -/** - * Handles legacy window name updates (WM_NAME), see also src/window.c, - * window_update_name_legacy(). - * - */ -int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, - uint8_t state, xcb_window_t window, - xcb_atom_t atom, xcb_get_property_reply_t - *prop); - -#if 0 -/** - * Store the window classes for jumping to them later. - * - */ -int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, - xcb_window_t window, xcb_atom_t atom, - xcb_get_property_reply_t *prop); - -#endif -/** - * Expose event means we should redraw our windows (= title bar) - * - */ -int handle_expose_event(void *data, xcb_connection_t *conn, - xcb_expose_event_t *event); -/** - * Handle client messages (EWMH) - * - */ -int handle_client_message(void *data, xcb_connection_t *conn, - xcb_client_message_event_t *event); - #if 0 /** * Handles _NET_WM_WINDOW_TYPE changes @@ -176,44 +50,4 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_get_property_reply_t *property); #endif -/** - * Handles the size hints set by a window, but currently only the part - * necessary for displaying clients proportionally inside their frames - * (mplayer for example) - * - * See ICCCM 4.1.2.3 for more details - * - */ -int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, - xcb_window_t window, xcb_atom_t name, - xcb_get_property_reply_t *reply); - -/** - * Handles the WM_HINTS property for extracting the urgency state of the window. - * - */ -int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, - xcb_atom_t name, xcb_get_property_reply_t *reply); - -/** - * Handles the transient for hints set by a window, signalizing that this - * window is a popup window for some other window. - * - * See ICCCM 4.1.2.6 for more details - * - */ -int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, - xcb_window_t window, xcb_atom_t name, - xcb_get_property_reply_t *reply); - -/** - * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a - * toolwindow (or similar) and to which window it belongs (logical parent). - * - */ -int handle_clientleader_change(void *data, xcb_connection_t *conn, - uint8_t state, xcb_window_t window, - xcb_atom_t name, xcb_get_property_reply_t *prop); - -int handle_focus_in(void *data, xcb_connection_t *conn, xcb_focus_in_event_t *event); #endif diff --git a/src/click.c b/src/click.c index 31b925fa..2092b6e7 100644 --- a/src/click.c +++ b/src/click.c @@ -243,7 +243,7 @@ done: * Then, route_click is called on the appropriate con. * */ -int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) { +int handle_button_press(xcb_button_press_event_t *event) { Con *con; DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event); diff --git a/src/handlers.c b/src/handlers.c index 619b36bb..f1c6128b 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -16,9 +16,6 @@ int randr_base = -1; -/* forward declaration for property_notify */ -static int property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom); - /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it, since it’d trigger an infinite loop of switching between the different windows when changing workspaces */ @@ -64,153 +61,13 @@ static bool event_is_ignored(const int sequence) { return false; } -/* - * Takes an xcb_generic_event_t and calls the appropriate handler, based on the - * event type. - * - */ -void handle_event(int type, xcb_generic_event_t *event) { - /* XXX: remove the NULL and conn parameters as soon as this version of libxcb is required */ - - if (randr_base > -1 && - type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { - handle_screen_change(NULL, conn, event); - return; - } - - switch (type) { - case XCB_KEY_PRESS: - handle_key_press(NULL, conn, (xcb_key_press_event_t*)event); - break; - - case XCB_BUTTON_PRESS: - handle_button_press(NULL, conn, (xcb_button_press_event_t*)event); - break; - - case XCB_MAP_REQUEST: - handle_map_request(NULL, conn, (xcb_map_request_event_t*)event); - break; - - case XCB_UNMAP_NOTIFY: - handle_unmap_notify_event(NULL, conn, (xcb_unmap_notify_event_t*)event); - break; - - case XCB_DESTROY_NOTIFY: - handle_destroy_notify_event(NULL, conn, (xcb_destroy_notify_event_t*)event); - break; - - case XCB_EXPOSE: - handle_expose_event(NULL, conn, (xcb_expose_event_t*)event); - break; - - case XCB_MOTION_NOTIFY: - handle_motion_notify(NULL, conn, (xcb_motion_notify_event_t*)event); - break; - - /* Enter window = user moved his mouse over the window */ - case XCB_ENTER_NOTIFY: - handle_enter_notify(NULL, conn, (xcb_enter_notify_event_t*)event); - break; - - /* Client message are sent to the root window. The only interesting - * client message for us is _NET_WM_STATE, we honour - * _NET_WM_STATE_FULLSCREEN */ - case XCB_CLIENT_MESSAGE: - handle_client_message(NULL, conn, (xcb_client_message_event_t*)event); - break; - - /* Configure request = window tried to change size on its own */ - case XCB_CONFIGURE_REQUEST: - handle_configure_request(NULL, conn, (xcb_configure_request_event_t*)event); - break; - - /* Mapping notify = keyboard mapping changed (Xmodmap), re-grab bindings */ - case XCB_MAPPING_NOTIFY: - handle_mapping_notify(NULL, conn, (xcb_mapping_notify_event_t*)event); - break; - - case XCB_FOCUS_IN: - handle_focus_in(NULL, conn, (xcb_focus_in_event_t*)event); - break; - - case XCB_PROPERTY_NOTIFY: - DLOG("Property notify\n"); - xcb_property_notify_event_t *e = (xcb_property_notify_event_t*)event; - property_notify(e->state, e->window, e->atom); - break; - - default: - DLOG("Unhandled event of type %d\n", type); - break; - } -} - -typedef int (*cb_property_handler_t)(void *data, xcb_connection_t *c, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property); - -struct property_handler_t { - xcb_atom_t atom; - uint32_t long_len; - cb_property_handler_t cb; -}; - -static struct property_handler_t property_handlers[] = { - { 0, 128, handle_windowname_change }, - { 0, UINT_MAX, handle_hints }, - { 0, 128, handle_windowname_change_legacy }, - { 0, UINT_MAX, handle_normal_hints }, - { 0, UINT_MAX, handle_clientleader_change }, - { 0, UINT_MAX, handle_transient_for } -}; -#define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t)) - -/* - * Sets the appropriate atoms for the property handlers after the atoms were - * received from X11 - * - */ -void property_handlers_init() { - property_handlers[0].atom = A__NET_WM_NAME; - property_handlers[1].atom = A_WM_HINTS; - property_handlers[2].atom = A_WM_NAME; - property_handlers[3].atom = A_WM_NORMAL_HINTS; - property_handlers[4].atom = A_WM_CLIENT_LEADER; - property_handlers[5].atom = A_WM_TRANSIENT_FOR; -} - -static int property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) { - struct property_handler_t *handler = NULL; - xcb_get_property_reply_t *propr = NULL; - int ret; - - for (int c = 0; c < sizeof(property_handlers) / sizeof(struct property_handler_t); c++) { - if (property_handlers[c].atom != atom) - continue; - - handler = &property_handlers[c]; - break; - } - - if (handler == NULL) { - DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom); - return 0; - } - - if (state != XCB_PROPERTY_DELETE) { - xcb_get_property_cookie_t cookie = xcb_get_property(conn, 0, window, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, handler->long_len); - propr = xcb_get_property_reply(conn, cookie, 0); - } - - ret = handler->cb(NULL, conn, state, window, atom, propr); - FREE(propr); - return ret; -} /* * There was a key press. We compare this key code with our bindings table and pass * the bound action to parse_command(). * */ -int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) { +static int handle_key_press(xcb_key_press_event_t *event) { DLOG("Keypress %d, state raw = %d\n", event->detail, event->state); /* Remove the numlock bit, all other bits are modifiers we can bind to */ @@ -284,8 +141,7 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) { * When the user moves the mouse pointer onto a window, this callback gets called. * */ -int handle_enter_notify(void *ignored, xcb_connection_t *conn, - xcb_enter_notify_event_t *event) { +static int handle_enter_notify(xcb_enter_notify_event_t *event) { Con *con; DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", @@ -361,7 +217,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, * and crossing virtual screen boundaries), this callback gets called. * */ -int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notify_event_t *event) { +static int handle_motion_notify(xcb_motion_notify_event_t *event) { /* Skip events where the pointer was over a child window, we are only * interested in events on the root window. */ if (event->child != 0) @@ -402,7 +258,7 @@ int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notif * we need to update our key bindings then (re-translate symbols). * */ -int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_notify_event_t *event) { +static int handle_mapping_notify(xcb_mapping_notify_event_t *event) { if (event->request != XCB_MAPPING_KEYBOARD && event->request != XCB_MAPPING_MODIFIER) return 0; @@ -423,7 +279,7 @@ int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_not * A new window appeared on the screen (=was mapped), so let’s manage it. * */ -int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_event_t *event) { +static int handle_map_request(xcb_map_request_event_t *event) { xcb_get_window_attributes_cookie_t cookie; cookie = xcb_get_window_attributes_unchecked(conn, event->window); @@ -442,7 +298,7 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve * We generate a synthethic configure notify event to signalize the client its "new" position. * */ -int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event) { +static int handle_configure_request(xcb_configure_request_event_t *event) { Con *con; DLOG("window 0x%08x wants to be at %dx%d with %dx%d\n", @@ -566,8 +422,7 @@ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_n * changes the screen configuration in any way (mode, position, …) * */ -int handle_screen_change(void *prophs, xcb_connection_t *conn, - xcb_generic_event_t *e) { +static int handle_screen_change(xcb_generic_event_t *e) { DLOG("RandR screen change\n"); randr_query_outputs(); @@ -582,7 +437,7 @@ int handle_screen_change(void *prophs, xcb_connection_t *conn, * now, so we better clean up before. * */ -int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event) { +static int handle_unmap_notify_event(xcb_unmap_notify_event_t *event) { /* FIXME: we cannot ignore this sequence because more UnmapNotifys with the same sequence * numbers but different window IDs may follow */ @@ -666,7 +521,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti * important fields in the event data structure). * */ -int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_notify_event_t *event) { +static int handle_destroy_notify_event(xcb_destroy_notify_event_t *event) { DLOG("destroy notify for 0x%08x, 0x%08x\n", event->event, event->window); xcb_unmap_notify_event_t unmap; @@ -674,14 +529,14 @@ int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_ unmap.event = event->event; unmap.window = event->window; - return handle_unmap_notify_event(NULL, conn, &unmap); + return handle_unmap_notify_event(&unmap); } /* * Called when a window changes its title * */ -int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, +static int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) { Con *con; if ((con = con_by_window_id(window)) == NULL || con->window == NULL) @@ -699,7 +554,7 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, * window_update_name_legacy(). * */ -int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state, +static int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) { Con *con; if ((con = con_by_window_id(window)) == NULL || con->window == NULL) @@ -716,7 +571,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t * Updates the client’s WM_CLASS property * */ -int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, +static int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) { Con *con; if ((con = con_by_window_id(window)) == NULL || con->window == NULL) @@ -731,7 +586,7 @@ int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, * Expose event means we should redraw our windows (= title bar) * */ -int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) { +static int handle_expose_event(xcb_expose_event_t *event) { Con *parent, *con; /* event->count is the number of minimum remaining expose events for this @@ -771,7 +626,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * * Handle client messages (EWMH) * */ -int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message_event_t *event) { +static int handle_client_message(xcb_client_message_event_t *event) { LOG("ClientMessage for window 0x%08x\n", event->window); if (event->type == A__NET_WM_STATE) { if (event->format != 32 || event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN) { @@ -825,7 +680,7 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi * See ICCCM 4.1.2.3 for more details * */ -int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, +static int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *reply) { Con *con = con_by_window_id(window); if (con == NULL) { @@ -926,7 +781,7 @@ render_and_return: * Handles the WM_HINTS property for extracting the urgency state of the window. * */ -int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, +static int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *reply) { Con *con = con_by_window_id(window); if (con == NULL) { @@ -976,7 +831,7 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t * See ICCCM 4.1.2.6 for more details * */ -int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, +static int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *prop) { Con *con; @@ -1010,7 +865,7 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_ * toolwindow (or similar) and to which window it belongs (logical parent). * */ -int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, +static int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *prop) { Con *con; if ((con = con_by_window_id(window)) == NULL || con->window == NULL) @@ -1034,7 +889,7 @@ int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state * decorations accordingly. * */ -int handle_focus_in(void *data, xcb_connection_t *conn, xcb_focus_in_event_t *event) { +static int handle_focus_in(xcb_focus_in_event_t *event) { DLOG("focus change in, for window 0x%08x\n", event->event); Con *con; if ((con = con_by_window_id(event->event)) == NULL || con->window == NULL) @@ -1058,3 +913,142 @@ int handle_focus_in(void *data, xcb_connection_t *conn, xcb_focus_in_event_t *ev x_push_changes(croot); return 1; } + +typedef int (*cb_property_handler_t)(void *data, xcb_connection_t *c, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property); + +struct property_handler_t { + xcb_atom_t atom; + uint32_t long_len; + cb_property_handler_t cb; +}; + +static struct property_handler_t property_handlers[] = { + { 0, 128, handle_windowname_change }, + { 0, UINT_MAX, handle_hints }, + { 0, 128, handle_windowname_change_legacy }, + { 0, UINT_MAX, handle_normal_hints }, + { 0, UINT_MAX, handle_clientleader_change }, + { 0, UINT_MAX, handle_transient_for } +}; +#define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t)) + +/* + * Sets the appropriate atoms for the property handlers after the atoms were + * received from X11 + * + */ +void property_handlers_init() { + property_handlers[0].atom = A__NET_WM_NAME; + property_handlers[1].atom = A_WM_HINTS; + property_handlers[2].atom = A_WM_NAME; + property_handlers[3].atom = A_WM_NORMAL_HINTS; + property_handlers[4].atom = A_WM_CLIENT_LEADER; + property_handlers[5].atom = A_WM_TRANSIENT_FOR; +} + +static int property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) { + struct property_handler_t *handler = NULL; + xcb_get_property_reply_t *propr = NULL; + int ret; + + for (int c = 0; c < sizeof(property_handlers) / sizeof(struct property_handler_t); c++) { + if (property_handlers[c].atom != atom) + continue; + + handler = &property_handlers[c]; + break; + } + + if (handler == NULL) { + DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom); + return 0; + } + + if (state != XCB_PROPERTY_DELETE) { + xcb_get_property_cookie_t cookie = xcb_get_property(conn, 0, window, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, handler->long_len); + propr = xcb_get_property_reply(conn, cookie, 0); + } + + ret = handler->cb(NULL, conn, state, window, atom, propr); + FREE(propr); + return ret; +} + +/* + * Takes an xcb_generic_event_t and calls the appropriate handler, based on the + * event type. + * + */ +void handle_event(int type, xcb_generic_event_t *event) { + if (randr_base > -1 && + type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { + handle_screen_change(event); + return; + } + + switch (type) { + case XCB_KEY_PRESS: + handle_key_press((xcb_key_press_event_t*)event); + break; + + case XCB_BUTTON_PRESS: + handle_button_press((xcb_button_press_event_t*)event); + break; + + case XCB_MAP_REQUEST: + handle_map_request((xcb_map_request_event_t*)event); + break; + + case XCB_UNMAP_NOTIFY: + handle_unmap_notify_event((xcb_unmap_notify_event_t*)event); + break; + + case XCB_DESTROY_NOTIFY: + handle_destroy_notify_event((xcb_destroy_notify_event_t*)event); + break; + + case XCB_EXPOSE: + handle_expose_event((xcb_expose_event_t*)event); + break; + + case XCB_MOTION_NOTIFY: + handle_motion_notify((xcb_motion_notify_event_t*)event); + break; + + /* Enter window = user moved his mouse over the window */ + case XCB_ENTER_NOTIFY: + handle_enter_notify((xcb_enter_notify_event_t*)event); + break; + + /* Client message are sent to the root window. The only interesting + * client message for us is _NET_WM_STATE, we honour + * _NET_WM_STATE_FULLSCREEN */ + case XCB_CLIENT_MESSAGE: + handle_client_message((xcb_client_message_event_t*)event); + break; + + /* Configure request = window tried to change size on its own */ + case XCB_CONFIGURE_REQUEST: + handle_configure_request((xcb_configure_request_event_t*)event); + break; + + /* Mapping notify = keyboard mapping changed (Xmodmap), re-grab bindings */ + case XCB_MAPPING_NOTIFY: + handle_mapping_notify((xcb_mapping_notify_event_t*)event); + break; + + case XCB_FOCUS_IN: + handle_focus_in((xcb_focus_in_event_t*)event); + break; + + case XCB_PROPERTY_NOTIFY: + DLOG("Property notify\n"); + xcb_property_notify_event_t *e = (xcb_property_notify_event_t*)event; + property_notify(e->state, e->window, e->atom); + break; + + default: + DLOG("Unhandled event of type %d\n", type); + break; + } +} From b3ee50b1847631603b3aed1dcff46d09e8fd46ad Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Mar 2011 18:07:07 +0100 Subject: [PATCH 555/867] Bugfix: Also update pixmaps when the position of the deco_rect has changed (Thanks fernandotcl) --- include/data.h | 2 +- src/x.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/data.h b/include/data.h index 33f1d0d1..843d0c9c 100644 --- a/include/data.h +++ b/include/data.h @@ -107,7 +107,7 @@ struct deco_render_params { int border_style; struct width_height con_rect; struct width_height con_window_rect; - struct width_height con_deco_rect; + Rect con_deco_rect; uint32_t background; bool con_is_leaf; xcb_font_t font; diff --git a/src/x.c b/src/x.c index 0c8a7ead..681ff370 100644 --- a/src/x.c +++ b/src/x.c @@ -268,7 +268,7 @@ void x_draw_decoration(Con *con) { Rect *w = &(con->window_rect); p->con_rect = (struct width_height){ r->width, r->height }; p->con_window_rect = (struct width_height){ w->width, w->height }; - p->con_deco_rect = (struct width_height){ con->deco_rect.width, con->deco_rect.height }; + p->con_deco_rect = con->deco_rect; p->background = config.client.background; p->con_is_leaf = con_is_leaf(con); p->font = config.font.id; From 38173749f87f2cf0e5513eb6da99ec4fcee3e63c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Mar 2011 18:17:18 +0100 Subject: [PATCH 556/867] Bugfix: Also invalidate caches of the following cons in a split con on cache miss (Thanks fernandotcl) --- src/x.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/x.c b/src/x.c index 681ff370..c21ff739 100644 --- a/src/x.c +++ b/src/x.c @@ -283,6 +283,12 @@ void x_draw_decoration(Con *con) { } DLOG("CACHE MISS\n"); + Con *next = con; + while ((next = TAILQ_NEXT(next, nodes))) { + DLOG("Also invalidating cache of %p\n", next); + FREE(next->deco_render_params); + } + FREE(con->deco_render_params); con->deco_render_params = p; From 67b37551d8d16350b9226e12a511605abd0d5767 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Mar 2011 18:27:14 +0100 Subject: [PATCH 557/867] Bugfix: Fix switching workspaces on multi-monitor setups (Thanks mseed) Fixes #356 --- src/workspace.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/workspace.c b/src/workspace.c index d09f0277..8104aa89 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -230,8 +230,10 @@ void workspace_show(const char *num) { /* enable fullscreen for the target workspace. If it happens to be the * same one we are currently on anyways, we can stop here. */ workspace->fullscreen_mode = CF_OUTPUT; - if (workspace == old) + if (workspace == con_get_workspace(focused)) { + DLOG("Not switching, already there.\n"); return; + } workspace_reassign_sticky(workspace); From 99ce340feac8f9d28b4558d380532bb14ebf85d6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Mar 2011 19:32:00 +0100 Subject: [PATCH 558/867] Focus cons when moving to a visible workspace on a different output (Thanks mseed) Fixes: #355 --- src/con.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/con.c b/src/con.c index 8c0a8303..d5029eea 100644 --- a/src/con.c +++ b/src/con.c @@ -527,6 +527,9 @@ void con_move_to_workspace(Con *con, Con *workspace) { con = con->parent; } + Con *source_output = con_get_output(con), + *dest_output = con_get_output(workspace); + /* 1: save the container which is going to be focused after the current * container is moved away */ Con *focus_next = con_next_focused(con); @@ -562,8 +565,15 @@ void con_move_to_workspace(Con *con, Con *workspace) { * calling tree_render(), so for the "real" focus this is a no-op) */ con_focus(con); - /* 8: keep focus on the current workspace */ - con_focus(focus_next); + /* 8: when moving to a visible workspace on a different output, we keep the + * con focused. Otherwise, we leave the focus on the current workspace as we + * don’t want to focus invisible workspaces */ + if (source_output != dest_output && + workspace_is_visible(workspace)) { + DLOG("Moved to a different output, focusing target"); + } else { + con_focus(focus_next); + } CALL(parent, on_remove_child); } From 6d8784af9851cd6cfab89b12d58fb34fe683f05e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Sun, 20 Mar 2011 11:34:34 -0300 Subject: [PATCH 559/867] Set the I3_SOCKET_PATH atom to the expanded path. --- include/ipc.h | 2 ++ src/ipc.c | 8 +++++++- src/main.c | 6 +++--- src/x.c | 4 ++-- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/include/ipc.h b/include/ipc.h index 7f92ee61..a5de487a 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -22,6 +22,8 @@ #include "i3/ipc.h" +extern char *current_socketpath; + typedef struct ipc_client { int fd; diff --git a/src/ipc.c b/src/ipc.c index a6f0dd5c..b76d2bb1 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -20,6 +20,8 @@ #include "all.h" +char *current_socketpath = NULL; + /* Shorter names for all those yajl_gen_* functions */ #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) #define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str)) @@ -626,6 +628,9 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) { int ipc_create_socket(const char *filename) { int sockfd; + FREE(current_socketpath); + current_socketpath = NULL; + char *resolved = resolve_tilde(filename); DLOG("Creating IPC-socket at %s\n", resolved); char *copy = sstrdup(resolved); @@ -655,13 +660,14 @@ int ipc_create_socket(const char *filename) { return -1; } - free(resolved); set_nonblock(sockfd); if (listen(sockfd, 5) < 0) { perror("listen()"); + free(resolved); return -1; } + current_socketpath = resolved; return sockfd; } diff --git a/src/main.c b/src/main.c index 2f632dd6..d1b1a532 100644 --- a/src/main.c +++ b/src/main.c @@ -350,9 +350,6 @@ int main(int argc, char *argv[]) { xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTING_WM_CHECK, A_WINDOW, 32, 1, &root); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3"); - /* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */ - x_set_i3_atoms(); - keysyms = xcb_key_symbols_alloc(conn); xcb_get_numlock_mask(conn); @@ -396,6 +393,9 @@ int main(int argc, char *argv[]) { } } + /* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */ + x_set_i3_atoms(); + struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io)); struct ev_io *xkb = scalloc(sizeof(struct ev_io)); struct ev_check *xcb_check = scalloc(sizeof(struct ev_check)); diff --git a/src/x.c b/src/x.c index c21ff739..826d2134 100644 --- a/src/x.c +++ b/src/x.c @@ -783,8 +783,8 @@ void x_set_name(Con *con, const char *name) { */ void x_set_i3_atoms() { xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_SOCKET_PATH, A_UTF8_STRING, 8, - (config.ipc_socket_path != NULL ? strlen(config.ipc_socket_path) : 0), - config.ipc_socket_path); + (current_socketpath == NULL ? 0 : strlen(current_socketpath)), + current_socketpath); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8, strlen(current_configpath), current_configpath); } From 39ee97fd821dd5eb37d2720181a38466d96d475d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Mar 2011 23:49:16 +0100 Subject: [PATCH 560/867] FREE() already nulls the pointer --- src/ipc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ipc.c b/src/ipc.c index b76d2bb1..68d654da 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -629,7 +629,6 @@ int ipc_create_socket(const char *filename) { int sockfd; FREE(current_socketpath); - current_socketpath = NULL; char *resolved = resolve_tilde(filename); DLOG("Creating IPC-socket at %s\n", resolved); From f0f7cb74780314dff1fae99079e01a45728219fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Tarl=C3=A1=20Cardoso=20Lemos?= Date: Mon, 21 Mar 2011 09:05:58 -0300 Subject: [PATCH 561/867] If the socket path isn't specified, write it to /tmp. --- src/main.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main.c b/src/main.c index d1b1a532..2bda27c5 100644 --- a/src/main.c +++ b/src/main.c @@ -262,6 +262,8 @@ int main(int argc, char *argv[]) { if (config.ipc_socket_path == NULL) { config.ipc_socket_path = getenv("I3SOCK"); + if (config.ipc_socket_path == NULL) + config.ipc_socket_path = get_process_filename("i3-ipc-socket"); } uint32_t mask = XCB_CW_EVENT_MASK; @@ -382,15 +384,13 @@ int main(int argc, char *argv[]) { die("Could not initialize libev. Bad LIBEV_FLAGS?\n"); /* Create the UNIX domain socket for IPC */ - if (config.ipc_socket_path != NULL) { - int ipc_socket = ipc_create_socket(config.ipc_socket_path); - if (ipc_socket == -1) { - ELOG("Could not create the IPC socket, IPC disabled\n"); - } else { - struct ev_io *ipc_io = scalloc(sizeof(struct ev_io)); - ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ); - ev_io_start(loop, ipc_io); - } + int ipc_socket = ipc_create_socket(config.ipc_socket_path); + if (ipc_socket == -1) { + ELOG("Could not create the IPC socket, IPC disabled\n"); + } else { + struct ev_io *ipc_io = scalloc(sizeof(struct ev_io)); + ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ); + ev_io_start(loop, ipc_io); } /* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */ From 4fd4e619ec25970aa4319b7e988ad5b03599653d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Mar 2011 23:54:13 +0100 Subject: [PATCH 562/867] little coding style fixes, fix compilation warning --- include/util.h | 13 ++++++++++--- src/main.c | 9 +++++---- src/util.c | 8 +++----- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/include/util.h b/include/util.h index 514e10bd..610e701e 100644 --- a/include/util.h +++ b/include/util.h @@ -104,7 +104,7 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, */ char *convert_utf8_to_ucs2(char *input, int *real_strlen); -/* +/** * 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. @@ -112,13 +112,20 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen); */ char *resolve_tilde(const char *path); -/* +/** * Checks if the given path exists by calling stat(). * */ bool path_exists(const char *path); -/* + +/** + * Returns the name of a temporary file with the specified prefix. + * + */ +char *get_process_filename(const char *prefix); + +/** * Restart i3 in-place * appends -a to argument list to disable autostart * diff --git a/src/main.c b/src/main.c index 2bda27c5..a5a492e7 100644 --- a/src/main.c +++ b/src/main.c @@ -260,11 +260,12 @@ int main(int argc, char *argv[]) { exit(0); } - if (config.ipc_socket_path == NULL) { + if (config.ipc_socket_path == NULL) config.ipc_socket_path = getenv("I3SOCK"); - if (config.ipc_socket_path == NULL) - config.ipc_socket_path = get_process_filename("i3-ipc-socket"); - } + + /* Fall back to a file name in /tmp/ based on the PID */ + if (config.ipc_socket_path == NULL) + config.ipc_socket_path = get_process_filename("i3-ipc-socket"); uint32_t mask = XCB_CW_EVENT_MASK; uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | diff --git a/src/util.c b/src/util.c index 693027fa..2e4d9c23 100644 --- a/src/util.c +++ b/src/util.c @@ -243,8 +243,7 @@ static char **append_argument(char **original, char *argument) { * Returns the name of a temporary file with the specified prefix. * */ -char *get_process_filename(const char *prefix) -{ +char *get_process_filename(const char *prefix) { struct passwd *pw = getpwuid(getuid()); const char *username = pw ? pw->pw_name : "unknown"; char *filename; @@ -253,9 +252,8 @@ char *get_process_filename(const char *prefix) perror("asprintf()"); return NULL; } - else { - return filename; - } + + return filename; } #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) From 20b1fd4293b496341523686e93312f6ee73e1df8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 30 Mar 2011 23:00:48 +0200 Subject: [PATCH 563/867] Skip FocusIn events with mode == NOTIFY_MODE_GRAB or NOTIFY_MODE_UNGRAB According to the Xlib Programming Manual section 10.7.2 [1], these events are generated when keyboard grabs activate/deactivate, while we are only interested in focus changes which are done by other programs independend from the keyboard. [1] http://tronche.com/gui/x/xlib/events/input-focus/grab.html --- src/handlers.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/handlers.c b/src/handlers.c index f1c6128b..ac0fd87a 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -896,6 +896,12 @@ static int handle_focus_in(xcb_focus_in_event_t *event) { return 1; DLOG("That is con %p / %s\n", con, con->name); + if (event->mode == XCB_NOTIFY_MODE_GRAB || + event->mode == XCB_NOTIFY_MODE_UNGRAB) { + DLOG("FocusIn event for grab/ungrab, ignoring\n"); + return 1; + } + if (event->detail == XCB_NOTIFY_DETAIL_POINTER) { DLOG("notify detail is pointer, ignoring this event\n"); return 1; From b644fb5f26c1768b70c5b49d8cd917a63a2d1d91 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 1 Apr 2011 20:40:32 +0200 Subject: [PATCH 564/867] x: recurse x_push_node in focus order. reduces flickering when switching workspaces --- src/x.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/x.c b/src/x.c index 826d2134..8aad722a 100644 --- a/src/x.c +++ b/src/x.c @@ -575,11 +575,10 @@ static void x_push_node(Con *con) { fake_absolute_configure_notify(con); } - /* handle all children and floating windows of this node */ - TAILQ_FOREACH(current, &(con->nodes_head), nodes) - x_push_node(current); - - TAILQ_FOREACH(current, &(con->floating_head), floating_windows) + /* Handle all children and floating windows of this node. We recurse + * in focus order to display the focused client in a stack first when + * switching workspaces (reduces flickering). */ + TAILQ_FOREACH(current, &(con->focus_head), focused) x_push_node(current); if (con->type != CT_ROOT && con->type != CT_OUTPUT) From 57447112babfe02a5b5abdcb602159c81744f755 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 1 Apr 2011 20:41:08 +0200 Subject: [PATCH 565/867] x: skip x_draw_decoration when con is not mapped This commit makes workspace switching completely free of cache misses, so decorations are not re-rendered when switching workspaces. --- src/x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/x.c b/src/x.c index 8aad722a..995b4373 100644 --- a/src/x.c +++ b/src/x.c @@ -581,7 +581,7 @@ static void x_push_node(Con *con) { TAILQ_FOREACH(current, &(con->focus_head), focused) x_push_node(current); - if (con->type != CT_ROOT && con->type != CT_OUTPUT) + if (con->type != CT_ROOT && con->type != CT_OUTPUT && con->mapped) x_draw_decoration(con); } From 6419e42f6df9d33a89e4966381f17949ebf08bdc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 1 Apr 2011 21:39:58 +0200 Subject: [PATCH 566/867] bugfix: fix race condition where window titles were not correctly updated Fixes: #351 --- src/manage.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/manage.c b/src/manage.c index ac199468..98a34a15 100644 --- a/src/manage.c +++ b/src/manage.c @@ -117,6 +117,17 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki goto out; } + uint32_t mask = 0; + uint32_t values[1]; + + /* Set a temporary event mask for the new window, consisting only of + * PropertyChange. We need to be notified of PropertyChanges because the + * client can change its properties *after* we requested them but *before* + * we actually reparented it and have set our final event mask. */ + mask = XCB_CW_EVENT_MASK; + values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE; + xcb_change_window_attributes(conn, window, mask, values); + #define GET_PROPERTY(atom, len) xcb_get_property_unchecked(conn, false, window, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, len) wm_type_cookie = GET_PROPERTY(A__NET_WM_WINDOW_TYPE, UINT32_MAX); @@ -130,8 +141,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */ DLOG("reparenting!\n"); - uint32_t mask = 0; - uint32_t values[1]; i3Window *cwindow = scalloc(sizeof(i3Window)); cwindow->id = window; From 26635a7595b9c06834df68bab3c0371046fd7230 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 1 Apr 2011 21:54:45 +0200 Subject: [PATCH 567/867] x: skip creating pixmaps when the rect is actually invisible This fixes a few X11 errors. --- src/x.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/x.c b/src/x.c index 995b4373..ee9e0a43 100644 --- a/src/x.c +++ b/src/x.c @@ -510,9 +510,12 @@ static void x_push_node(Con *con) { xcb_set_window_rect(conn, con->frame, rect); /* As the pixmap only depends on the size and not on the position, it - * is enough to check if width/height have changed */ - if (state->rect.width != rect.width || - state->rect.height != rect.height) { + * is enough to check if width/height have changed. Also, we don’t + * create a pixmap at all when the window is actually not visible + * (height == 0). */ + if (rect.height > 0 && + (state->rect.width != rect.width || + state->rect.height != rect.height)) { DLOG("CACHE: creating new pixmap\n"); if (con->pixmap == 0) { con->pixmap = xcb_generate_id(conn); From d8bf633e56783f6ecbcee836d8597854331d8d26 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 1 Apr 2011 21:57:08 +0200 Subject: [PATCH 568/867] Bugfix: Flush the Xlib connection after creating cursors Fixes a race condition where the cursors were created after we were already using them. --- src/xcursor.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/xcursor.c b/src/xcursor.c index ee77d0c1..54ef34d2 100644 --- a/src/xcursor.c +++ b/src/xcursor.c @@ -28,6 +28,8 @@ void xcursor_load_cursors() { cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr"); cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow"); cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow"); + + XFlush(xlibdpy); } Cursor xcursor_get_cursor(enum xcursor_cursor_t c) { From 21c7a69812e40e8015b4c48a3d7e7dc1479d6371 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 1 Apr 2011 22:40:12 +0200 Subject: [PATCH 569/867] optimization: when moving floating windows, render/push only the floatingcon --- include/x.h | 8 ++++++++ src/floating.c | 8 ++++---- src/x.c | 10 ++++++---- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/include/x.h b/include/x.h index 15d37420..827f6f85 100644 --- a/include/x.h +++ b/include/x.h @@ -60,6 +60,14 @@ void x_window_kill(xcb_window_t window); */ void x_draw_decoration(Con *con); +/** + * This function pushes the properties of each node of the layout tree to + * X11 if they have changed (like the map state, position of the window, …). + * It recursively traverses all children of the given node. + * + */ +void x_push_node(Con *con, bool skip_decoration); + /** * Pushes all changes (state of each node, see x_push_node() and the window * stack) to X11. diff --git a/src/floating.c b/src/floating.c index 9d4e1cf8..6cc9a168 100644 --- a/src/floating.c +++ b/src/floating.c @@ -231,10 +231,10 @@ DRAGGING_CB(drag_window_callback) { /* Reposition the client correctly while moving */ con->rect.x = old_rect->x + (new_x - event->root_x); con->rect.y = old_rect->y + (new_y - event->root_y); - /* TODO: don’t re-render the whole tree just because we change - * coordinates of a floating window */ - tree_render(); - x_push_changes(croot); + + render_con(con, false); + x_push_node(con, true); + xcb_flush(conn); } /* diff --git a/src/x.c b/src/x.c index ee9e0a43..9ea87644 100644 --- a/src/x.c +++ b/src/x.c @@ -442,7 +442,7 @@ copy_pixmaps: * It recursively traverses all children of the given node. * */ -static void x_push_node(Con *con) { +void x_push_node(Con *con, bool skip_decoration) { Con *current; con_state *state; Rect rect = con->rect; @@ -582,9 +582,11 @@ static void x_push_node(Con *con) { * in focus order to display the focused client in a stack first when * switching workspaces (reduces flickering). */ TAILQ_FOREACH(current, &(con->focus_head), focused) - x_push_node(current); + x_push_node(current, skip_decoration); - if (con->type != CT_ROOT && con->type != CT_OUTPUT && con->mapped) + if (!skip_decoration && + (con->type != CT_ROOT && con->type != CT_OUTPUT) && + con->mapped) x_draw_decoration(con); } @@ -687,7 +689,7 @@ void x_push_changes(Con *con) { DLOG("Done, EnterNotify re-enabled\n"); DLOG("\n\n PUSHING CHANGES\n\n"); - x_push_node(con); + x_push_node(con, false); xcb_window_t to_focus = focused->frame; if (focused->window != NULL) From 650eebc3479ecbf527eed52b89a3feabf7d40bb9 Mon Sep 17 00:00:00 2001 From: Simon Kampe Date: Wed, 23 Mar 2011 16:11:46 +0100 Subject: [PATCH 570/867] Implemented config key 'new_container' --- include/config.h | 2 +- src/cfgparse.l | 7 ++++--- src/cfgparse.y | 14 +++++++++++--- src/con.c | 12 ++++++++++-- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/include/config.h b/include/config.h index 55d597eb..97da3dd2 100644 --- a/include/config.h +++ b/include/config.h @@ -92,7 +92,7 @@ struct Config { const char *ipc_socket_path; const char *restart_state_path; - int container_mode; + int default_layout; int container_stack_limit; int container_stack_limit_value; diff --git a/src/cfgparse.l b/src/cfgparse.l index cc0dd320..7295fed9 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -106,9 +106,10 @@ workspace_bar { return TOKWORKSPACEBAR; } popup_during_fullscreen { return TOK_POPUP_DURING_FULLSCREEN; } ignore { return TOK_IGNORE; } leave_fullscreen { return TOK_LEAVE_FULLSCREEN; } -default { /* yylval.number = MODE_DEFAULT; */return TOKCONTAINERMODE; } -stacking { /* yylval.number = MODE_STACK; */return TOKCONTAINERMODE; } -tabbed { /* yylval.number = MODE_TABBED; */return TOKCONTAINERMODE; } +default { /* yylval.number = MODE_DEFAULT; */return TOK_DEFAULT; } +stacking { /* yylval.number = MODE_STACK; */return TOK_STACKING; } +stacked { return TOK_STACKING; } +tabbed { /* yylval.number = MODE_TABBED; */return TOK_TABBED; } stack-limit { return TOKSTACKLIMIT; } cols { /* yylval.number = STACK_LIMIT_COLS; */return TOKSTACKLIMIT; } rows { /* yylval.number = STACK_LIMIT_ROWS; */return TOKSTACKLIMIT; } diff --git a/src/cfgparse.y b/src/cfgparse.y index 68678b80..75e68122 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -235,7 +235,9 @@ void parse_file(const char *f) { %token TOK_1PIXEL "1pixel" %token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse" %token TOKWORKSPACEBAR "workspace_bar" -%token TOKCONTAINERMODE "default/stacking/tabbed" +%token TOK_DEFAULT "default" +%token TOK_STACKING "stacking" +%token TOK_TABBED "tabbed" %token TOKSTACKLIMIT "stack-limit" %token TOK_POPUP_DURING_FULLSCREEN "popup_during_fullscreen" %token TOK_IGNORE "ignore" @@ -393,10 +395,10 @@ direction: ; new_container: - TOKNEWCONTAINER WHITESPACE TOKCONTAINERMODE + TOKNEWCONTAINER WHITESPACE layout_mode { DLOG("new containers will be in mode %d\n", $3); - config.container_mode = $3; + config.default_layout = $3; #if 0 /* We also need to change the layout of the already existing @@ -437,6 +439,12 @@ new_container: } ; +layout_mode: + TOK_DEFAULT { $$ = L_DEFAULT; } + | TOK_STACKING { $$ = L_STACKED; } + | TOK_TABBED { $$ = L_TABBED; } + ; + new_window: TOKNEWWINDOW WHITESPACE border_style { diff --git a/src/con.c b/src/con.c index d5029eea..07406d8d 100644 --- a/src/con.c +++ b/src/con.c @@ -59,8 +59,16 @@ Con *con_new(Con *parent) { TAILQ_INIT(&(new->focus_head)); TAILQ_INIT(&(new->swallow_head)); - if (parent != NULL) - con_attach(new, parent, false); + if (parent != NULL) { + /* Set layout of ws if this is the first child of the ws. */ + if (parent->type == CT_WORKSPACE && con_is_leaf(parent)) { + con_set_layout(new, config.default_layout); + con_attach(new, parent, false); + con_set_layout(parent, config.default_layout); + } else { + con_attach(new, parent, false); + } + } return new; } From c3b4006f6b11578020238b1aa3d51eeab80d29e3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 2 Apr 2011 21:49:35 +0200 Subject: [PATCH 571/867] Rename new_container to workspace_layout --- src/cfgparse.l | 2 +- src/cfgparse.y | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cfgparse.l b/src/cfgparse.l index 7295fed9..a2d62e54 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -96,7 +96,7 @@ default_orientation { return TOK_ORIENTATION; } horizontal { return TOK_HORIZ; } vertical { return TOK_VERT; } auto { return TOK_AUTO; } -new_container { return TOKNEWCONTAINER; } +workspace_layout { return TOK_WORKSPACE_LAYOUT; } new_window { return TOKNEWWINDOW; } normal { return TOK_NORMAL; } none { return TOK_NONE; } diff --git a/src/cfgparse.y b/src/cfgparse.y index 75e68122..721c5698 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -228,7 +228,7 @@ void parse_file(const char *f) { %token TOK_HORIZ "horizontal" %token TOK_VERT "vertical" %token TOK_AUTO "auto" -%token TOKNEWCONTAINER "new_container" +%token TOK_WORKSPACE_LAYOUT "workspace_layout" %token TOKNEWWINDOW "new_window" %token TOK_NORMAL "normal" %token TOK_NONE "none" @@ -256,7 +256,7 @@ line: | mode | floating_modifier | orientation - | new_container + | workspace_layout | new_window | focus_follows_mouse | workspace_bar @@ -394,8 +394,8 @@ direction: | TOK_AUTO { $$ = NO_ORIENTATION; } ; -new_container: - TOKNEWCONTAINER WHITESPACE layout_mode +workspace_layout: + TOK_WORKSPACE_LAYOUT WHITESPACE layout_mode { DLOG("new containers will be in mode %d\n", $3); config.default_layout = $3; @@ -419,7 +419,7 @@ new_container: } #endif } - | TOKNEWCONTAINER WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER + | TOK_WORKSPACE_LAYOUT WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER { DLOG("stack-limit %d with val %d\n", $5, $7); config.container_stack_limit = $5; From 02acf426d316a7e62262d97434c76a46e6c63307 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 2 Apr 2011 22:08:19 +0200 Subject: [PATCH 572/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20set=20the=20l?= =?UTF-8?q?ayout=20when=20it=E2=80=99s=20default=20layout=20anyways?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes some nasty side-effects --- src/con.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/con.c b/src/con.c index 07406d8d..806d0b29 100644 --- a/src/con.c +++ b/src/con.c @@ -60,8 +60,11 @@ Con *con_new(Con *parent) { TAILQ_INIT(&(new->swallow_head)); if (parent != NULL) { - /* Set layout of ws if this is the first child of the ws. */ - if (parent->type == CT_WORKSPACE && con_is_leaf(parent)) { + /* Set layout of ws if this is the first child of the ws and the user + * wanted something different than the default layout. */ + if (parent->type == CT_WORKSPACE && + con_is_leaf(parent) && + config.default_layout != L_DEFAULT) { con_set_layout(new, config.default_layout); con_attach(new, parent, false); con_set_layout(parent, config.default_layout); From 36583ec6ee7ada36764a492fdaae158a5cdd04c3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 18 Apr 2011 18:44:18 +0200 Subject: [PATCH 573/867] Bugfix: When moving floating cons to other workspaces, attach them to the workspace --- src/con.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/con.c b/src/con.c index 806d0b29..1adfcf35 100644 --- a/src/con.c +++ b/src/con.c @@ -549,8 +549,10 @@ void con_move_to_workspace(Con *con, Con *workspace) { Con *next = con_descend_focused(workspace); /* 3: we go up one level, but only when next is a normal container */ - if (next->type != CT_WORKSPACE) + if (next->type != CT_WORKSPACE) { + DLOG("next originally = %p / %s / type %d\n", next, next->name, next->type); next = next->parent; + } /* 4: if the target container is floating, we get the workspace instead. * Only tiling windows need to get inserted next to the current container. @@ -561,6 +563,12 @@ void con_move_to_workspace(Con *con, Con *workspace) { next = floatingcon->parent; } + if (con->type == CT_FLOATING_CON) { + Con *ws = con_get_workspace(next); + DLOG("This is a floating window, using workspace %p / %s\n", ws, ws->name); + next = ws; + } + DLOG("Re-attaching container to %p / %s\n", next, next->name); /* 5: re-attach the con to the parent of this focused container */ Con *parent = con->parent; @@ -581,7 +589,7 @@ void con_move_to_workspace(Con *con, Con *workspace) { * don’t want to focus invisible workspaces */ if (source_output != dest_output && workspace_is_visible(workspace)) { - DLOG("Moved to a different output, focusing target"); + DLOG("Moved to a different output, focusing target\n"); } else { con_focus(focus_next); } From 60532a90e7f575f5d941eca1076282b78b3510db Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 18 Apr 2011 18:44:39 +0200 Subject: [PATCH 574/867] Bugfix: Assign floating cons to correct workspace when moving between monitors (Thanks dothebart) Fixes: #371 --- src/floating.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/floating.c b/src/floating.c index 6cc9a168..0f653430 100644 --- a/src/floating.c +++ b/src/floating.c @@ -235,6 +235,30 @@ DRAGGING_CB(drag_window_callback) { render_con(con, false); x_push_node(con, true); xcb_flush(conn); + + /* Check if we cross workspace boundaries while moving */ + Output *output = get_output_containing( + con->rect.x + (con->rect.width / 2), + con->rect.y + (con->rect.height / 2)); + + if (!output) { + ELOG("No output found at destination coordinates?\n"); + return; + } + + if (con_get_output(con) == output->con) { + DLOG("still the same ws\n"); + return; + } + + DLOG("Need to re-assign!\n"); + + Con *content = output_get_content(output->con); + Con *ws = TAILQ_FIRST(&(content->nodes_head)); + DLOG("Moving con %p / %s to workspace %p / %s\n", con, con->name, ws, ws->name); + con_move_to_workspace(con, ws); + con_focus(con_descend_focused(con)); + tree_render(); } /* From f613df48d23e3f934129c79726d438ee19f2400c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 18 Apr 2011 19:28:03 +0200 Subject: [PATCH 575/867] =?UTF-8?q?Bugfix:=20Check=20if=20a=20floating=20w?= =?UTF-8?q?indow=E2=80=99s=20coordinates=20are=20within=20a=20different=20?= =?UTF-8?q?workspace=20when=20managing=20(Thanks=20Merovius)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: #297 --- include/floating.h | 7 ++++++ src/floating.c | 53 +++++++++++++++++++++++++++++----------------- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/include/floating.h b/include/floating.h index c3935252..6ab4cf2e 100644 --- a/include/floating.h +++ b/include/floating.h @@ -59,6 +59,13 @@ void toggle_floating_mode(Con *con, bool automatic); */ void floating_raise_con(Con *con); +/** + * Checks if con’s coordinates are within its workspace and re-assigns it to + * the actual workspace if not. + * + */ +bool floating_maybe_reassign_ws(Con *con); + #if 0 /** * Removes the floating client from its workspace and attaches it to the new diff --git a/src/floating.c b/src/floating.c index 0f653430..78804c8e 100644 --- a/src/floating.c +++ b/src/floating.c @@ -156,6 +156,8 @@ void floating_enable(Con *con, bool automatic) { // TODO: don’t influence focus handling when Con was not focused before. if (set_focus) con_focus(con); + + floating_maybe_reassign_ws(nc); } void floating_disable(Con *con, bool automatic) { @@ -225,6 +227,36 @@ void floating_raise_con(Con *con) { TAILQ_INSERT_TAIL(&(con->parent->floating_head), con, floating_windows); } +/* + * Checks if con’s coordinates are within its workspace and re-assigns it to + * the actual workspace if not. + * + */ +bool floating_maybe_reassign_ws(Con *con) { + Output *output = get_output_containing( + con->rect.x + (con->rect.width / 2), + con->rect.y + (con->rect.height / 2)); + + if (!output) { + ELOG("No output found at destination coordinates?\n"); + return false; + } + + if (con_get_output(con) == output->con) { + DLOG("still the same ws\n"); + return false; + } + + DLOG("Need to re-assign!\n"); + + Con *content = output_get_content(output->con); + Con *ws = TAILQ_FIRST(&(content->nodes_head)); + DLOG("Moving con %p / %s to workspace %p / %s\n", con, con->name, ws, ws->name); + con_move_to_workspace(con, ws); + con_focus(con_descend_focused(con)); + return true; +} + DRAGGING_CB(drag_window_callback) { struct xcb_button_press_event_t *event = extra; @@ -237,27 +269,8 @@ DRAGGING_CB(drag_window_callback) { xcb_flush(conn); /* Check if we cross workspace boundaries while moving */ - Output *output = get_output_containing( - con->rect.x + (con->rect.width / 2), - con->rect.y + (con->rect.height / 2)); - - if (!output) { - ELOG("No output found at destination coordinates?\n"); + if (!floating_maybe_reassign_ws(con)) return; - } - - if (con_get_output(con) == output->con) { - DLOG("still the same ws\n"); - return; - } - - DLOG("Need to re-assign!\n"); - - Con *content = output_get_content(output->con); - Con *ws = TAILQ_FIRST(&(content->nodes_head)); - DLOG("Moving con %p / %s to workspace %p / %s\n", con, con->name, ws, ws->name); - con_move_to_workspace(con, ws); - con_focus(con_descend_focused(con)); tree_render(); } From 3d5af35fa45b9d160b37f7a56c00222d56febf18 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 18 Apr 2011 19:36:10 +0200 Subject: [PATCH 576/867] Bugfix: Center floating windows with invalid coordinates on current ws --- src/floating.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/floating.c b/src/floating.c index 78804c8e..1d08ec9e 100644 --- a/src/floating.c +++ b/src/floating.c @@ -157,7 +157,19 @@ void floating_enable(Con *con, bool automatic) { if (set_focus) con_focus(con); - floating_maybe_reassign_ws(nc); + /* Check if we need to re-assign it to a different workspace because of its + * coordinates and exit if that was done successfully. */ + if (floating_maybe_reassign_ws(nc)) + return; + + /* Sanitize coordinates: Check if they are on any output */ + if (get_output_containing(nc->rect.x, nc->rect.y) != NULL) + return; + + ELOG("No output found at destination coordinates, centering floating window on current ws\n"); + Con *ws = nc->parent; + 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); } void floating_disable(Con *con, bool automatic) { From 84b804cda61c9fda33521c11882b926ef4fc7f79 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 18 Apr 2011 21:10:50 +0200 Subject: [PATCH 577/867] x: Set pixmap as background window, saves a lot of CopyAreas --- src/x.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/x.c b/src/x.c index 9ea87644..22660d12 100644 --- a/src/x.c +++ b/src/x.c @@ -277,9 +277,9 @@ void x_draw_decoration(Con *con) { (con->window == NULL || !con->window->name_x_changed) && !con->parent->pixmap_recreated && memcmp(p, con->deco_render_params, sizeof(struct deco_render_params)) == 0) { - DLOG("CACHE HIT, copying existing pixmaps\n"); + DLOG("CACHE HIT, not re-rendering\n"); free(p); - goto copy_pixmaps; + return; } DLOG("CACHE MISS\n"); @@ -350,7 +350,7 @@ void x_draw_decoration(Con *con) { * decoration. */ if (p->border_style != BS_NORMAL) { DLOG("border style not BS_NORMAL, aborting rendering of decoration\n"); - goto copy_pixmaps; + goto update_pixmaps; } /* 4: paint the bar */ @@ -390,7 +390,7 @@ void x_draw_decoration(Con *con) { "another container" ); - goto copy_pixmaps; + goto update_pixmaps; } int indent_level = 0, @@ -431,9 +431,9 @@ void x_draw_decoration(Con *con) { win->name_x ); -copy_pixmaps: - xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); - xcb_copy_area(conn, parent->pixmap, parent->frame, parent->pm_gc, 0, 0, 0, 0, parent->rect.width, parent->rect.height); +update_pixmaps: + xcb_clear_area(conn, false, con->frame, 0, 0, con->rect.width, con->rect.height); + xcb_clear_area(conn, false, parent->frame, 0, 0, parent->rect.width, parent->rect.height); } /* @@ -526,6 +526,8 @@ void x_push_node(Con *con, bool skip_decoration) { } xcb_create_pixmap(conn, root_depth, con->pixmap, con->frame, rect.width, rect.height); xcb_create_gc(conn, con->pm_gc, con->pixmap, 0, 0); + uint32_t values[] = { con->pixmap }; + xcb_change_window_attributes(conn, con->frame, XCB_CW_BACK_PIXMAP, values); con->pixmap_recreated = true; } memcpy(&(state->rect), &rect, sizeof(Rect)); From cd6f93be3fa607e98fe17d05c86b31dcfc6fdf1c Mon Sep 17 00:00:00 2001 From: Sardem FF7 Date: Mon, 18 Apr 2011 23:06:32 +0200 Subject: [PATCH 578/867] Rename bind to bindcode Also fallback when using just 'bind' to be backward-compatible --- docs/userguide | 6 ++-- man/i3.man | 76 +++++++++++++++++++++++++------------------------- src/cfgparse.l | 7 +++-- src/cfgparse.y | 10 +++---- 4 files changed, 50 insertions(+), 49 deletions(-) diff --git a/docs/userguide b/docs/userguide index 35e587af..5610eaa5 100644 --- a/docs/userguide +++ b/docs/userguide @@ -256,7 +256,7 @@ keysyms. *Syntax*: ---------------------------------- bindsym [Modifiers+]keysym command -bind [Modifiers+]keycode command +bindcode [Modifiers+]keycode command ---------------------------------- *Examples*: @@ -268,7 +268,7 @@ bindsym Mod1+f f bindsym Mod1+Shift+r restart # Notebook-specific hotkeys -bind 214 exec /home/michael/toggle_beamer.sh +bindcode 214 exec /home/michael/toggle_beamer.sh -------------------------------- Available Modifiers: @@ -680,7 +680,7 @@ mode "resize" { bindsym d resize right +10 bindsym Shift+d resize right -10 - bind 36 mode default + bindcode 36 mode default } # Enter resize mode diff --git a/man/i3.man b/man/i3.man index 7dbe0dfa..e46ccbea 100644 --- a/man/i3.man +++ b/man/i3.man @@ -156,84 +156,84 @@ You can specify a custom path using the -c option. font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 # Start terminal (Mod1+Enter) -bind Mod1+36 exec /usr/bin/urxvt +bindcode Mod1+36 exec /usr/bin/urxvt # Start dmenu (Mod1+v) -bind Mod1+55 exec /usr/bin/dmenu_run +bindcode Mod1+55 exec /usr/bin/dmenu_run # Kill current client (Mod1+Shift+q) -bind Mod1+Shift+24 kill +bindcode Mod1+Shift+24 kill # Beamer on/off -bind Mod1+73 exec /home/michael/toggle_beamer.sh +bindcode Mod1+73 exec /home/michael/toggle_beamer.sh # Screen locking -bind Mod1+68 exec /usr/bin/i3lock +bindcode Mod1+68 exec /usr/bin/i3lock # Restart i3 inplace (Mod1+Shift+r) -bind Mod1+Shift+27 restart +bindcode Mod1+Shift+27 restart # Exit i3 (Mod1+Shift+e) -bind Mod1+Shift+26 exit +bindcode Mod1+Shift+26 exit # Brightness -bind Mod1+97 exec sudo sh -c "echo up > /proc/acpi/ibm/brightness" -bind Mod1+103 exec sudo sh -c "echo down > /proc/acpi/ibm/brightness" +bindcode Mod1+97 exec sudo sh -c "echo up > /proc/acpi/ibm/brightness" +bindcode Mod1+103 exec sudo sh -c "echo down > /proc/acpi/ibm/brightness" # Fullscreen (Mod1+f) -bind Mod1+41 f +bindcode Mod1+41 f # Stacking (Mod1+h) -bind Mod1+43 s +bindcode Mod1+43 s # Default (Mod1+e) -bind Mod1+26 d +bindcode Mod1+26 d # Toggle tiling/floating of the current window (Mod1+Shift+Space) -bind Mod1+Shift+65 t +bindcode Mod1+Shift+65 t # Go into the tiling layer / floating layer, depending on whether # the current window is tiling / floating (Mod1+t) -bind Mod1+28 focus ft +bindcode Mod1+28 focus ft # Focus (Mod1+j/k/l/;) -bind Mod1+44 h -bind Mod1+45 j -bind Mod1+46 k -bind Mod1+47 l +bindcode Mod1+44 h +bindcode Mod1+45 j +bindcode Mod1+46 k +bindcode Mod1+47 l # Focus Container (Mod3+j/k/l/;) -bind Mod3+44 wch -bind Mod3+45 wcj -bind Mod3+46 wck -bind Mod3+47 wcl +bindcode Mod3+44 wch +bindcode Mod3+45 wcj +bindcode Mod3+46 wck +bindcode Mod3+47 wcl # Snap (Mod1+Control+j/k/l/;) -bind Mod1+Control+44 sh -bind Mod1+Control+45 sj -bind Mod1+Control+46 sk -bind Mod1+Control+47 sl +bindcode Mod1+Control+44 sh +bindcode Mod1+Control+45 sj +bindcode Mod1+Control+46 sk +bindcode Mod1+Control+47 sl # Move (Mod1+Shift+j/k/l/;) -bind Mod1+Shift+44 mh -bind Mod1+Shift+45 mj -bind Mod1+Shift+46 mk -bind Mod1+Shift+47 ml +bindcode Mod1+Shift+44 mh +bindcode Mod1+Shift+45 mj +bindcode Mod1+Shift+46 mk +bindcode Mod1+Shift+47 ml # Move Container (Mod3+Shift+j/k/l/;) -bind Mod3+Shift+44 wcmh -bind Mod3+Shift+45 wcmj -bind Mod3+Shift+46 wcmk -bind Mod3+Shift+47 wcml +bindcode Mod3+Shift+44 wcmh +bindcode Mod3+Shift+45 wcmj +bindcode Mod3+Shift+46 wcmk +bindcode Mod3+Shift+47 wcml # Workspaces -bind Mod1+10 1 -bind Mod1+11 2 +bindcode Mod1+10 1 +bindcode Mod1+11 2 ... # Move to Workspace -bind Mod1+Shift+10 1 -bind Mod1+Shift+11 2 +bindcode Mod1+Shift+10 1 +bindcode Mod1+Shift+11 2 ... ------------------------------------------------------------- diff --git a/src/cfgparse.l b/src/cfgparse.l index a2d62e54..c343aad6 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -33,7 +33,7 @@ int yycolumn = 1; EOL (\r?\n) -%s BIND_COND +%s BINDCODE_COND %s BINDSYM_COND %s BIND_AWS_COND %s BINDSYM_AWS_COND @@ -73,7 +73,8 @@ EOL (\r?\n) [0-9a-fA-F]+ { yylval.string = strdup(yytext); return HEX; } [0-9]+ { yylval.number = atoi(yytext); return NUMBER; } mode { return TOKMODE; } -bind { BEGIN(BIND_COND); return TOKBIND; } +bind { BEGIN(BINDCODE_COND); return TOKBINDCODE; } +bindcode { BEGIN(BINDCODE_COND); return TOKBINDCODE; } bindsym { BEGIN(BINDSYM_COND); return TOKBINDSYM; } floating_modifier { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; } workspace { BEGIN(INITIAL); return TOKWORKSPACE; } @@ -138,7 +139,7 @@ shift { return TOKSHIFT; } BEGIN(INITIAL); yy_push_state(BUFFER_LINE); } -[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; } +[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; } [ \t]+ { BEGIN(BINDSYM_AWS_COND); return WHITESPACE; } [ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } [ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } diff --git a/src/cfgparse.y b/src/cfgparse.y index 721c5698..e8818b1f 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -202,7 +202,7 @@ void parse_file(const char *f) { %token STR_NG "" %token HEX "" %token OUTPUT "" -%token TOKBIND +%token TOKBINDCODE %token TOKTERMINAL %token TOKCOMMENT "" %token TOKFONT "font" @@ -289,14 +289,14 @@ bindline: ; binding: - TOKBIND WHITESPACE bind { $$ = $3; } + TOKBINDCODE WHITESPACE bindcode { $$ = $3; } | TOKBINDSYM WHITESPACE bindsym { $$ = $3; } ; -bind: +bindcode: binding_modifiers NUMBER WHITESPACE command { - printf("\tFound binding mod%d with key %d and command %s\n", $1, $2, $4); + printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $4); Binding *new = scalloc(sizeof(Binding)); new->keycode = $2; @@ -310,7 +310,7 @@ bind: bindsym: binding_modifiers word_or_number WHITESPACE command { - printf("\tFound symbolic mod%d with key %s and command %s\n", $1, $2, $4); + printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $4); Binding *new = scalloc(sizeof(Binding)); new->symbol = $2; From 3721bcb86865f9a269d1d655ff2c99e6afd3f676 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 19 Apr 2011 21:50:56 +0200 Subject: [PATCH 579/867] Bugfix: Ignore EnterNotifies generated by UnmapNotifies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Actually, commit 1c5adc6c35cffaedc08c7d1dd1b03a3269d1367c commented out code without ever fixing it. I think this was responsible for the 'workspace switching sometimes does not work' bug. My observations: Had it again today and analyzed a log of it. Looks like after unmapping the windows on one workspace (in my case: chromium, eclipse, urxvt, focus on eclipse) we get UnmapNotify events for chromium and eclipse, but then we get an EnterNotify for the terminal (due to unmapping the other windows and therefore mapping the terminal under the cursor), only afterwards the UnmapNotify follows. So, there are two things wrong with that: • We handle EnterNotifys for unmapped windows • Unmapping windows sometimes works in a sequence, sometimes the sequence gets split. Not sure why (if unmapping can take longer for some windows or if our syncing is wrong -- but i checked the latter briefly and it looks correct). Maybe GrabServer helps? • We don’t ignore EnterNotify events caused by UnmapNotifies. We used to, but then there was a different problem and we decided to solve the EnterNotify problem in another way, which actually never happened (commit 1c5adc6c35cffaedc08c7d1dd1b03a3269d1367c). --- include/data.h | 1 + include/handlers.h | 2 +- src/handlers.c | 20 ++++++++++++-------- src/x.c | 6 +++--- src/xcb.c | 2 +- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/include/data.h b/include/data.h index 843d0c9c..f20d764e 100644 --- a/include/data.h +++ b/include/data.h @@ -115,6 +115,7 @@ struct deco_render_params { struct Ignore_Event { int sequence; + int response_type; time_t added; SLIST_ENTRY(Ignore_Event) ignore_events; diff --git a/include/handlers.h b/include/handlers.h index ff0883d5..839bac61 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -15,7 +15,7 @@ extern int randr_base; -void add_ignore_event(const int sequence); +void add_ignore_event(const int sequence, const int response_type); /** * Takes an xcb_generic_event_t and calls the appropriate handler, based on the diff --git a/src/handlers.c b/src/handlers.c index ac0fd87a..fbd660bc 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -21,10 +21,11 @@ int randr_base = -1; changing workspaces */ static SLIST_HEAD(ignore_head, Ignore_Event) ignore_events; -void add_ignore_event(const int sequence) { +void add_ignore_event(const int sequence, const int response_type) { struct Ignore_Event *event = smalloc(sizeof(struct Ignore_Event)); event->sequence = sequence; + event->response_type = response_type; event->added = time(NULL); SLIST_INSERT_HEAD(&ignore_events, event, ignore_events); @@ -34,7 +35,7 @@ void add_ignore_event(const int sequence) { * Checks if the given sequence is ignored and returns true if so. * */ -static bool event_is_ignored(const int sequence) { +static bool event_is_ignored(const int sequence, const int response_type) { struct Ignore_Event *event; time_t now = time(NULL); for (event = SLIST_FIRST(&ignore_events); event != SLIST_END(&ignore_events);) { @@ -50,6 +51,10 @@ static bool event_is_ignored(const int sequence) { if (event->sequence != sequence) continue; + if (event->response_type != 0 && + event->response_type != response_type) + continue; + /* instead of removing a sequence number we better wait until it gets * garbage collected. it may generate multiple events (there are multiple * enter_notifies for one configure_request, for example). */ @@ -153,8 +158,10 @@ static int handle_enter_notify(xcb_enter_notify_event_t *event) { } /* Some events are not interesting, because they were not generated * actively by the user, but by reconfiguration of windows */ - if (event_is_ignored(event->sequence)) + if (event_is_ignored(event->sequence, XCB_ENTER_NOTIFY)) { + DLOG("Event ignored\n"); return 1; + } bool enter_child = false; /* Get container by frame or by child window */ @@ -285,7 +292,7 @@ static int handle_map_request(xcb_map_request_event_t *event) { cookie = xcb_get_window_attributes_unchecked(conn, event->window); DLOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence); - add_ignore_event(event->sequence); + add_ignore_event(event->sequence, 0); manage_window(event->window, cookie, false); x_push_changes(croot); @@ -438,12 +445,9 @@ static int handle_screen_change(xcb_generic_event_t *e) { * */ static int handle_unmap_notify_event(xcb_unmap_notify_event_t *event) { - - /* FIXME: we cannot ignore this sequence because more UnmapNotifys with the same sequence - * numbers but different window IDs may follow */ /* we need to ignore EnterNotify events which will be generated because a * different window is visible now */ - //add_ignore_event(event->sequence); + add_ignore_event(event->sequence, XCB_ENTER_NOTIFY); DLOG("UnmapNotify for 0x%08x (received from 0x%08x), serial %d\n", event->window, event->event, event->sequence); Con *con = con_by_window_id(event->window); diff --git a/src/x.c b/src/x.c index 22660d12..ab60c51d 100644 --- a/src/x.c +++ b/src/x.c @@ -564,14 +564,14 @@ void x_push_node(Con *con, bool skip_decoration) { cookie = xcb_map_window(conn, con->window->id); DLOG("mapping child window (serial %d)\n", cookie.sequence); /* Ignore enter_notifies which are generated when mapping */ - add_ignore_event(cookie.sequence); + add_ignore_event(cookie.sequence, 0); state->child_mapped = true; } cookie = xcb_map_window(conn, con->frame); DLOG("mapping container (serial %d)\n", cookie.sequence); /* Ignore enter_notifies which are generated when mapping */ - add_ignore_event(cookie.sequence); + add_ignore_event(cookie.sequence, 0); state->mapped = con->mapped; } @@ -631,7 +631,7 @@ static void x_push_node_unmaps(Con *con) { DLOG("ignore_unmap for con %p (frame 0x%08x) now %d\n", con, con->frame, con->ignore_unmap); } /* Ignore enter_notifies which are generated when unmapping */ - add_ignore_event(cookie.sequence); + add_ignore_event(cookie.sequence, 0); state->mapped = con->mapped; } diff --git a/src/xcb.c b/src/xcb.c index a7758ad3..3fd0bfcd 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -328,7 +328,7 @@ void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r) { XCB_CONFIG_WINDOW_HEIGHT, &(r.x)); /* ignore events which are generated because we configured a window */ - add_ignore_event(cookie.sequence); + add_ignore_event(cookie.sequence, 0); } /* From 8a40dc0011828f57661a6bd9e2df09c138f4a1a9 Mon Sep 17 00:00:00 2001 From: Sardem FF7 Date: Tue, 19 Apr 2011 00:22:32 +0200 Subject: [PATCH 580/867] Use XDG_RUNTIME_DIR when available XDG_RUNTIME_DIR is the volatile runtime data dir provided by modern session manager such as systemd --- src/main.c | 14 ++++++++------ src/util.c | 32 ++++++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/main.c b/src/main.c index a5a492e7..91bd5d68 100644 --- a/src/main.c +++ b/src/main.c @@ -260,12 +260,13 @@ int main(int argc, char *argv[]) { exit(0); } - if (config.ipc_socket_path == NULL) - config.ipc_socket_path = getenv("I3SOCK"); - - /* Fall back to a file name in /tmp/ based on the PID */ - if (config.ipc_socket_path == NULL) - config.ipc_socket_path = get_process_filename("i3-ipc-socket"); + if (config.ipc_socket_path == NULL) { + /* Fall back to a file name in /tmp/ based on the PID */ + if ((config.ipc_socket_path = getenv("I3SOCK")) == NULL) + config.ipc_socket_path = get_process_filename("ipc-socket"); + else + config.ipc_socket_path = sstrdup(config.ipc_socket_path); + } uint32_t mask = XCB_CW_EVENT_MASK; uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | @@ -389,6 +390,7 @@ int main(int argc, char *argv[]) { if (ipc_socket == -1) { ELOG("Could not create the IPC socket, IPC disabled\n"); } else { + free(config.ipc_socket_path); struct ev_io *ipc_io = scalloc(sizeof(struct ev_io)); ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ); ev_io_start(loop, ipc_io); diff --git a/src/util.c b/src/util.c index 2e4d9c23..a72b52d5 100644 --- a/src/util.c +++ b/src/util.c @@ -244,15 +244,35 @@ static char **append_argument(char **original, char *argument) { * */ char *get_process_filename(const char *prefix) { - struct passwd *pw = getpwuid(getuid()); - const char *username = pw ? pw->pw_name : "unknown"; + char *dir = getenv("XDG_RUNTIME_DIR"); + if (dir == NULL) { + struct passwd *pw = getpwuid(getuid()); + const char *username = pw ? pw->pw_name : "unknown"; + if (asprintf(&dir, "/tmp/i3-%s", username) == -1) { + perror("asprintf()"); + return NULL; + } + } else { + char *tmp; + if (asprintf(&tmp, "%s/i3", dir) == -1) { + perror("asprintf()"); + return NULL; + } + dir = tmp; + } + if (!path_exists(dir)) { + if (mkdir(dir, 0700) == -1) { + perror("mkdir()"); + return NULL; + } + } char *filename; - int res = asprintf(&filename, "/tmp/%s-%s.%d", prefix, username, getpid()); - if (res == -1) { + if (asprintf(&filename, "%s/%s.%d", dir, prefix, getpid()) == -1) { perror("asprintf()"); - return NULL; + filename = NULL; } + free(dir); return filename; } @@ -275,7 +295,7 @@ char *store_restart_layout() { * resolve the tildes in the specified path */ char *filename; if (config.restart_state_path == NULL) { - filename = get_process_filename("i3-restart-state"); + filename = get_process_filename("restart-state"); if (!filename) return NULL; } else { From f67dd28cf0a020db9c90ebd7ab3ac492d11fbd62 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 26 Apr 2011 19:15:55 +0200 Subject: [PATCH 581/867] tests: add testcase for the different socket path locations --- testcases/t/59-socketpaths.t | 78 ++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 testcases/t/59-socketpaths.t diff --git a/testcases/t/59-socketpaths.t b/testcases/t/59-socketpaths.t new file mode 100644 index 00000000..3c9f90eb --- /dev/null +++ b/testcases/t/59-socketpaths.t @@ -0,0 +1,78 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3 +# +# Tests if the various ipc_socket_path options are correctly handled +# +use i3test; +use Cwd qw(abs_path); +use Proc::Background; +use File::Temp qw(tempfile tempdir); +use POSIX qw(getuid); +use v5.10; + +# assuming we are run by complete-run.pl +my $i3_path = abs_path("../i3"); + +##################################################################### +# default case: socket will be created in /tmp/i3-/ipc-socket. +##################################################################### + +my ($fh, $tmpfile) = tempfile(); +say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; +close($fh); + +diag("Starting i3"); +my $i3cmd = "unset XDG_RUNTIME_DIR; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null"; +my $process = Proc::Background->new($i3cmd); +sleep 1; + +diag("pid = " . $process->pid); + +my $folder = "/tmp/i3-" . getpwuid(getuid()); +ok(-d $folder, "folder $folder exists"); +my $socketpath = "$folder/ipc-socket." . $process->pid; +ok(-S $socketpath, "file $socketpath exists and is a socket"); + +kill(9, $process->pid) or die "could not kill i3"; + +##################################################################### +# XDG_RUNTIME_DIR case: socket gets created in $XDG_RUNTIME_DIR/i3/ipc-socket. +##################################################################### + +my $rtdir = tempdir(CLEANUP => 1); + +ok(! -e "$rtdir/i3", "$rtdir/i3 does not exist yet"); + +$i3cmd = "export XDG_RUNTIME_DIR=$rtdir; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null"; +$process = Proc::Background->new($i3cmd); +sleep 1; + +ok(-d "$rtdir/i3", "$rtdir/i3 exists and is a directory"); +$socketpath = "$rtdir/i3/ipc-socket." . $process->pid; +ok(-S $socketpath, "file $socketpath exists and is a socket"); + +kill(9, $process->pid) or die "could not kill i3"; + +##################################################################### +# configuration file case: socket gets placed whereever we specify +##################################################################### + +my $tmpdir = tempdir(CLEANUP => 1); +$socketpath = $tmpdir . "/config.sock"; +ok(! -e $socketpath, "$socketpath does not exist yet"); + +($fh, $tmpfile) = tempfile(); +say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; +say $fh "ipc-socket $socketpath"; +close($fh); + +$i3cmd = "export XDG_RUNTIME_DIR=$rtdir; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null"; +$process = Proc::Background->new($i3cmd); +sleep 1; + +ok(-S $socketpath, "file $socketpath exists and is a socket"); + +kill(9, $process->pid) or die "could not kill i3"; + +done_testing; From 4fc26e7de0ac92553a61daaa288f69ea35bf0873 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 26 Apr 2011 19:20:29 +0200 Subject: [PATCH 582/867] tests: make complete-run.pl scan tests for !NO_I3_INSTANCE!, simplify code --- testcases/complete-run.pl | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index ee21bd5d..9f201e1b 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -6,24 +6,26 @@ use warnings; use v5.10; use DateTime; use Data::Dumper; -use Cwd qw(abs_path getcwd); +use Cwd qw(abs_path); use Proc::Background; use TAP::Harness; use TAP::Parser::Aggregator; use File::Basename qw(basename); +# reads in a whole file +sub slurp { + open my $fh, '<', shift; + local $/; + <$fh>; +} + my $i3cmd = "export DISPLAY=:0; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c " . abs_path("../i3.config"); # 1: get a list of all testcases -my $curdir = getcwd(); my @testfiles = @ARGV; -# if no files were passed on command line, run all tests -if (@testfiles == 0) { - chdir "t"; - push @testfiles, "t/$_" while (<*.t>); - chdir $curdir; -} +# if no files were passed on command line, run all tests from t/ +@testfiles = if @testfiles == 0; # 2: create an output directory for this test-run my $outdir = "testsuite-"; @@ -44,11 +46,12 @@ $aggregator->start(); for my $t (@testfiles) { my $logpath = "$outdir/i3-log-for-" . basename($t); my $cmd = "$i3cmd >$logpath 2>&1"; + my $dont_start = (slurp($t) =~ /# !NO_I3_INSTANCE!/); - my $process = Proc::Background->new($cmd); + my $process = Proc::Background->new($cmd) unless $dont_start; say "testing $t with logfile $logpath"; $harness->aggregate_tests($aggregator, [ $t ]); - kill(9, $process->pid) or die "could not kill i3"; + kill(9, $process->pid) or die "could not kill i3" unless $dont_start; } $aggregator->stop(); From 3dd555239019036ca3c99d36402b3924b45feb27 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 26 Apr 2011 19:28:33 +0200 Subject: [PATCH 583/867] fix warning by removing 'const' from ipc_socket_path --- include/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/config.h b/include/config.h index 97da3dd2..c4fe6b08 100644 --- a/include/config.h +++ b/include/config.h @@ -89,7 +89,7 @@ struct Config { const char *terminal; i3Font font; - const char *ipc_socket_path; + char *ipc_socket_path; const char *restart_state_path; int default_layout; From 28b9ed6eb31015381a588a41c3878746fe0de2db Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 27 Apr 2011 10:18:46 +0200 Subject: [PATCH 584/867] Bugfix: Ensure that all outputs have a ->con before handling disabled outputs (Thanks JimdiGriz) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Steps to reproduce: • xrandr --output VGA1 --auto • xrandr --output LVDS1 --off --- src/randr.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/randr.c b/src/randr.c index 0c702db0..0c43670d 100644 --- a/src/randr.c +++ b/src/randr.c @@ -633,6 +633,18 @@ void randr_query_outputs() { } } + /* Ensure that all outputs which are active also have a con. This is + * necessary because in the next step, a clone might get disabled. Example: + * LVDS1 active, VGA1 gets activated as a clone of LVDS1 (has no con). + * LVDS1 gets disabled. */ + TAILQ_FOREACH(output, &outputs, outputs) { + if (output->active && output->con == NULL) { + DLOG("Need to initialize a Con for output %s\n", output->name); + output_init_con(output); + output->changed = false; + } + } + /* Handle outputs which have a new mode or are disabled now (either * because the user disabled them or because they are clones) */ TAILQ_FOREACH(output, &outputs, outputs) { @@ -706,12 +718,6 @@ void randr_query_outputs() { output->changed = false; } - if (output->active && output->con == NULL) { - DLOG("Need to initialize a Con for output %s\n", output->name); - output_init_con(output); - output->changed = false; - } - if (output->changed) { output_change_mode(conn, output); output->changed = false; From 528f486eeea2a2fa4cd3b0d4b0002e36babf2254 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 27 Apr 2011 19:52:53 +0200 Subject: [PATCH 585/867] Make code compatible with yajl 2.0 *and* 1.0 --- common.mk | 4 ++++ src/ipc.c | 40 ++++++++++++++++++++++++++++++- src/load_layout.c | 37 ++++++++++++++++++++++++---- src/util.c | 19 +++++++++++++-- yajl-fallback/yajl/yajl_version.h | 7 ++++++ 5 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 yajl-fallback/yajl/yajl_version.h diff --git a/common.mk b/common.mk index 182c12e5..86585e59 100644 --- a/common.mk +++ b/common.mk @@ -80,6 +80,10 @@ ifeq ($(UNAME),FreeBSD) LDFLAGS += -liconv endif +# Fallback for libyajl 1 which did not include yajl_version.h. We need +# YAJL_MAJOR from that file to decide which code path should be used. +CFLAGS += -idirafter yajl-fallback + ifneq (,$(filter Linux GNU GNU/%, $(UNAME))) CFLAGS += -D_GNU_SOURCE endif diff --git a/src/ipc.c b/src/ipc.c index 68d654da..27f89677 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009-2010 Michael Stapelberg and contributors + * © 2009-2011 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -17,6 +17,7 @@ #include #include #include +#include #include "all.h" @@ -282,12 +283,20 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { IPC_HANDLER(tree) { setlocale(LC_NUMERIC, "C"); +#if YAJL_MAJOR >= 2 + yajl_gen gen = yajl_gen_alloc(NULL); +#else yajl_gen gen = yajl_gen_alloc(NULL, NULL); +#endif dump_node(gen, croot, false); setlocale(LC_NUMERIC, ""); const unsigned char *payload; +#if YAJL_MAJOR >= 2 + size_t length; +#else unsigned int length; +#endif y(get_buf, &payload, &length); ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_TREE, length); @@ -300,7 +309,11 @@ IPC_HANDLER(tree) { * */ IPC_HANDLER(get_workspaces) { +#if YAJL_MAJOR >= 2 + yajl_gen gen = yajl_gen_alloc(NULL); +#else yajl_gen gen = yajl_gen_alloc(NULL, NULL); +#endif y(array_open); Con *focused_ws = con_get_workspace(focused); @@ -351,7 +364,11 @@ IPC_HANDLER(get_workspaces) { y(array_close); const unsigned char *payload; +#if YAJL_MAJOR >= 2 + size_t length; +#else unsigned int length; +#endif y(get_buf, &payload, &length); ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length); @@ -364,7 +381,11 @@ IPC_HANDLER(get_workspaces) { * */ IPC_HANDLER(get_outputs) { +#if YAJL_MAJOR >= 2 + yajl_gen gen = yajl_gen_alloc(NULL); +#else yajl_gen gen = yajl_gen_alloc(NULL, NULL); +#endif y(array_open); Output *output; @@ -401,7 +422,11 @@ IPC_HANDLER(get_outputs) { y(array_close); const unsigned char *payload; +#if YAJL_MAJOR >= 2 + size_t length; +#else unsigned int length; +#endif y(get_buf, &payload, &length); ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_OUTPUTS, length); @@ -412,8 +437,17 @@ IPC_HANDLER(get_outputs) { * Callback for the YAJL parser (will be called when a string is parsed). * */ +#if YAJL_MAJOR < 2 static int add_subscription(void *extra, const unsigned char *s, unsigned int len) { +#else +static int add_subscription(void *extra, const unsigned char *s, + size_t len) { + if (len < 0) { + DLOG("Invalid subscription with len %zd\n", len); + return 1; + } +#endif ipc_client *client = extra; DLOG("should add subscription to extra %p, sub %.*s\n", client, len, s); @@ -463,7 +497,11 @@ IPC_HANDLER(subscribe) { memset(&callbacks, 0, sizeof(yajl_callbacks)); callbacks.yajl_string = add_subscription; +#if YAJL_MAJOR >= 2 + p = yajl_alloc(&callbacks, NULL, (void*)client); +#else p = yajl_alloc(&callbacks, NULL, NULL, (void*)client); +#endif stat = yajl_parse(p, (const unsigned char*)message, message_size); if (stat != yajl_status_ok) { unsigned char *err; diff --git a/src/load_layout.c b/src/load_layout.c index 92cfecbd..c104ad2e 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "all.h" @@ -66,8 +67,16 @@ static int json_end_array(void *ctx) { return 1; } +#if YAJL_MAJOR < 2 static int json_key(void *ctx, const unsigned char *val, unsigned int len) { - LOG("key: %.*s\n", len, val); +#else +static int json_key(void *ctx, const unsigned char *val, size_t len) { + if (len < 0) { + LOG("Invalid key, len = %zd\n", len); + return 1; + } +#endif + LOG("key: %.*s\n", (int)len, val); FREE(last_key); last_key = scalloc((len+1) * sizeof(char)); memcpy(last_key, val, len); @@ -83,7 +92,15 @@ static int json_key(void *ctx, const unsigned char *val, unsigned int len) { return 1; } +#if YAJL_MAJOR >= 2 +static int json_string(void *ctx, const unsigned char *val, size_t len) { +#else static int json_string(void *ctx, const unsigned char *val, unsigned int len) { +#endif + if (len < 0) { + LOG("Invalid string for key %s\n", last_key); + return 1; + } LOG("string: %.*s for key %s\n", len, val, last_key); if (parsing_swallows) { /* TODO: the other swallowing keys */ @@ -102,7 +119,7 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) { LOG("sticky_group of this container is %s\n", json_node->sticky_group); } else if (strcasecmp(last_key, "orientation") == 0) { char *buf = NULL; - asprintf(&buf, "%.*s", len, val); + asprintf(&buf, "%.*s", (int)len, val); if (strcasecmp(buf, "none") == 0) json_node->orientation = NO_ORIENTATION; else if (strcasecmp(buf, "horizontal") == 0) @@ -116,7 +133,11 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) { return 1; } +#if YAJL_MAJOR >= 2 +static int json_int(void *ctx, long long val) { +#else static int json_int(void *ctx, long val) { +#endif LOG("int %d for key %s\n", val, last_key); if (strcasecmp(last_key, "layout") == 0) { json_node->layout = val; @@ -197,8 +218,13 @@ void tree_append_json(const char *filename) { callbacks.yajl_map_key = json_key; callbacks.yajl_integer = json_int; callbacks.yajl_double = json_double; +#if YAJL_MAJOR >= 2 + g = yajl_gen_alloc(NULL); + hand = yajl_alloc(&callbacks, NULL, (void*)g); +#else g = yajl_gen_alloc(NULL, NULL); hand = yajl_alloc(&callbacks, NULL, NULL, (void*)g); +#endif yajl_status stat; json_node = focused; to_focus = NULL; @@ -207,8 +233,7 @@ void tree_append_json(const char *filename) { parsing_geometry = false; setlocale(LC_NUMERIC, "C"); stat = yajl_parse(hand, (const unsigned char*)buf, n); - if (stat != yajl_status_ok && - stat != yajl_status_insufficient_data) + if (stat != yajl_status_ok) { unsigned char * str = yajl_get_error(hand, 1, (const unsigned char*)buf, n); fprintf(stderr, "%s\n", (const char *) str); @@ -216,7 +241,11 @@ void tree_append_json(const char *filename) { } setlocale(LC_NUMERIC, ""); +#if YAJL_MAJOR >= 2 + yajl_complete_parse(hand); +#else yajl_parse_complete(hand); +#endif fclose(f); if (to_focus) diff --git a/src/util.c b/src/util.c index a72b52d5..d9de98e8 100644 --- a/src/util.c +++ b/src/util.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009-2010 Michael Stapelberg and contributors + * © 2009-2011 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -18,6 +18,7 @@ #endif #include #include +#include #include "all.h" @@ -281,14 +282,22 @@ char *get_process_filename(const char *prefix) { char *store_restart_layout() { setlocale(LC_NUMERIC, "C"); +#if YAJL_MAJOR >= 2 + yajl_gen gen = yajl_gen_alloc(NULL); +#else yajl_gen gen = yajl_gen_alloc(NULL, NULL); +#endif dump_node(gen, croot, true); setlocale(LC_NUMERIC, ""); const unsigned char *payload; +#if YAJL_MAJOR >= 2 + size_t length; +#else unsigned int length; +#endif y(get_buf, &payload, &length); /* create a temporary file if one hasn't been specified, or just @@ -324,11 +333,17 @@ char *store_restart_layout() { return NULL; } written += n; +#if YAJL_MAJOR >= 2 + printf("written: %d of %zd\n", written, length); +#else printf("written: %d of %d\n", written, length); +#endif } close(fd); - printf("layout: %.*s\n", length, payload); + if (length > 0) { + printf("layout: %.*s\n", (int)length, payload); + } y(free); diff --git a/yajl-fallback/yajl/yajl_version.h b/yajl-fallback/yajl/yajl_version.h new file mode 100644 index 00000000..c6da442e --- /dev/null +++ b/yajl-fallback/yajl/yajl_version.h @@ -0,0 +1,7 @@ +#ifndef YAJL_VERSION_H_ +#define YAJL_VERSION_H_ +/* Fallback for libyajl 1 which does not provide yajl_version.h */ +#define YAJL_MAJOR 1 +#define YAJL_MINOR 0 +#define YAJL_MICRO 0 +#endif From 7cc3dae079229f6eb05a6c891f982536c6d457f9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 27 Apr 2011 20:04:34 +0200 Subject: [PATCH 586/867] Make code compatible with yajl 2.0 *and* 1.0 --- common.mk | 4 ++++ src/ipc.c | 23 ++++++++++++++++++++++- yajl-fallback/yajl/yajl_version.h | 7 +++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 yajl-fallback/yajl/yajl_version.h diff --git a/common.mk b/common.mk index e3a9f80b..d45286a8 100644 --- a/common.mk +++ b/common.mk @@ -73,6 +73,10 @@ ifeq ($(UNAME),FreeBSD) LDFLAGS += -liconv endif +# Fallback for libyajl 1 which did not include yajl_version.h. We need +# YAJL_MAJOR from that file to decide which code path should be used. +CFLAGS += -idirafter yajl-fallback + ifneq (,$(filter Linux GNU GNU/%, $(UNAME))) CFLAGS += -D_GNU_SOURCE endif diff --git a/src/ipc.c b/src/ipc.c index fcda355e..d2f0def0 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009-2010 Michael Stapelberg and contributors + * © 2009-2011 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -26,6 +26,7 @@ #include #include #include +#include #include "queue.h" #include "ipc.h" @@ -182,7 +183,11 @@ IPC_HANDLER(get_workspaces) { if (last_focused == SLIST_END(&(c_ws->focus_stack))) last_focused = NULL; +#if YAJL_MAJOR >= 2 + yajl_gen gen = yajl_gen_alloc(NULL); +#else yajl_gen gen = yajl_gen_alloc(NULL, NULL); +#endif y(array_open); TAILQ_FOREACH(ws, workspaces, workspaces) { @@ -226,7 +231,11 @@ IPC_HANDLER(get_workspaces) { y(array_close); const unsigned char *payload; +#if YAJL_MAJOR >= 2 + size_t length; +#else unsigned int length; +#endif y(get_buf, &payload, &length); ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length); @@ -241,7 +250,11 @@ IPC_HANDLER(get_workspaces) { IPC_HANDLER(get_outputs) { Output *output; +#if YAJL_MAJOR >= 2 + yajl_gen gen = yajl_gen_alloc(NULL); +#else yajl_gen gen = yajl_gen_alloc(NULL, NULL); +#endif y(array_open); TAILQ_FOREACH(output, &outputs, outputs) { @@ -276,7 +289,11 @@ IPC_HANDLER(get_outputs) { y(array_close); const unsigned char *payload; +#if YAJL_MAJOR >= 2 + size_t length; +#else unsigned int length; +#endif y(get_buf, &payload, &length); ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_OUTPUTS, length); @@ -338,7 +355,11 @@ IPC_HANDLER(subscribe) { memset(&callbacks, 0, sizeof(yajl_callbacks)); callbacks.yajl_string = add_subscription; +#if YAJL_MAJOR >= 2 + p = yajl_alloc(&callbacks, NULL, (void*)client); +#else p = yajl_alloc(&callbacks, NULL, NULL, (void*)client); +#endif stat = yajl_parse(p, (const unsigned char*)message, message_size); if (stat != yajl_status_ok) { unsigned char *err; diff --git a/yajl-fallback/yajl/yajl_version.h b/yajl-fallback/yajl/yajl_version.h new file mode 100644 index 00000000..c6da442e --- /dev/null +++ b/yajl-fallback/yajl/yajl_version.h @@ -0,0 +1,7 @@ +#ifndef YAJL_VERSION_H_ +#define YAJL_VERSION_H_ +/* Fallback for libyajl 1 which does not provide yajl_version.h */ +#define YAJL_MAJOR 1 +#define YAJL_MINOR 0 +#define YAJL_MICRO 0 +#endif From 2c6508a6a36f38e9db8c36249c0ce74f0aa5e92d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 28 Apr 2011 20:24:16 +0200 Subject: [PATCH 587/867] remove useless checks, size_t != ssize_t :) --- src/ipc.c | 4 ---- src/load_layout.c | 8 -------- 2 files changed, 12 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index 27f89677..e63718cb 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -443,10 +443,6 @@ static int add_subscription(void *extra, const unsigned char *s, #else static int add_subscription(void *extra, const unsigned char *s, size_t len) { - if (len < 0) { - DLOG("Invalid subscription with len %zd\n", len); - return 1; - } #endif ipc_client *client = extra; diff --git a/src/load_layout.c b/src/load_layout.c index c104ad2e..f946c666 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -71,10 +71,6 @@ static int json_end_array(void *ctx) { static int json_key(void *ctx, const unsigned char *val, unsigned int len) { #else static int json_key(void *ctx, const unsigned char *val, size_t len) { - if (len < 0) { - LOG("Invalid key, len = %zd\n", len); - return 1; - } #endif LOG("key: %.*s\n", (int)len, val); FREE(last_key); @@ -97,10 +93,6 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { #else static int json_string(void *ctx, const unsigned char *val, unsigned int len) { #endif - if (len < 0) { - LOG("Invalid string for key %s\n", last_key); - return 1; - } LOG("string: %.*s for key %s\n", len, val, last_key); if (parsing_swallows) { /* TODO: the other swallowing keys */ From a5bef3ab51cf4b6557688a7530f4de822cb5a32d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 28 Apr 2011 20:25:57 +0200 Subject: [PATCH 588/867] yajl compatibility: forgot add_subscription (Thanks badboy) --- src/ipc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ipc.c b/src/ipc.c index d2f0def0..3b4bb524 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -304,8 +304,13 @@ IPC_HANDLER(get_outputs) { * Callback for the YAJL parser (will be called when a string is parsed). * */ +#if YAJL_MAJOR >= 2 +static int add_subscription(void *extra, const unsigned char *s, + size_t len) { +#else static int add_subscription(void *extra, const unsigned char *s, unsigned int len) { +#endif ipc_client *client = extra; DLOG("should add subscription to extra %p, sub %.*s\n", client, len, s); From a1492839642fb109e2cca6ec2f747b246ad5ea65 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 28 Apr 2011 21:44:29 +0200 Subject: [PATCH 589/867] Fix unaligned memory access on sparc (Thanks David Coppa) --- src/ipc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index e63718cb..d0dc1920 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -597,7 +597,8 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { n -= strlen(I3_IPC_MAGIC); /* The next 32 bit after the magic are the message size */ - uint32_t message_size = *((uint32_t*)message); + uint32_t message_size; + memcpy(&message_size, (uint32_t*)message, sizeof(uint32_t)); message += sizeof(uint32_t); n -= sizeof(uint32_t); @@ -607,7 +608,8 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { } /* The last 32 bits of the header are the message type */ - uint32_t message_type = *((uint32_t*)message); + uint32_t message_type; + memcpy(&message_type, (uint32_t*)message, sizeof(uint32_t)); message += sizeof(uint32_t); n -= sizeof(uint32_t); From 1ddb1e6dfa8038fb87a926c730fc26a9951fa652 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 28 Apr 2011 21:47:14 +0200 Subject: [PATCH 590/867] Fix unaligned memory access on sparc (Thanks David Coppa) --- src/ipc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index 3b4bb524..13bc4ffb 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -463,7 +463,8 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { n -= strlen(I3_IPC_MAGIC); /* The next 32 bit after the magic are the message size */ - uint32_t message_size = *((uint32_t*)message); + uint32_t message_size; + memcpy(&message_size, (uint32_t*)message, sizeof(uint32_t)); message += sizeof(uint32_t); n -= sizeof(uint32_t); @@ -473,7 +474,8 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { } /* The last 32 bits of the header are the message type */ - uint32_t message_type = *((uint32_t*)message); + uint32_t message_type; + memcpy(&message_type, (uint32_t*)message, sizeof(uint32_t)); message += sizeof(uint32_t); n -= sizeof(uint32_t); From 8b21812bbd5c1e88f7d6248d0ba2360c396509e3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 30 Apr 2011 00:37:03 +0200 Subject: [PATCH 591/867] Bugfix: Add missing tree_render() when handling the urgency hint (Thanks mxf) --- src/handlers.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/handlers.c b/src/handlers.c index fbd660bc..2f954823 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -815,6 +815,8 @@ static int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w workspace_update_urgent_flag(con_get_workspace(con)); + tree_render(); + #if 0 /* If the workspace this client is on is not visible, we need to redraw * the workspace bar */ From 2491a155ee9ddc6dc52c0d9904c05e67facb2bea Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 1 May 2011 00:26:10 +0200 Subject: [PATCH 592/867] initial commit of the i3-config-wizard (GUI working, functionality incomplete) --- .gitignore | 8 +- i3-config-wizard/Makefile | 39 ++++ i3-config-wizard/cfgparse.l | 104 +++++++++++ i3-config-wizard/cfgparse.y | 153 ++++++++++++++++ i3-config-wizard/main.c | 355 ++++++++++++++++++++++++++++++++++++ i3-config-wizard/xcb.c | 166 +++++++++++++++++ i3-config-wizard/xcb.h | 6 + 7 files changed, 827 insertions(+), 4 deletions(-) create mode 100644 i3-config-wizard/Makefile create mode 100644 i3-config-wizard/cfgparse.l create mode 100644 i3-config-wizard/cfgparse.y create mode 100644 i3-config-wizard/main.c create mode 100644 i3-config-wizard/xcb.c create mode 100644 i3-config-wizard/xcb.h diff --git a/.gitignore b/.gitignore index e3034ec3..d1555b0d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ loglevels.tmp *.swp testcases/testsuite-* testcases/latest -src/*.output -src/*.tab.c -src/*.tab.h -src/*.yy.c +*.output +*.tab.c +*.tab.h +*.yy.c diff --git a/i3-config-wizard/Makefile b/i3-config-wizard/Makefile new file mode 100644 index 00000000..44715766 --- /dev/null +++ b/i3-config-wizard/Makefile @@ -0,0 +1,39 @@ +# Default value so one can compile i3-input standalone +TOPDIR=.. + +include $(TOPDIR)/common.mk + +# Depend on the object files of all source-files in src/*.c and on all header files +FILES:=$(patsubst %.c,%.o,$(wildcard *.c)) +HEADERS:=$(wildcard *.h) + +# Depend on the specific file (.c for each .o) and on all headers +%.o: %.c ${HEADERS} + echo "CC $<" + $(CC) $(CFLAGS) -c -o $@ $< + +all: cfgparse.y.o cfgparse.yy.o ${FILES} + echo "LINK i3-config-wizard" + $(CC) -o i3-config-wizard ${FILES} $(LDFLAGS) + +cfgparse.yy.o: cfgparse.l cfgparse.y.o ${HEADERS} + echo "LEX $<" + flex -i -o$(@:.o=.c) $< + $(CC) $(CFLAGS) -c -o $@ $(@:.o=.c) + +cfgparse.y.o: cfgparse.y ${HEADERS} + echo "YACC $<" + bison --debug --verbose -b $(basename $< .y) -d $< + $(CC) $(CFLAGS) -c -o $@ $(<:.y=.tab.c) + + +install: all + echo "INSTALL" + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 i3-config-wizard $(DESTDIR)$(PREFIX)/bin/ + +clean: + rm -f *.o + +distclean: clean + rm -f i3-config-wizard diff --git a/i3-config-wizard/cfgparse.l b/i3-config-wizard/cfgparse.l new file mode 100644 index 00000000..d94abe2a --- /dev/null +++ b/i3-config-wizard/cfgparse.l @@ -0,0 +1,104 @@ +%option nounput +%option noinput +%option noyy_top_state +%option stack + +%{ +/* + * vim:ts=8:expandtab + * + */ +#include +#include +#include +#include +#include "cfgparse.tab.h" + +int yycolumn = 1; + +struct context { + int line_number; + char *line_copy; + + char *compact_error; + + /* These are the same as in YYLTYPE */ + int first_column; + int last_column; +}; + + +#define YY_DECL int yylex (struct context *context) + +#define YY_USER_ACTION { \ + context->first_column = yycolumn; \ + context->last_column = yycolumn+yyleng-1; \ + yycolumn += yyleng; \ +} + +%} + +EOL (\r?\n) + +%s BINDCODE_COND +%s BIND_AWS_COND +%s BIND_A2WS_COND +%x BUFFER_LINE + +%% + + { + /* This is called when a new line is lexed. We only want the + * first line to match to go into state BUFFER_LINE */ + if (context->line_number == 0) { + context->line_number = 1; + BEGIN(INITIAL); + yy_push_state(BUFFER_LINE); + } + } + +^[^\r\n]*/{EOL}? { + /* save whole line */ + context->line_copy = strdup(yytext); + + yyless(0); + yy_pop_state(); + yy_set_bol(true); + yycolumn = 1; +} + + +[^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; } +[0-9]+ { yylval.number = atoi(yytext); return NUMBER; } +bind { BEGIN(BINDCODE_COND); return TOKBINDCODE; } +bindcode { BEGIN(BINDCODE_COND); return TOKBINDCODE; } +Mod1 { yylval.number = (1 << 3); return MODIFIER; } +Mod2 { yylval.number = (1 << 4); return MODIFIER; } +Mod3 { yylval.number = (1 << 5); return MODIFIER; } +Mod4 { yylval.number = (1 << 6); return MODIFIER; } +Mod5 { yylval.number = (1 << 7); return MODIFIER; } +Mode_switch { yylval.number = (1 << 8); return MODIFIER; } +control { return TOKCONTROL; } +ctrl { return TOKCONTROL; } +shift { return TOKSHIFT; } +{EOL} { + if (context->line_copy) { + free(context->line_copy); + context->line_copy = NULL; + } + context->line_number++; + BEGIN(INITIAL); + yy_push_state(BUFFER_LINE); + } +[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; } +[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } +[ \t]+ { return WHITESPACE; } +. { return (int)yytext[0]; } + +<> { + while (yy_start_stack_ptr > 0) + yy_pop_state(); + yyterminate(); +} + +%% diff --git a/i3-config-wizard/cfgparse.y b/i3-config-wizard/cfgparse.y new file mode 100644 index 00000000..85e57bfb --- /dev/null +++ b/i3-config-wizard/cfgparse.y @@ -0,0 +1,153 @@ +%{ +/* + * vim:ts=4:sw=4:expandtab + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +extern Display *dpy; + +struct context { + int line_number; + char *line_copy; + + char *compact_error; + + /* These are the same as in YYLTYPE */ + int first_column; + int last_column; + + char *result; +}; + +typedef struct yy_buffer_state *YY_BUFFER_STATE; +extern int yylex(struct context *context); +extern int yyparse(void); +extern FILE *yyin; +YY_BUFFER_STATE yy_scan_string(const char *); + +static struct context *context; + +/* We don’t need yydebug for now, as we got decent error messages using + * yyerror(). Should you ever want to extend the parser, it might be handy + * to just comment it in again, so it stays here. */ +//int yydebug = 1; + +void yyerror(const char *error_message) { + fprintf(stderr, "\n"); + fprintf(stderr, "CONFIG: %s\n", error_message); + fprintf(stderr, "CONFIG: line %d:\n", + context->line_number); + fprintf(stderr, "CONFIG: %s\n", context->line_copy); + fprintf(stderr, "CONFIG: "); + for (int c = 1; c <= context->last_column; c++) + if (c >= context->first_column) + fprintf(stderr, "^"); + else fprintf(stderr, " "); + fprintf(stderr, "\n"); + fprintf(stderr, "\n"); +} + +int yywrap() { + return 1; +} + +char *rewrite_binding(const char *bindingline) { + char *result = NULL; + + context = calloc(sizeof(struct context), 1); + + yy_scan_string(bindingline); + + if (yyparse() != 0) { + fprintf(stderr, "Could not parse configfile\n"); + exit(1); + } + + result = context->result; + + if (context->line_copy) + free(context->line_copy); + free(context); + + return result; +} + +/* XXX: does not work for combinations of modifiers yet */ +static char *modifier_to_string(int modifiers) { + //printf("should convert %d to string\n", modifiers); + if (modifiers == (1 << 3)) + return strdup("Mod1"); + else if (modifiers == ((1 << 3) | (1 << 0))) + return strdup("Mod1+shift"); + else return strdup("UNKNOWN"); +} + +%} + +%error-verbose +%lex-param { struct context *context } + +%union { + int number; + char *string; +} + +%token NUMBER "" +%token STR "" +%token TOKBINDCODE +%token MODIFIER "" +%token TOKCONTROL "control" +%token TOKSHIFT "shift" +%token WHITESPACE "" + +%% + +lines: /* empty */ + | lines WHITESPACE bindcode + | lines error + | lines bindcode + ; + +bindcode: + TOKBINDCODE WHITESPACE binding_modifiers NUMBER WHITESPACE STR + { + //printf("\tFound keycode binding mod%d with key %d and command %s\n", $3, $4, $6); + int level = 0; + if (($3 & (1 << 0))) { + /* When shift is included, we really need to use the second-level + * symbol (upper-case). The lower-case symbol could be on a + * different key than the upper-case one (unlikely for letters, but + * more likely for special characters). */ + level = 1; + } + KeySym sym = XKeycodeToKeysym(dpy, $4, level); + char *str = XKeysymToString(sym); + char *modifiers = modifier_to_string($3); + // TODO: modifier to string + asprintf(&(context->result), "bindsym %s+%s %s\n", modifiers, str, $6); + free(modifiers); + } + ; + +binding_modifiers: + /* NULL */ { $$ = 0; } + | binding_modifier + | binding_modifiers '+' binding_modifier { $$ = $1 | $3; } + | binding_modifiers '+' { $$ = $1; } + ; + +binding_modifier: + MODIFIER { $$ = $1; } + | TOKCONTROL { $$ = (1 << 2); } + | TOKSHIFT { $$ = (1 << 0); } + ; diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c new file mode 100644 index 00000000..d50669ed --- /dev/null +++ b/i3-config-wizard/main.c @@ -0,0 +1,355 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2011 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * i3-config-wizard: Program to convert configs using keycodes to configs using + * keysyms. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#define FREE(pointer) do { \ + if (pointer != NULL) { \ + free(pointer); \ + pointer = NULL; \ + } \ +} \ +while (0) + +#include "xcb.h" + +enum { STEP_WELCOME, STEP_GENERATE } current_step = STEP_WELCOME; +enum { MOD_ALT, MOD_SUPER } modifier = MOD_SUPER; + +static xcb_connection_t *conn; +static uint32_t font_id; +static uint32_t font_bold_id; +static char *socket_path; +static int sockfd; +static int font_height; +static int font_bold_height; +static xcb_window_t win; +static xcb_pixmap_t pixmap; +static xcb_gcontext_t pixmap_gc; +static xcb_key_symbols_t *symbols; +xcb_window_t root; +Display *dpy; + +static void finish(); + +/* + * Try to get the socket path from X11 and return NULL if it doesn’t work. + * As i3-msg is a short-running tool, we don’t bother with cleaning up the + * connection and leave it up to the operating system on exit. + * + */ +static char *socket_path_from_x11() { + xcb_connection_t *conn; + int screen; + if ((conn = xcb_connect(NULL, &screen)) == NULL || + xcb_connection_has_error(conn)) + return NULL; + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen); + xcb_window_t root = root_screen->root; + + xcb_intern_atom_cookie_t atom_cookie; + xcb_intern_atom_reply_t *atom_reply; + + atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH"); + atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL); + if (atom_reply == NULL) + return NULL; + + xcb_get_property_cookie_t prop_cookie; + xcb_get_property_reply_t *prop_reply; + prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX); + prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0) + return NULL; + if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply), + (char*)xcb_get_property_value(prop_reply)) == -1) + return NULL; + return socket_path; +} + +/* + * Handles expose events, that is, draws the window contents. + * + */ +static int handle_expose() { + /* re-draw the background */ + xcb_rectangle_t border = {0, 0, 300, (15*font_height) + 8}, + inner = {2, 2, 296, (15*font_height) + 8 - 4}; + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#285577")); + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); + + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id); + +#define txt(x, row, text) xcb_image_text_8(conn, strlen(text), pixmap, pixmap_gc, x, (row * font_height) + 2, text) + + if (current_step == STEP_WELCOME) { + /* restore font color */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF")); + + txt(10, 1, "i3: first configuration"); + txt(10, 4, "You have not configured i3 yet."); + txt(10, 5, "Do you want me to generate ~/.i3/config?"); + txt(85, 8, "Yes, generate ~/.i3/config"); + txt(85, 10, "No, I will use the defaults"); + + /* green */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#00FF00")); + txt(25, 8, ""); + + /* red */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000")); + txt(31, 10, ""); + } + + if (current_step == STEP_GENERATE) { + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF")); + + txt(10, 1, "i3: generate config"); + + txt(10, 4, "Please choose either:"); + txt(85, 6, "Win as default modifier"); + txt(85, 7, "Alt as default modifier"); + txt(10, 9, "Afterwards, press"); + txt(85, 11, "to write ~/.i3/config"); + txt(85, 12, "to abort"); + + /* the not-selected modifier */ + if (modifier == MOD_SUPER) + txt(31, 7, ""); + else txt(31, 6, ""); + + /* the selected modifier */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_bold_id); + if (modifier == MOD_SUPER) + txt(31, 6, ""); + else txt(31, 7, ""); + + /* green */ + uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_FONT; + uint32_t values[] = { get_colorpixel(conn, "#00FF00"), font_id }; + xcb_change_gc(conn, pixmap_gc, mask, values); + + txt(25, 11, ""); + + /* red */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000")); + txt(31, 12, ""); + } + + /* Copy the contents of the pixmap to the real window */ + xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, 500); + xcb_flush(conn); + + return 1; +} + +static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) { + printf("Keypress %d, state raw = %d\n", event->detail, event->state); + + xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state); + + printf("sym = %c (%d)\n", sym, sym); + + if (sym == XK_Return) { + if (current_step == STEP_WELCOME) + current_step = STEP_GENERATE; + else finish(); + } + + /* cancel any time */ + if (sym == XK_Escape) + exit(0); + + if (sym == XK_Alt_L) + modifier = MOD_ALT; + + if (sym == XK_Super_L) + modifier = MOD_SUPER; + + handle_expose(); + return 1; +} + +static void finish() { + printf("finishing the wizard\n"); + +#if 0 + dpy = XOpenDisplay(NULL); + + FILE *kc_config = fopen("../i3.config.kc", "r"); + char *line = NULL; + size_t len = 0; + ssize_t read; + while ((read = getline(&line, &len, kc_config)) != -1) { + /* See if that line is interesting by skipping leading whitespaces, + * then checking for 'bindcode' */ + char *walk = line; + while (isspace(*walk) && walk < (line + len)) + walk++; + if (strncmp(walk, "bindcode", strlen("bindcode")) != 0) + continue; + char *result = rewrite_binding(walk); + printf("in: %s", walk); + printf("out: %s", result); + free(result); + + } + + free(line); + fclose(kc_config); + + exit(0); +#endif + + exit(0); +} + +int main(int argc, char *argv[]) { + socket_path = getenv("I3SOCK"); + char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; + char *patternbold = "-misc-fixed-bold-r-normal--13-120-75-75-C-70-iso10646-1"; + int o, option_index = 0; + + static struct option long_options[] = { + {"socket", required_argument, 0, 's'}, + {"version", no_argument, 0, 'v'}, + {"limit", required_argument, 0, 'l'}, + {"prompt", required_argument, 0, 'P'}, + {"prefix", required_argument, 0, 'p'}, + {"font", required_argument, 0, 'f'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + char *options_string = "s:vh"; + + while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { + switch (o) { + case 's': + FREE(socket_path); + socket_path = strdup(optarg); + break; + case 'v': + printf("i3-config-wizard " I3_VERSION); + return 0; + case 'h': + printf("i3-config-wizard " I3_VERSION); + printf("i3-config-wizard [-s ] [-v]\n"); + return 0; + } + } + + if (socket_path == NULL) + socket_path = socket_path_from_x11(); + + if (socket_path == NULL) + socket_path = "/tmp/i3-ipc.sock"; + + int screens; + conn = xcb_connect(NULL, &screens); + if (xcb_connection_has_error(conn)) + errx(1, "Cannot open display\n"); + + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); + root = root_screen->root; + + symbols = xcb_key_symbols_alloc(conn); + + font_id = get_font_id(conn, pattern, &font_height); + font_bold_id = get_font_id(conn, patternbold, &font_bold_height); + + /* Open an input window */ + win = open_input_window(conn, 300, 205); + + /* Create pixmap */ + pixmap = xcb_generate_id(conn); + pixmap_gc = xcb_generate_id(conn); + xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, 500); + xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + + /* Set input focus (we have override_redirect=1, so the wm will not do + * this for us) */ + xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME); + + /* Grab the keyboard to get all input */ + xcb_flush(conn); + + /* Try (repeatedly, if necessary) to grab the keyboard. We might not + * get the keyboard at the first attempt because of the keybinding + * still being active when started via a wm’s keybinding. */ + xcb_grab_keyboard_cookie_t cookie; + xcb_grab_keyboard_reply_t *reply = NULL; + + int count = 0; + while ((reply == NULL || reply->status != XCB_GRAB_STATUS_SUCCESS) && (count++ < 500)) { + cookie = xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + reply = xcb_grab_keyboard_reply(conn, cookie, NULL); + usleep(1000); + } + + if (reply->status != XCB_GRAB_STATUS_SUCCESS) { + fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status); + exit(-1); + } + + xcb_flush(conn); + + xcb_generic_event_t *event; + while ((event = xcb_wait_for_event(conn)) != NULL) { + if (event->response_type == 0) { + fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence); + continue; + } + + /* Strip off the highest bit (set if the event is generated) */ + int type = (event->response_type & 0x7F); + + switch (type) { + case XCB_KEY_PRESS: + handle_key_press(NULL, conn, (xcb_key_press_event_t*)event); + break; + + /* TODO: handle mappingnotify */ + + case XCB_EXPOSE: + handle_expose(); + break; + } + + free(event); + } + + return 0; +} diff --git a/i3-config-wizard/xcb.c b/i3-config-wizard/xcb.c new file mode 100644 index 00000000..9a9bf615 --- /dev/null +++ b/i3-config-wizard/xcb.c @@ -0,0 +1,166 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include +#include +#include +#include +#include + +#include +#include + +#include + +extern xcb_window_t root; + +/* + * Convenience-wrapper around xcb_change_gc which saves us declaring a variable + * + */ +void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) { + xcb_change_gc(conn, gc, mask, &value); +} + +/* + * Returns the colorpixel to use for the given hex color (think of HTML). + * + * The hex_color has to start with #, for example #FF00FF. + * + * NOTE that get_colorpixel() does _NOT_ check the given color code for validity. + * This has to be done by the caller. + * + */ +uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) { + char strgroups[3][3] = {{hex[1], hex[2], '\0'}, + {hex[3], hex[4], '\0'}, + {hex[5], hex[6], '\0'}}; + uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)), + (strtol(strgroups[1], NULL, 16)), + (strtol(strgroups[2], NULL, 16))}; + + return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2]; +} + +/* + * Returns the mask for Mode_switch (to be used for looking up keysymbols by + * keycode). + * + */ +uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode) { + xcb_key_symbols_t *symbols = xcb_key_symbols_alloc(conn); + + xcb_get_modifier_mapping_reply_t *modmap_r; + xcb_keycode_t *modmap, kc; + xcb_keycode_t *modeswitchcodes = xcb_key_symbols_get_keycode(symbols, keycode); + if (modeswitchcodes == NULL) + return 0; + + modmap_r = xcb_get_modifier_mapping_reply(conn, xcb_get_modifier_mapping(conn), NULL); + modmap = xcb_get_modifier_mapping_keycodes(modmap_r); + + for (int i = 0; i < 8; i++) + for (int j = 0; j < modmap_r->keycodes_per_modifier; j++) { + kc = modmap[i * modmap_r->keycodes_per_modifier + j]; + for (xcb_keycode_t *ktest = modeswitchcodes; *ktest; ktest++) { + if (*ktest != kc) + continue; + + free(modeswitchcodes); + free(modmap_r); + return (1 << i); + } + } + + return 0; +} + +/* + * Opens the window we use for input/output and maps it + * + */ +xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) { + xcb_window_t win = xcb_generate_id(conn); + //xcb_cursor_t cursor_id = xcb_generate_id(conn); + +#if 0 + /* Use the default cursor (left pointer) */ + if (cursor > -1) { + i3Font *cursor_font = load_font(conn, "cursor"); + xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id, + XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1, + 0, 0, 0, 65535, 65535, 65535); + } +#endif + + uint32_t mask = 0; + uint32_t values[3]; + + mask |= XCB_CW_BACK_PIXEL; + values[0] = 0; + + mask |= XCB_CW_OVERRIDE_REDIRECT; + values[1] = 1; + + mask |= XCB_CW_EVENT_MASK; + values[2] = XCB_EVENT_MASK_EXPOSURE; + + xcb_create_window(conn, + XCB_COPY_FROM_PARENT, + win, /* the window id */ + root, /* parent == root */ + 490, 297, width, height, /* dimensions */ + 0, /* border = 0, we draw our own */ + XCB_WINDOW_CLASS_INPUT_OUTPUT, + XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ + mask, + values); + +#if 0 + if (cursor > -1) + xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id); +#endif + + /* Map the window (= make it visible) */ + xcb_map_window(conn, win); + + return win; +} + +/* + * Returns the ID of the font matching the given pattern and stores the height + * of the font (in pixels) in *font_height. die()s if no font matches. + * + */ +int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height) { + xcb_void_cookie_t font_cookie; + xcb_list_fonts_with_info_cookie_t info_cookie; + + /* Send all our requests first */ + int result; + result = xcb_generate_id(conn); + font_cookie = xcb_open_font_checked(conn, result, strlen(pattern), pattern); + info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern); + + xcb_generic_error_t *error = xcb_request_check(conn, font_cookie); + if (error != NULL) { + fprintf(stderr, "ERROR: Could not open font: %d\n", error->error_code); + exit(1); + } + + /* Get information (height/name) for this font */ + xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL); + if (reply == NULL) + errx(1, "Could not load font \"%s\"\n", pattern); + + *font_height = reply->font_ascent + reply->font_descent; + + return result; +} diff --git a/i3-config-wizard/xcb.h b/i3-config-wizard/xcb.h new file mode 100644 index 00000000..71280e1c --- /dev/null +++ b/i3-config-wizard/xcb.h @@ -0,0 +1,6 @@ + +void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value); +uint32_t get_colorpixel(xcb_connection_t *conn, char *hex); +uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode); +xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height); +int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height); From 9101f4cce2d23847a498733b2ea0792ab24cb27f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 1 May 2011 12:50:18 +0200 Subject: [PATCH 593/867] wizard: check if the config file does not already exist and if we can create it --- i3-config-wizard/main.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index d50669ed..b546f607 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include @@ -45,6 +47,7 @@ while (0) enum { STEP_WELCOME, STEP_GENERATE } current_step = STEP_WELCOME; enum { MOD_ALT, MOD_SUPER } modifier = MOD_SUPER; +static char *config_path = "/tmp/wizout/i3.config"; static xcb_connection_t *conn; static uint32_t font_id; static uint32_t font_bold_id; @@ -271,6 +274,22 @@ int main(int argc, char *argv[]) { } } + /* 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); + return 0; + } + + int fd; + if ((fd = open(config_path, O_CREAT | O_RDWR, 0644)) == -1) { + printf("Cannot open file \"%s\" for writing: %s. Exiting.\n", config_path, strerror(errno)); + return 0; + } + close(fd); + unlink(config_path); + if (socket_path == NULL) socket_path = socket_path_from_x11(); From 43ec3ddbafe93c7562929de280fd4e2bb97dfa1b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 1 May 2011 13:56:35 +0200 Subject: [PATCH 594/867] wizard: actually write the output config --- i3-config-wizard/cfgparse.l | 1 + i3-config-wizard/cfgparse.y | 6 +++ i3-config-wizard/ipc.c | 68 +++++++++++++++++++++++++++++++++ i3-config-wizard/ipc.h | 9 +++++ i3-config-wizard/main.c | 76 ++++++++++++++++++++++++++++++------- 5 files changed, 146 insertions(+), 14 deletions(-) create mode 100644 i3-config-wizard/ipc.c create mode 100644 i3-config-wizard/ipc.h diff --git a/i3-config-wizard/cfgparse.l b/i3-config-wizard/cfgparse.l index d94abe2a..772b8470 100644 --- a/i3-config-wizard/cfgparse.l +++ b/i3-config-wizard/cfgparse.l @@ -78,6 +78,7 @@ Mod3 { yylval.number = (1 << 5); return MODIFIER; } Mod4 { yylval.number = (1 << 6); return MODIFIER; } Mod5 { yylval.number = (1 << 7); return MODIFIER; } Mode_switch { yylval.number = (1 << 8); return MODIFIER; } +$mod { yylval.number = (1 << 9); return TOKMODVAR; } control { return TOKCONTROL; } ctrl { return TOKCONTROL; } shift { return TOKSHIFT; } diff --git a/i3-config-wizard/cfgparse.y b/i3-config-wizard/cfgparse.y index 85e57bfb..f2e4a054 100644 --- a/i3-config-wizard/cfgparse.y +++ b/i3-config-wizard/cfgparse.y @@ -89,6 +89,10 @@ static char *modifier_to_string(int modifiers) { return strdup("Mod1"); else if (modifiers == ((1 << 3) | (1 << 0))) return strdup("Mod1+shift"); + else if (modifiers == (1 << 9)) + return strdup("$mod"); + else if (modifiers == ((1 << 9) | (1 << 0))) + return strdup("$mod+shift"); else return strdup("UNKNOWN"); } @@ -105,6 +109,7 @@ static char *modifier_to_string(int modifiers) { %token NUMBER "" %token STR "" %token TOKBINDCODE +%token TOKMODVAR "$mod" %token MODIFIER "" %token TOKCONTROL "control" %token TOKSHIFT "shift" @@ -148,6 +153,7 @@ binding_modifiers: binding_modifier: MODIFIER { $$ = $1; } + | TOKMODVAR { $$ = $1; } | TOKCONTROL { $$ = (1 << 2); } | TOKSHIFT { $$ = (1 << 0); } ; diff --git a/i3-config-wizard/ipc.c b/i3-config-wizard/ipc.c new file mode 100644 index 00000000..597a86ef --- /dev/null +++ b/i3-config-wizard/ipc.c @@ -0,0 +1,68 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include +#include +#include +#include +#include +#include +#include + +/* + * Formats a message (payload) of the given size and type and sends it to i3 via + * the given socket file descriptor. + * + */ +void ipc_send_message(int sockfd, uint32_t message_size, + uint32_t message_type, uint8_t *payload) { + int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + sizeof(uint32_t) + message_size; + char msg[buffer_size]; + char *walk = msg; + + strcpy(walk, "i3-ipc"); + walk += strlen("i3-ipc"); + memcpy(walk, &message_size, sizeof(uint32_t)); + walk += sizeof(uint32_t); + memcpy(walk, &message_type, sizeof(uint32_t)); + walk += sizeof(uint32_t); + memcpy(walk, payload, message_size); + + int sent_bytes = 0; + int bytes_to_go = buffer_size; + while (sent_bytes < bytes_to_go) { + int n = write(sockfd, msg + sent_bytes, bytes_to_go); + if (n == -1) + err(EXIT_FAILURE, "write() failed"); + + sent_bytes += n; + bytes_to_go -= n; + } +} + +/* + * Connects to the i3 IPC socket and returns the file descriptor for the + * socket. die()s if anything goes wrong. + * + */ +int connect_ipc(char *socket_path) { + int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (sockfd == -1) + err(EXIT_FAILURE, "Could not create socket"); + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_LOCAL; + strcpy(addr.sun_path, socket_path); + if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) + err(EXIT_FAILURE, "Could not connect to i3"); + + return sockfd; +} diff --git a/i3-config-wizard/ipc.h b/i3-config-wizard/ipc.h new file mode 100644 index 00000000..c40c909d --- /dev/null +++ b/i3-config-wizard/ipc.h @@ -0,0 +1,9 @@ +#ifndef _IPC_H +#define _IPC_H + +void ipc_send_message(int sockfd, uint32_t message_size, + uint32_t message_type, uint8_t *payload); + +int connect_ipc(char *socket_path); + +#endif diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index b546f607..a4dd070c 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,7 @@ while (0) #include "xcb.h" +#include "ipc.h" enum { STEP_WELCOME, STEP_GENERATE } current_step = STEP_WELCOME; enum { MOD_ALT, MOD_SUPER } modifier = MOD_SUPER; @@ -52,7 +54,6 @@ static xcb_connection_t *conn; static uint32_t font_id; static uint32_t font_bold_id; static char *socket_path; -static int sockfd; static int font_height; static int font_bold_height; static xcb_window_t win; @@ -62,6 +63,7 @@ static xcb_key_symbols_t *symbols; xcb_window_t root; Display *dpy; +char *rewrite_binding(const char *bindingline); static void finish(); /* @@ -205,36 +207,82 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press return 1; } +/* + * Creates the config file and tells i3 to reload. + * + */ static void finish() { - printf("finishing the wizard\n"); + printf("creating \"%s\"...\n", config_path); -#if 0 - dpy = XOpenDisplay(NULL); + if (!(dpy = XOpenDisplay(NULL))) + errx(1, "Could not connect to X11"); FILE *kc_config = fopen("../i3.config.kc", "r"); + if (kc_config == NULL) + err(1, "Could not open input file \"%s\"", "../i3.config.kc"); + + FILE *ks_config = fopen(config_path, "w"); + if (ks_config == NULL) + err(1, "Could not open output config file \"%s\"", config_path); + char *line = NULL; size_t len = 0; ssize_t read; + bool head_of_file = true; + + /* write a header about auto-generation to the output file */ + fputs("# This file has been auto-generated by i3-config-wizard(1).\n", ks_config); + fputs("# It will not be overwritten, so edit it as you like.\n", ks_config); + fputs("#\n", ks_config); + fputs("# Should you change your keyboard layout somewhen, delete\n", ks_config); + fputs("# this file and re-run i3-config-wizard(1).\n", ks_config); + fputs("#\n", ks_config); + fputs("# See http://i3wm.org/docs/userguide.html\n", ks_config); + while ((read = getline(&line, &len, kc_config)) != -1) { - /* See if that line is interesting by skipping leading whitespaces, - * then checking for 'bindcode' */ + /* skip the warning block at the beginning of the input file */ + if (head_of_file && + strncmp("# WARNING", line, strlen("# WARNING")) == 0) + continue; + + head_of_file = false; + + /* Skip leading whitespace */ char *walk = line; while (isspace(*walk) && walk < (line + len)) walk++; - if (strncmp(walk, "bindcode", strlen("bindcode")) != 0) - continue; - char *result = rewrite_binding(walk); - printf("in: %s", walk); - printf("out: %s", result); - free(result); + /* Set the modifier the user chose */ + if (strncmp(walk, "set $mod ", strlen("set $mod ")) == 0) { + if (modifier == MOD_ALT) + fputs("set $mod Mod1\n", ks_config); + else fputs("set $mod Mod4\n", ks_config); + continue; + } + + /* Check for 'bindcode'. If it’s not a bindcode line, we + * just copy it to the output file */ + if (strncmp(walk, "bindcode", strlen("bindcode")) != 0) { + fputs(line, ks_config); + continue; + } + char *result = rewrite_binding(walk); + fputs(result, ks_config); + free(result); } + /* sync to do our best in order to have the file really stored on disk */ + fflush(ks_config); + fsync(fileno(ks_config)); + free(line); fclose(kc_config); + fclose(ks_config); - exit(0); -#endif + /* tell i3 to reload the config file */ + int sockfd = connect_ipc(socket_path); + ipc_send_message(sockfd, strlen("reload"), 0, (uint8_t*)"reload"); + close(sockfd); exit(0); } From d28008aa639d1f971ee5874a6e20ddf2c9c8bfa9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 1 May 2011 18:48:30 +0200 Subject: [PATCH 595/867] =?UTF-8?q?Bugfix:=20Correctly=20render=20decorati?= =?UTF-8?q?ons=20in=20tabbed=20containers=20(don=E2=80=99t=20overlap)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a regression introduced in b644fb5f26c1768b70c5b49d8cd917a63a2d1d91. --- include/x.h | 2 +- src/floating.c | 2 +- src/x.c | 26 +++++++++++++++++++++----- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/include/x.h b/include/x.h index 827f6f85..d110d92c 100644 --- a/include/x.h +++ b/include/x.h @@ -66,7 +66,7 @@ void x_draw_decoration(Con *con); * It recursively traverses all children of the given node. * */ -void x_push_node(Con *con, bool skip_decoration); +void x_push_node(Con *con); /** * Pushes all changes (state of each node, see x_push_node() and the window diff --git a/src/floating.c b/src/floating.c index 1d08ec9e..23f8a60a 100644 --- a/src/floating.c +++ b/src/floating.c @@ -277,7 +277,7 @@ DRAGGING_CB(drag_window_callback) { con->rect.y = old_rect->y + (new_y - event->root_y); render_con(con, false); - x_push_node(con, true); + x_push_node(con); xcb_flush(conn); /* Check if we cross workspace boundaries while moving */ diff --git a/src/x.c b/src/x.c index ab60c51d..2e77b143 100644 --- a/src/x.c +++ b/src/x.c @@ -442,7 +442,7 @@ update_pixmaps: * It recursively traverses all children of the given node. * */ -void x_push_node(Con *con, bool skip_decoration) { +void x_push_node(Con *con) { Con *current; con_state *state; Rect rect = con->rect; @@ -584,10 +584,25 @@ void x_push_node(Con *con, bool skip_decoration) { * in focus order to display the focused client in a stack first when * switching workspaces (reduces flickering). */ TAILQ_FOREACH(current, &(con->focus_head), focused) - x_push_node(current, skip_decoration); + x_push_node(current); +} - if (!skip_decoration && - (con->type != CT_ROOT && con->type != CT_OUTPUT) && +/* + * Recursively calls x_draw_decoration. This cannot be done in x_push_node + * because x_push_node uses focus order to recurse (see the comment above) + * while drawing the decoration needs to happen in the actual order. + * + */ +static void x_deco_recurse(Con *con) { + Con *current; + + TAILQ_FOREACH(current, &(con->nodes_head), nodes) + x_deco_recurse(current); + + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) + x_deco_recurse(current); + + if ((con->type != CT_ROOT && con->type != CT_OUTPUT) && con->mapped) x_draw_decoration(con); } @@ -691,7 +706,8 @@ void x_push_changes(Con *con) { DLOG("Done, EnterNotify re-enabled\n"); DLOG("\n\n PUSHING CHANGES\n\n"); - x_push_node(con, false); + x_push_node(con); + x_deco_recurse(con); xcb_window_t to_focus = focused->frame; if (focused->window != NULL) From b401e08a5d82ac94419e55c1c3cffc6555b44e5a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 1 May 2011 19:46:41 +0200 Subject: [PATCH 596/867] Bugfix: For fullscreen cons, use a deco_height of 0 to correctly render the background color This should fix #364. --- src/x.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/x.c b/src/x.c index 2e77b143..3b757ace 100644 --- a/src/x.c +++ b/src/x.c @@ -297,14 +297,19 @@ void x_draw_decoration(Con *con) { con->parent->pixmap_recreated = false; + /* If the con is in fullscreen mode, the decoration height we work with is set to 0 */ + Rect deco_rect = con->deco_rect; + if (con_get_fullscreen_con(con->parent) == con) + deco_rect.height = 0; + /* 2: draw the client.background, but only for the parts around the client_rect */ xcb_rectangle_t background[] = { /* top area */ - { 0, con->deco_rect.height, r->width, w->y }, + { 0, deco_rect.height, r->width, w->y }, /* bottom area */ { 0, (w->y + w->height), r->width, r->height - (w->y + w->height) }, /* right area */ - { w->width, con->deco_rect.height, r->width - (w->x + w->width), r->height } + { w->width, deco_rect.height, r->width - (w->x + w->width), r->height } }; #if 0 for (int i = 0; i < 3; i++) From 79323a0be0ba81927ad95ecd564a9cf766540078 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 1 May 2011 22:27:06 +0200 Subject: [PATCH 597/867] Bugfix: Fix the client background rectangle calculation (Thanks Mike) Really fixes #364. --- src/x.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/x.c b/src/x.c index 3b757ace..358a2201 100644 --- a/src/x.c +++ b/src/x.c @@ -305,14 +305,16 @@ void x_draw_decoration(Con *con) { /* 2: draw the client.background, but only for the parts around the client_rect */ xcb_rectangle_t background[] = { /* top area */ - { 0, deco_rect.height, r->width, w->y }, + { 0, 0, r->width, w->y }, /* bottom area */ { 0, (w->y + w->height), r->width, r->height - (w->y + w->height) }, + /* left area */ + { 0, 0, w->x, r->height }, /* right area */ - { w->width, deco_rect.height, r->width - (w->x + w->width), r->height } + { w->x + w->width, 0, r->width - (w->x + w->width), r->height } }; #if 0 - for (int i = 0; i < 3; i++) + for (int i = 0; i < 4; i++) DLOG("rect is (%d, %d) with %d x %d\n", background[i].x, background[i].y, From b2754fd67907ba974387671d4ef01e5802042e93 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 2 May 2011 11:05:50 +0200 Subject: [PATCH 598/867] Bugfix: When re-assigning floating windows to a different output, use the last focused workspace, not the first --- src/floating.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/floating.c b/src/floating.c index 23f8a60a..875407a7 100644 --- a/src/floating.c +++ b/src/floating.c @@ -262,7 +262,7 @@ bool floating_maybe_reassign_ws(Con *con) { DLOG("Need to re-assign!\n"); Con *content = output_get_content(output->con); - Con *ws = TAILQ_FIRST(&(content->nodes_head)); + Con *ws = TAILQ_FIRST(&(content->focus_head)); DLOG("Moving con %p / %s to workspace %p / %s\n", con, con->name, ws, ws->name); con_move_to_workspace(con, ws); con_focus(con_descend_focused(con)); From 7e51f626ef096c4d1bbbcb0c25cc27685c78bca7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 2 May 2011 11:06:13 +0200 Subject: [PATCH 599/867] Bugfix: Before rendering, attach the con to its floating_con Otherwise, the rendering will produce negative coordinates. --- src/floating.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/floating.c b/src/floating.c index 875407a7..85aaa6e7 100644 --- a/src/floating.c +++ b/src/floating.c @@ -147,12 +147,13 @@ void floating_enable(Con *con, bool automatic) { } } + TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes); + TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused); + /* render the cons to get initial window_rect correct */ render_con(nc, false); render_con(con, false); - TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes); - TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused); // TODO: don’t influence focus handling when Con was not focused before. if (set_focus) con_focus(con); From 3d1acd6c2f80470cba402cf9df64b00297ac2a59 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 2 May 2011 23:29:26 +0200 Subject: [PATCH 600/867] re-implement assigning windows to workspaces --- include/all.h | 1 + include/data.h | 9 +++++---- include/i3.h | 2 +- include/match.h | 6 ++++++ src/cfgparse.l | 3 ++- src/cfgparse.y | 39 ++++++++++++++++++++++------------- src/manage.c | 54 ++++++++++++++++++++++++------------------------- src/match.c | 18 ++++++++++++++++- src/render.c | 1 + 9 files changed, 84 insertions(+), 49 deletions(-) diff --git a/include/all.h b/include/all.h index 9d13bb95..f9f12a70 100644 --- a/include/all.h +++ b/include/all.h @@ -34,6 +34,7 @@ #include "xcb_compat.h" #endif +#include "data.h" #include "util.h" #include "ipc.h" #include "tree.h" diff --git a/include/data.h b/include/data.h index f20d764e..d4836dfe 100644 --- a/include/data.h +++ b/include/data.h @@ -285,20 +285,21 @@ struct Match { enum { M_USER = 0, M_RESTART } source; + char *target_ws; + /* Where the window looking for a match should be inserted: * * M_HERE = the matched container will be replaced by the window * (layout saving) - * M_ACTIVE = the window will be inserted next to the currently focused - * container below the matched container - * (assignments) + * M_ASSIGN_WS = the matched container will be inserted in the target_ws. * M_BELOW = the window will be inserted as a child of the matched container * (dockareas) * */ - enum { M_HERE = 0, M_ACTIVE, M_BELOW } insert_where; + enum { M_HERE = 0, M_ASSIGN_WS, M_BELOW } insert_where; TAILQ_ENTRY(Match) matches; + TAILQ_ENTRY(Match) assignments; }; struct Con { diff --git a/include/i3.h b/include/i3.h index 060a0cf8..2f18ce70 100644 --- a/include/i3.h +++ b/include/i3.h @@ -26,7 +26,7 @@ extern Display *xlibdpy, *xkbdpy; extern int xkb_current_group; extern TAILQ_HEAD(bindings_head, Binding) *bindings; extern TAILQ_HEAD(autostarts_head, Autostart) autostarts; -extern TAILQ_HEAD(assignments_head, Assignment) assignments; +extern TAILQ_HEAD(assignments_head, Match) assignments; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; extern uint8_t root_depth; extern bool xcursor_supported, xkb_supported; diff --git a/include/match.h b/include/match.h index ef025172..4f0e9bdc 100644 --- a/include/match.h +++ b/include/match.h @@ -22,4 +22,10 @@ bool match_is_empty(Match *match); */ bool match_matches_window(Match *match, i3Window *window); +/** + * Returns the first match in 'assignments' that matches the given window. + * + */ +Match *match_by_assignment(i3Window *window); + #endif diff --git a/src/cfgparse.l b/src/cfgparse.l index c343aad6..1615288e 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -11,7 +11,6 @@ #include #include #include -#include "cfgparse.tab.h" #include #include "data.h" @@ -19,6 +18,8 @@ #include "log.h" #include "util.h" +#include "cfgparse.tab.h" + int yycolumn = 1; #define YY_DECL int yylex (struct context *context) diff --git a/src/cfgparse.y b/src/cfgparse.y index e8818b1f..7ffab78d 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -192,7 +192,7 @@ void parse_file(const char *f) { char *string; uint32_t *single_color; struct Colortriple *color; - struct Assignment *assignment; + Match *match; struct Binding *binding; } @@ -539,30 +539,40 @@ workspace_name: assign: TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target { -#if 0 printf("assignment of %s\n", $3); - struct Assignment *new = $6; - printf(" to %d\n", new->workspace); - printf(" floating = %d\n", new->floating); - new->windowclass_title = $3; - TAILQ_INSERT_TAIL(&assignments, new, assignments); -#endif + struct Match *match = $6; + + char *separator = NULL; + if ((separator = strchr($3, '/')) != NULL) { + *(separator++) = '\0'; + match->title = sstrdup(separator); + } + if (*$3 != '\0') + match->class = sstrdup($3); + free($3); + + printf(" class = %s\n", match->class); + printf(" title = %s\n", match->title); + if (match->insert_where == M_ASSIGN_WS) + printf(" to ws %s\n", match->target_ws); + TAILQ_INSERT_TAIL(&assignments, match, assignments); } ; assign_target: NUMBER { -#if 0 - struct Assignment *new = scalloc(sizeof(struct Assignment)); - new->workspace = $1; - new->floating = ASSIGN_FLOATING_NO; - $$ = new; -#endif + /* TODO: named workspaces */ + Match *match = smalloc(sizeof(Match)); + match_init(match); + match->insert_where = M_ASSIGN_WS; + asprintf(&(match->target_ws), "%d", $1); + $$ = match; } | '~' { + /* TODO: compatiblity */ #if 0 struct Assignment *new = scalloc(sizeof(struct Assignment)); new->floating = ASSIGN_FLOATING_ONLY; @@ -571,6 +581,7 @@ assign_target: } | '~' NUMBER { + /* TODO: compatiblity */ #if 0 struct Assignment *new = scalloc(sizeof(struct Assignment)); new->workspace = $2; diff --git a/src/manage.c b/src/manage.c index 98a34a15..b511189c 100644 --- a/src/manage.c +++ b/src/manage.c @@ -203,38 +203,36 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki DLOG("Initial geometry: (%d, %d, %d, %d)\n", geom->x, geom->y, geom->width, geom->height); - Con *nc; + Con *nc = NULL; Match *match; - /* TODO: assignments */ - /* TODO: two matches for one container */ - - /* See if any container swallows this new window */ - nc = con_for_window(search_at, cwindow, &match); - if (nc == NULL) { - if (focused->type == CT_CON && con_accepts_window(focused)) { - LOG("using current container, focused = %p, focused->name = %s\n", - focused, focused->name); - nc = focused; - } else nc = tree_open_con(NULL); - } else { - /* M_ACTIVE are assignments */ - if (match != NULL && match->insert_where == M_ACTIVE) { - /* We need to go down the focus stack starting from nc */ - while (TAILQ_FIRST(&(nc->focus_head)) != TAILQ_END(&(nc->focus_head))) { - DLOG("walking down one step...\n"); - nc = TAILQ_FIRST(&(nc->focus_head)); - } - /* We need to open a new con */ - /* TODO: make a difference between match-once containers (directly assign - * cwindow) and match-multiple (tree_open_con first) */ - nc = tree_open_con(nc->parent); + /* check assignments first */ + if ((match = match_by_assignment(cwindow))) { + DLOG("Assignment matches (%p)\n", match); + if (match->insert_where == M_ASSIGN_WS) { + nc = con_descend_focused(workspace_get(match->target_ws, NULL)); + DLOG("focused on ws %s: %p / %s\n", match->target_ws, nc, nc->name); + if (nc->type == CT_WORKSPACE) + nc = tree_open_con(nc); + else nc = tree_open_con(nc->parent); } + } else { + /* TODO: two matches for one container */ - /* M_BELOW inserts the new window as a child of the one which was - * matched (e.g. dock areas) */ - else if (match != NULL && match->insert_where == M_BELOW) { - nc = tree_open_con(nc); + /* See if any container swallows this new window */ + nc = con_for_window(search_at, cwindow, &match); + if (nc == NULL) { + if (focused->type == CT_CON && con_accepts_window(focused)) { + LOG("using current container, focused = %p, focused->name = %s\n", + focused, focused->name); + nc = focused; + } else nc = tree_open_con(NULL); + } else { + /* M_BELOW inserts the new window as a child of the one which was + * matched (e.g. dock areas) */ + if (match != NULL && match->insert_where == M_BELOW) { + nc = tree_open_con(nc); + } } } diff --git a/src/match.c b/src/match.c index da58047e..9ed4d434 100644 --- a/src/match.c +++ b/src/match.c @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) * * A "match" is a data structure which acts like a mask or expression to match * certain windows or not. For example, when using commands, you can specify a @@ -82,3 +82,19 @@ bool match_matches_window(Match *match, i3Window *window) { return false; } +/* + * Returns the first match in 'assignments' that matches the given window. + * + */ +Match *match_by_assignment(i3Window *window) { + Match *match; + + TAILQ_FOREACH(match, &assignments, assignments) { + if (!match_matches_window(match, window)) + continue; + DLOG("got a matching assignment (to %s)\n", match->target_ws); + return match; + } + + return NULL; +} diff --git a/src/render.c b/src/render.c index 5bdc2e21..a59d418b 100644 --- a/src/render.c +++ b/src/render.c @@ -139,6 +139,7 @@ void render_con(Con *con, bool render_fullscreen) { if (!render_fullscreen) *inset = rect_add(*inset, con_border_style_rect(con)); + DLOG("Starting with inset = (%d, %d) %d x %d\n", inset->x, inset->y, inset->width, inset->height); /* Obey x11 border */ DLOG("X11 border: %d\n", con->border_width); inset->width -= (2 * con->border_width); From a075fd4ee2a7916c28240b396a9c2dc5588fbd86 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 5 May 2011 20:39:05 +0200 Subject: [PATCH 601/867] cmdparse.y: set type on the tokens/non-terminals (Thanks Merovius) --- src/cmdparse.y | 259 +++++++++++++++++++++++++------------------------ 1 file changed, 134 insertions(+), 125 deletions(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index e15410a4..5dacc64f 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -101,57 +101,66 @@ char *parse_cmd(const char *new) { int number; } -%token TOK_ATTACH "attach" -%token TOK_EXEC "exec" -%token TOK_EXIT "exit" -%token TOK_RELOAD "reload" -%token TOK_RESTART "restart" -%token TOK_KILL "kill" -%token TOK_FULLSCREEN "fullscreen" -%token TOK_GLOBAL "global" -%token TOK_LAYOUT "layout" -%token TOK_DEFAULT "default" -%token TOK_STACKED "stacked" -%token TOK_TABBED "tabbed" -%token TOK_BORDER "border" -%token TOK_NORMAL "normal" -%token TOK_NONE "none" -%token TOK_1PIXEL "1pixel" -%token TOK_MODE "mode" -%token TOK_TILING "tiling" -%token TOK_FLOATING "floating" -%token TOK_WORKSPACE "workspace" -%token TOK_TOGGLE "toggle" -%token TOK_FOCUS "focus" -%token TOK_MOVE "move" -%token TOK_OPEN "open" -%token TOK_NEXT "next" -%token TOK_PREV "prev" -%token TOK_SPLIT "split" -%token TOK_HORIZONTAL "horizontal" -%token TOK_VERTICAL "vertical" -%token TOK_LEVEL "level" -%token TOK_UP "up" -%token TOK_DOWN "down" -%token TOK_LEFT "left" -%token TOK_RIGHT "right" -%token TOK_RESTORE "restore" -%token TOK_MARK "mark" -%token TOK_RESIZE "resize" -%token TOK_GROW "grow" -%token TOK_SHRINK "shrink" -%token TOK_PX "px" -%token TOK_OR "or" -%token TOK_PPT "ppt" -%token TOK_NOP "nop" +%token TOK_ATTACH "attach" +%token TOK_EXEC "exec" +%token TOK_EXIT "exit" +%token TOK_RELOAD "reload" +%token TOK_RESTART "restart" +%token TOK_KILL "kill" +%token TOK_FULLSCREEN "fullscreen" +%token TOK_GLOBAL "global" +%token TOK_LAYOUT "layout" +%token TOK_DEFAULT "default" +%token TOK_STACKED "stacked" +%token TOK_TABBED "tabbed" +%token TOK_BORDER "border" +%token TOK_NORMAL "normal" +%token TOK_NONE "none" +%token TOK_1PIXEL "1pixel" +%token TOK_MODE "mode" +%token TOK_TILING "tiling" +%token TOK_FLOATING "floating" +%token TOK_WORKSPACE "workspace" +%token TOK_TOGGLE "toggle" +%token TOK_FOCUS "focus" +%token TOK_MOVE "move" +%token TOK_OPEN "open" +%token TOK_NEXT "next" +%token TOK_PREV "prev" +%token TOK_SPLIT "split" +%token TOK_HORIZONTAL "horizontal" +%token TOK_VERTICAL "vertical" +%token TOK_LEVEL "level" +%token TOK_UP "up" +%token TOK_DOWN "down" +%token TOK_LEFT "left" +%token TOK_RIGHT "right" +%token TOK_RESTORE "restore" +%token TOK_MARK "mark" +%token TOK_RESIZE "resize" +%token TOK_GROW "grow" +%token TOK_SHRINK "shrink" +%token TOK_PX "px" +%token TOK_OR "or" +%token TOK_PPT "ppt" +%token TOK_NOP "nop" -%token TOK_CLASS "class" -%token TOK_ID "id" -%token TOK_CON_ID "con_id" +%token TOK_CLASS "class" +%token TOK_ID "id" +%token TOK_CON_ID "con_id" -%token WHITESPACE "" -%token STR "" -%token NUMBER "" +%token WHITESPACE "" +%token STR "" +%token NUMBER "" + +%type direction +%type level_direction +%type window_mode +%type border_style +%type layout_mode +%type resize_px +%type resize_way +%type resize_tiling %% @@ -254,19 +263,19 @@ matchend: criteria: TOK_CLASS '=' STR { - printf("criteria: class = %s\n", $3); - current_match.class = $3; + printf("criteria: class = %s\n", $3); + current_match.class = $3; } | TOK_CON_ID '=' STR { - printf("criteria: id = %s\n", $3); + printf("criteria: id = %s\n", $3); char *end; - long parsed = strtol($3, &end, 10); + long parsed = strtol($3, &end, 10); if (parsed == LONG_MIN || parsed == LONG_MAX || parsed < 0 || (end && *end != '\0')) { - ELOG("Could not parse con id \"%s\"\n", $3); + ELOG("Could not parse con id \"%s\"\n", $3); } else { current_match.con_id = (Con*)parsed; printf("id as int = %p\n", current_match.con_id); @@ -274,14 +283,14 @@ criteria: } | TOK_ID '=' STR { - printf("criteria: window id = %s\n", $3); + printf("criteria: window id = %s\n", $3); char *end; - long parsed = strtol($3, &end, 10); + long parsed = strtol($3, &end, 10); if (parsed == LONG_MIN || parsed == LONG_MAX || parsed < 0 || (end && *end != '\0')) { - ELOG("Could not parse window id \"%s\"\n", $3); + ELOG("Could not parse window id \"%s\"\n", $3); } else { current_match.id = parsed; printf("window id as int = %d\n", current_match.id); @@ -289,8 +298,8 @@ criteria: } | TOK_MARK '=' STR { - printf("criteria: mark = %s\n", $3); - current_match.mark = $3; + printf("criteria: mark = %s\n", $3); + current_match.mark = $3; } ; @@ -327,9 +336,9 @@ operation: exec: TOK_EXEC WHITESPACE STR { - printf("should execute %s\n", $3); - start_application($3); - free($3); + printf("should execute %s\n", $3); + start_application($3); + free($3); } ; @@ -410,9 +419,9 @@ kill: workspace: TOK_WORKSPACE WHITESPACE STR { - printf("should switch to workspace %s\n", $3); - workspace_show($3); - free($3); + printf("should switch to workspace %s\n", $3); + workspace_show($3); + free($3); } ; @@ -449,8 +458,8 @@ next: TOK_NEXT WHITESPACE direction { /* TODO: use matches */ - printf("should select next window in direction %c\n", $3); - tree_next('n', ($3 == 'v' ? VERT : HORIZ)); + printf("should select next window in direction %c\n", $3); + tree_next('n', ($3 == 'v' ? VERT : HORIZ)); } ; @@ -458,8 +467,8 @@ prev: TOK_PREV WHITESPACE direction { /* TODO: use matches */ - printf("should select prev window in direction %c\n", $3); - tree_next('p', ($3 == 'v' ? VERT : HORIZ)); + printf("should select prev window in direction %c\n", $3); + tree_next('p', ($3 == 'v' ? VERT : HORIZ)); } ; @@ -467,27 +476,27 @@ split: TOK_SPLIT WHITESPACE direction { /* TODO: use matches */ - printf("splitting in direction %c\n", $3); - tree_split(focused, ($3 == 'v' ? VERT : HORIZ)); + printf("splitting in direction %c\n", $3); + tree_split(focused, ($3 == 'v' ? VERT : HORIZ)); } ; direction: - TOK_HORIZONTAL { $$ = 'h'; } - | 'h' { $$ = 'h'; } - | TOK_VERTICAL { $$ = 'v'; } - | 'v' { $$ = 'v'; } + TOK_HORIZONTAL { $$ = 'h'; } + | 'h' { $$ = 'h'; } + | TOK_VERTICAL { $$ = 'v'; } + | 'v' { $$ = 'v'; } ; mode: TOK_MODE WHITESPACE window_mode { - if ($3 == TOK_TOGGLE) { + if ($3 == TOK_TOGGLE) { printf("should toggle mode\n"); toggle_floating_mode(focused, false); } else { - printf("should switch mode to %s\n", ($3 == TOK_FLOATING ? "floating" : "tiling")); - if ($3 == TOK_FLOATING) { + printf("should switch mode to %s\n", ($3 == TOK_FLOATING ? "floating" : "tiling")); + if ($3 == TOK_FLOATING) { floating_enable(focused, false); } else { floating_disable(focused, false); @@ -497,65 +506,65 @@ mode: ; window_mode: - TOK_FLOATING { $$ = TOK_FLOATING; } - | TOK_TILING { $$ = TOK_TILING; } - | TOK_TOGGLE { $$ = TOK_TOGGLE; } + TOK_FLOATING { $$ = TOK_FLOATING; } + | TOK_TILING { $$ = TOK_TILING; } + | TOK_TOGGLE { $$ = TOK_TOGGLE; } ; border: TOK_BORDER WHITESPACE border_style { - printf("border style should be changed to %d\n", $3); + printf("border style should be changed to %d\n", $3); owindow *current; /* check if the match is empty, not if the result is empty */ if (match_is_empty(¤t_match)) - focused->border_style = $3; + focused->border_style = $3; else { TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); - current->con->border_style = $3; + current->con->border_style = $3; } } } ; border_style: - TOK_NORMAL { $$ = BS_NORMAL; } - | TOK_NONE { $$ = BS_NONE; } - | TOK_1PIXEL { $$ = BS_1PIXEL; } + TOK_NORMAL { $$ = BS_NORMAL; } + | TOK_NONE { $$ = BS_NONE; } + | TOK_1PIXEL { $$ = BS_1PIXEL; } ; level: TOK_LEVEL WHITESPACE level_direction { - printf("level %c\n", $3); - if ($3 == 'u') + printf("level %c\n", $3); + if ($3 == 'u') level_up(); else level_down(); } ; level_direction: - TOK_UP { $$ = 'u'; } - | TOK_DOWN { $$ = 'd'; } + TOK_UP { $$ = 'u'; } + | TOK_DOWN { $$ = 'd'; } ; move: TOK_MOVE WHITESPACE direction { - printf("moving in direction %d\n", $3); - tree_move($3); + printf("moving in direction %d\n", $3); + tree_move($3); } | TOK_MOVE WHITESPACE TOK_WORKSPACE WHITESPACE STR { owindow *current; - printf("should move window to workspace %s\n", $5); + printf("should move window to workspace %s\n", $5); /* get the workspace */ - Con *ws = workspace_get($5, NULL); - free($5); + Con *ws = workspace_get($5, NULL); + free($5); /* check if the match is empty, not if the result is empty */ if (match_is_empty(¤t_match)) @@ -572,25 +581,25 @@ move: restore: TOK_RESTORE WHITESPACE STR { - printf("restoring \"%s\"\n", $3); - tree_append_json($3); - free($3); + printf("restoring \"%s\"\n", $3); + tree_append_json($3); + free($3); } ; layout: TOK_LAYOUT WHITESPACE layout_mode { - printf("changing layout to %d\n", $3); + printf("changing layout to %d\n", $3); owindow *current; /* check if the match is empty, not if the result is empty */ if (match_is_empty(¤t_match)) - con_set_layout(focused->parent, $3); + con_set_layout(focused->parent, $3); else { TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); - con_set_layout(current->con, $3); + con_set_layout(current->con, $3); } } @@ -598,24 +607,24 @@ layout: ; layout_mode: - TOK_DEFAULT { $$ = L_DEFAULT; } - | TOK_STACKED { $$ = L_STACKED; } - | TOK_TABBED { $$ = L_TABBED; } + TOK_DEFAULT { $$ = L_DEFAULT; } + | TOK_STACKED { $$ = L_STACKED; } + | TOK_TABBED { $$ = L_TABBED; } ; mark: TOK_MARK WHITESPACE STR { - printf("marking window with str %s\n", $3); + printf("marking window with str %s\n", $3); owindow *current; /* check if the match is empty, not if the result is empty */ if (match_is_empty(¤t_match)) - focused->mark = sstrdup($3); + focused->mark = sstrdup($3); else { TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); - current->con->mark = sstrdup($3); + current->con->mark = sstrdup($3); } } @@ -627,9 +636,9 @@ nop: TOK_NOP WHITESPACE STR { printf("-------------------------------------------------\n"); - printf(" NOP: %s\n", $3); + printf(" NOP: %s\n", $3); printf("-------------------------------------------------\n"); - free($3); + free($3); } ; @@ -637,11 +646,11 @@ resize: TOK_RESIZE WHITESPACE resize_way WHITESPACE direction resize_px resize_tiling { /* resize [ px] [or ppt] */ - printf("resizing in way %d, direction %d, px %d or ppt %d\n", $3, $5, $6, $7); - int direction = $5; - int px = $6; - int ppt = $7; - if ($3 == TOK_SHRINK) { + printf("resizing in way %d, direction %d, px %d or ppt %d\n", $3, $5, $6, $7); + int direction = $5; + int px = $6; + int ppt = $7; + if ($3 == TOK_SHRINK) { px *= -1; ppt *= -1; } @@ -694,33 +703,33 @@ resize: resize_px: /* empty */ { - $$ = 10; + $$ = 10; } | WHITESPACE NUMBER WHITESPACE TOK_PX { - $$ = $2; + $$ = $2; } ; resize_tiling: /* empty */ { - $$ = 10; + $$ = 10; } | WHITESPACE TOK_OR WHITESPACE NUMBER WHITESPACE TOK_PPT { - $$ = $4; + $$ = $4; } ; resize_way: - TOK_GROW { $$ = TOK_GROW; } - | TOK_SHRINK { $$ = TOK_SHRINK; } + TOK_GROW { $$ = TOK_GROW; } + | TOK_SHRINK { $$ = TOK_SHRINK; } ; direction: - TOK_UP { $$ = TOK_UP; } - | TOK_DOWN { $$ = TOK_DOWN; } - | TOK_LEFT { $$ = TOK_LEFT; } - | TOK_RIGHT { $$ = TOK_RIGHT; } + TOK_UP { $$ = TOK_UP; } + | TOK_DOWN { $$ = TOK_DOWN; } + | TOK_LEFT { $$ = TOK_LEFT; } + | TOK_RIGHT { $$ = TOK_RIGHT; } ; From bd73275771e5f5e23a21ece5d9ef08fadd48e4fc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 5 May 2011 21:19:47 +0200 Subject: [PATCH 602/867] re-indent cfgparse.y --- src/cfgparse.y | 956 ++++++++++++++++++++++++------------------------- 1 file changed, 478 insertions(+), 478 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 7ffab78d..33b8f774 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -1,6 +1,6 @@ %{ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * */ #include @@ -25,160 +25,160 @@ static struct context *context; //int yydebug = 1; void yyerror(const char *error_message) { - ELOG("\n"); - ELOG("CONFIG: %s\n", error_message); - ELOG("CONFIG: in file \"%s\", line %d:\n", - context->filename, context->line_number); - ELOG("CONFIG: %s\n", context->line_copy); - ELOG("CONFIG: "); - for (int c = 1; c <= context->last_column; c++) - if (c >= context->first_column) - printf("^"); - else printf(" "); - printf("\n"); - ELOG("\n"); + ELOG("\n"); + ELOG("CONFIG: %s\n", error_message); + ELOG("CONFIG: in file \"%s\", line %d:\n", + context->filename, context->line_number); + ELOG("CONFIG: %s\n", context->line_copy); + ELOG("CONFIG: "); + for (int c = 1; c <= context->last_column; c++) + if (c >= context->first_column) + printf("^"); + else printf(" "); + printf("\n"); + ELOG("\n"); } int yywrap() { - return 1; + return 1; } void parse_file(const char *f) { - SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables); - int fd, ret, read_bytes = 0; - struct stat stbuf; - char *buf; - FILE *fstr; - char buffer[1026], key[512], value[512]; + SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables); + int fd, ret, read_bytes = 0; + struct stat stbuf; + char *buf; + FILE *fstr; + char buffer[1026], key[512], value[512]; - if ((fd = open(f, O_RDONLY)) == -1) - die("Could not open configuration file: %s\n", strerror(errno)); + if ((fd = open(f, O_RDONLY)) == -1) + die("Could not open configuration file: %s\n", strerror(errno)); - if (fstat(fd, &stbuf) == -1) - die("Could not fstat file: %s\n", strerror(errno)); + if (fstat(fd, &stbuf) == -1) + die("Could not fstat file: %s\n", strerror(errno)); - buf = scalloc((stbuf.st_size + 1) * sizeof(char)); - while (read_bytes < stbuf.st_size) { - if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0) - die("Could not read(): %s\n", strerror(errno)); - read_bytes += ret; + buf = scalloc((stbuf.st_size + 1) * sizeof(char)); + while (read_bytes < stbuf.st_size) { + if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0) + die("Could not read(): %s\n", strerror(errno)); + read_bytes += ret; + } + + if (lseek(fd, 0, SEEK_SET) == (off_t)-1) + die("Could not lseek: %s\n", strerror(errno)); + + if ((fstr = fdopen(fd, "r")) == NULL) + die("Could not fdopen: %s\n", strerror(errno)); + + while (!feof(fstr)) { + if (fgets(buffer, 1024, fstr) == NULL) { + if (feof(fstr)) + break; + die("Could not read configuration file\n"); } - if (lseek(fd, 0, SEEK_SET) == (off_t)-1) - die("Could not lseek: %s\n", strerror(errno)); + /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */ + if (sscanf(buffer, "%s %[^\n]", key, value) < 1 || + key[0] == '#' || strlen(key) < 3) + continue; - if ((fstr = fdopen(fd, "r")) == NULL) - die("Could not fdopen: %s\n", strerror(errno)); + if (strcasecmp(key, "set") == 0) { + if (value[0] != '$') + die("Malformed variable assignment, name has to start with $\n"); - while (!feof(fstr)) { - if (fgets(buffer, 1024, fstr) == NULL) { - if (feof(fstr)) - break; - die("Could not read configuration file\n"); - } + /* get key/value for this variable */ + char *v_key = value, *v_value; + if ((v_value = strstr(value, " ")) == NULL) + die("Malformed variable assignment, need a value\n"); - /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */ - if (sscanf(buffer, "%s %[^\n]", key, value) < 1 || - key[0] == '#' || strlen(key) < 3) - continue; + *(v_value++) = '\0'; - if (strcasecmp(key, "set") == 0) { - if (value[0] != '$') - die("Malformed variable assignment, name has to start with $\n"); - - /* get key/value for this variable */ - char *v_key = value, *v_value; - if ((v_value = strstr(value, " ")) == NULL) - die("Malformed variable assignment, need a value\n"); - - *(v_value++) = '\0'; - - struct Variable *new = scalloc(sizeof(struct Variable)); - new->key = sstrdup(v_key); - new->value = sstrdup(v_value); - SLIST_INSERT_HEAD(&variables, new, variables); - DLOG("Got new variable %s = %s\n", v_key, v_value); - continue; - } + struct Variable *new = scalloc(sizeof(struct Variable)); + new->key = sstrdup(v_key); + new->value = sstrdup(v_value); + SLIST_INSERT_HEAD(&variables, new, variables); + DLOG("Got new variable %s = %s\n", v_key, v_value); + continue; } - fclose(fstr); + } + fclose(fstr); - /* For every custom variable, see how often it occurs in the file and - * how much extra bytes it requires when replaced. */ - struct Variable *current, *nearest; - int extra_bytes = 0; - /* We need to copy the buffer because we need to invalidate the - * variables (otherwise we will count them twice, which is bad when - * 'extra' is negative) */ - char *bufcopy = sstrdup(buf); + /* For every custom variable, see how often it occurs in the file and + * how much extra bytes it requires when replaced. */ + struct Variable *current, *nearest; + int extra_bytes = 0; + /* We need to copy the buffer because we need to invalidate the + * variables (otherwise we will count them twice, which is bad when + * 'extra' is negative) */ + char *bufcopy = sstrdup(buf); + SLIST_FOREACH(current, &variables, variables) { + int extra = (strlen(current->value) - strlen(current->key)); + char *next; + for (next = bufcopy; + (next = strcasestr(bufcopy + (next - bufcopy), current->key)) != NULL; + next += strlen(current->key)) { + *next = '_'; + extra_bytes += extra; + } + } + FREE(bufcopy); + + /* Then, allocate a new buffer and copy the file over to the new one, + * but replace occurences of our variables */ + char *walk = buf, *destwalk; + char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char)); + destwalk = new; + while (walk < (buf + stbuf.st_size)) { + /* Find the next variable */ + SLIST_FOREACH(current, &variables, variables) + current->next_match = strcasestr(walk, current->key); + nearest = NULL; + int distance = stbuf.st_size; SLIST_FOREACH(current, &variables, variables) { - int extra = (strlen(current->value) - strlen(current->key)); - char *next; - for (next = bufcopy; - (next = strcasestr(bufcopy + (next - bufcopy), current->key)) != NULL; - next += strlen(current->key)) { - *next = '_'; - extra_bytes += extra; - } + if (current->next_match == NULL) + continue; + if ((current->next_match - walk) < distance) { + distance = (current->next_match - walk); + nearest = current; + } } - FREE(bufcopy); - - /* Then, allocate a new buffer and copy the file over to the new one, - * but replace occurences of our variables */ - char *walk = buf, *destwalk; - char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char)); - destwalk = new; - while (walk < (buf + stbuf.st_size)) { - /* Find the next variable */ - SLIST_FOREACH(current, &variables, variables) - current->next_match = strcasestr(walk, current->key); - nearest = NULL; - int distance = stbuf.st_size; - SLIST_FOREACH(current, &variables, variables) { - if (current->next_match == NULL) - continue; - if ((current->next_match - walk) < distance) { - distance = (current->next_match - walk); - nearest = current; - } - } - if (nearest == NULL) { - /* If there are no more variables, we just copy the rest */ - strncpy(destwalk, walk, (buf + stbuf.st_size) - walk); - destwalk += (buf + stbuf.st_size) - walk; - *destwalk = '\0'; - break; - } else { - /* Copy until the next variable, then copy its value */ - strncpy(destwalk, walk, distance); - strncpy(destwalk + distance, nearest->value, strlen(nearest->value)); - walk += distance + strlen(nearest->key); - destwalk += distance + strlen(nearest->value); - } + if (nearest == NULL) { + /* If there are no more variables, we just copy the rest */ + strncpy(destwalk, walk, (buf + stbuf.st_size) - walk); + destwalk += (buf + stbuf.st_size) - walk; + *destwalk = '\0'; + break; + } else { + /* Copy until the next variable, then copy its value */ + strncpy(destwalk, walk, distance); + strncpy(destwalk + distance, nearest->value, strlen(nearest->value)); + walk += distance + strlen(nearest->key); + destwalk += distance + strlen(nearest->value); } + } - yy_scan_string(new); + yy_scan_string(new); - context = scalloc(sizeof(struct context)); - context->filename = f; + context = scalloc(sizeof(struct context)); + context->filename = f; - if (yyparse() != 0) { - fprintf(stderr, "Could not parse configfile\n"); - exit(1); - } + if (yyparse() != 0) { + fprintf(stderr, "Could not parse configfile\n"); + exit(1); + } - FREE(context->line_copy); - free(context); - free(new); - free(buf); + FREE(context->line_copy); + free(context); + free(new); + free(buf); - while (!SLIST_EMPTY(&variables)) { - current = SLIST_FIRST(&variables); - FREE(current->key); - FREE(current->value); - SLIST_REMOVE_HEAD(&variables, variables); - FREE(current); - } + while (!SLIST_EMPTY(&variables)) { + current = SLIST_FIRST(&variables); + FREE(current->key); + FREE(current->value); + SLIST_REMOVE_HEAD(&variables, variables); + FREE(current); + } } %} @@ -188,12 +188,12 @@ void parse_file(const char *f) { %lex-param { struct context *context } %union { - int number; - char *string; - uint32_t *single_color; - struct Colortriple *color; - Match *match; - struct Binding *binding; + int number; + char *string; + uint32_t *single_color; + struct Colortriple *color; + Match *match; + struct Binding *binding; } %token NUMBER "" @@ -246,453 +246,453 @@ void parse_file(const char *f) { %% lines: /* empty */ - | lines WHITESPACE line - | lines error - | lines line - ; + | lines WHITESPACE line + | lines error + | lines line + ; line: - bindline - | mode - | floating_modifier - | orientation - | workspace_layout - | new_window - | focus_follows_mouse - | workspace_bar - | workspace - | assign - | ipcsocket - | restart_state - | exec - | single_color - | color - | terminal - | font - | comment - | popup_during_fullscreen - ; + bindline + | mode + | floating_modifier + | orientation + | workspace_layout + | new_window + | focus_follows_mouse + | workspace_bar + | workspace + | assign + | ipcsocket + | restart_state + | exec + | single_color + | color + | terminal + | font + | comment + | popup_during_fullscreen + ; comment: - TOKCOMMENT - ; + TOKCOMMENT + ; command: - STR - ; + STR + ; bindline: - binding - { - TAILQ_INSERT_TAIL(bindings, $1, bindings); - } - ; + binding + { + TAILQ_INSERT_TAIL(bindings, $1, bindings); + } + ; binding: - TOKBINDCODE WHITESPACE bindcode { $$ = $3; } - | TOKBINDSYM WHITESPACE bindsym { $$ = $3; } - ; + TOKBINDCODE WHITESPACE bindcode { $$ = $3; } + | TOKBINDSYM WHITESPACE bindsym { $$ = $3; } + ; bindcode: - binding_modifiers NUMBER WHITESPACE command - { - printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $4); - Binding *new = scalloc(sizeof(Binding)); + binding_modifiers NUMBER WHITESPACE command + { + printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $4); + Binding *new = scalloc(sizeof(Binding)); - new->keycode = $2; - new->mods = $1; - new->command = $4; + new->keycode = $2; + new->mods = $1; + new->command = $4; - $$ = new; - } - ; + $$ = new; + } + ; bindsym: - binding_modifiers word_or_number WHITESPACE command - { - printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $4); - Binding *new = scalloc(sizeof(Binding)); + binding_modifiers word_or_number WHITESPACE command + { + printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $4); + Binding *new = scalloc(sizeof(Binding)); - new->symbol = $2; - new->mods = $1; - new->command = $4; + new->symbol = $2; + new->mods = $1; + new->command = $4; - $$ = new; - } - ; + $$ = new; + } + ; word_or_number: - WORD - | NUMBER - { - asprintf(&$$, "%d", $1); - } - ; + WORD + | NUMBER + { + asprintf(&$$, "%d", $1); + } + ; mode: - TOKMODE WHITESPACE QUOTEDSTRING WHITESPACE '{' modelines '}' - { - if (strcasecmp($3, "default") == 0) { - printf("You cannot use the name \"default\" for your mode\n"); - exit(1); - } - printf("\t now in mode %s\n", $3); - printf("\t current bindings = %p\n", current_bindings); - Binding *binding; - TAILQ_FOREACH(binding, current_bindings, bindings) { - printf("got binding on mods %d, keycode %d, symbol %s, command %s\n", - binding->mods, binding->keycode, binding->symbol, binding->command); - } - - struct Mode *mode = scalloc(sizeof(struct Mode)); - mode->name = $3; - mode->bindings = current_bindings; - current_bindings = NULL; - SLIST_INSERT_HEAD(&modes, mode, modes); + TOKMODE WHITESPACE QUOTEDSTRING WHITESPACE '{' modelines '}' + { + if (strcasecmp($3, "default") == 0) { + printf("You cannot use the name \"default\" for your mode\n"); + exit(1); } - ; + printf("\t now in mode %s\n", $3); + printf("\t current bindings = %p\n", current_bindings); + Binding *binding; + TAILQ_FOREACH(binding, current_bindings, bindings) { + printf("got binding on mods %d, keycode %d, symbol %s, command %s\n", + binding->mods, binding->keycode, binding->symbol, binding->command); + } + + struct Mode *mode = scalloc(sizeof(struct Mode)); + mode->name = $3; + mode->bindings = current_bindings; + current_bindings = NULL; + SLIST_INSERT_HEAD(&modes, mode, modes); + } + ; modelines: - /* empty */ - | modelines modeline - ; + /* empty */ + | modelines modeline + ; modeline: - WHITESPACE - | comment - | binding - { - if (current_bindings == NULL) { - current_bindings = scalloc(sizeof(struct bindings_head)); - TAILQ_INIT(current_bindings); - } - - TAILQ_INSERT_TAIL(current_bindings, $1, bindings); + WHITESPACE + | comment + | binding + { + if (current_bindings == NULL) { + current_bindings = scalloc(sizeof(struct bindings_head)); + TAILQ_INIT(current_bindings); } - ; + + TAILQ_INSERT_TAIL(current_bindings, $1, bindings); + } + ; floating_modifier: - TOKFLOATING_MODIFIER WHITESPACE binding_modifiers - { - DLOG("floating modifier = %d\n", $3); - config.floating_modifier = $3; - } - ; + TOKFLOATING_MODIFIER WHITESPACE binding_modifiers + { + DLOG("floating modifier = %d\n", $3); + config.floating_modifier = $3; + } + ; orientation: - TOK_ORIENTATION WHITESPACE direction - { - DLOG("New containers should start with split direction %d\n", $3); - config.default_orientation = $3; - } - ; + TOK_ORIENTATION WHITESPACE direction + { + DLOG("New containers should start with split direction %d\n", $3); + config.default_orientation = $3; + } + ; direction: - TOK_HORIZ { $$ = HORIZ; } - | TOK_VERT { $$ = VERT; } - | TOK_AUTO { $$ = NO_ORIENTATION; } - ; + TOK_HORIZ { $$ = HORIZ; } + | TOK_VERT { $$ = VERT; } + | TOK_AUTO { $$ = NO_ORIENTATION; } + ; workspace_layout: - TOK_WORKSPACE_LAYOUT WHITESPACE layout_mode - { - DLOG("new containers will be in mode %d\n", $3); - config.default_layout = $3; + TOK_WORKSPACE_LAYOUT WHITESPACE layout_mode + { + DLOG("new containers will be in mode %d\n", $3); + config.default_layout = $3; #if 0 - /* We also need to change the layout of the already existing - * workspaces here. Workspaces may exist at this point because - * of the other directives which are modifying workspaces - * (setting the preferred screen or name). While the workspace - * objects are already created, they have never been used. - * Thus, the user very likely awaits the default container mode - * to trigger in this case, regardless of where it is inside - * his configuration file. */ - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->table == NULL) - continue; - switch_layout_mode(global_conn, - ws->table[0][0], - config.container_mode); - } -#endif + /* We also need to change the layout of the already existing + * workspaces here. Workspaces may exist at this point because + * of the other directives which are modifying workspaces + * (setting the preferred screen or name). While the workspace + * objects are already created, they have never been used. + * Thus, the user very likely awaits the default container mode + * to trigger in this case, regardless of where it is inside + * his configuration file. */ + Workspace *ws; + TAILQ_FOREACH(ws, workspaces, workspaces) { + if (ws->table == NULL) + continue; + switch_layout_mode(global_conn, + ws->table[0][0], + config.container_mode); } - | TOK_WORKSPACE_LAYOUT WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER - { - DLOG("stack-limit %d with val %d\n", $5, $7); - config.container_stack_limit = $5; - config.container_stack_limit_value = $7; +#endif + } + | TOK_WORKSPACE_LAYOUT WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER + { + DLOG("stack-limit %d with val %d\n", $5, $7); + config.container_stack_limit = $5; + config.container_stack_limit_value = $7; #if 0 - /* See the comment above */ - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->table == NULL) - continue; - Container *con = ws->table[0][0]; - con->stack_limit = config.container_stack_limit; - con->stack_limit_value = config.container_stack_limit_value; - } -#endif + /* See the comment above */ + Workspace *ws; + TAILQ_FOREACH(ws, workspaces, workspaces) { + if (ws->table == NULL) + continue; + Container *con = ws->table[0][0]; + con->stack_limit = config.container_stack_limit; + con->stack_limit_value = config.container_stack_limit_value; } - ; +#endif + } + ; layout_mode: - TOK_DEFAULT { $$ = L_DEFAULT; } - | TOK_STACKING { $$ = L_STACKED; } - | TOK_TABBED { $$ = L_TABBED; } - ; + TOK_DEFAULT { $$ = L_DEFAULT; } + | TOK_STACKING { $$ = L_STACKED; } + | TOK_TABBED { $$ = L_TABBED; } + ; new_window: - TOKNEWWINDOW WHITESPACE border_style - { - DLOG("new windows should start with border style %d\n", $3); - config.default_border = $3; - } - ; + TOKNEWWINDOW WHITESPACE border_style + { + DLOG("new windows should start with border style %d\n", $3); + config.default_border = $3; + } + ; border_style: - TOK_NORMAL { $$ = BS_NORMAL; } - | TOK_NONE { $$ = BS_NONE; } - | TOK_1PIXEL { $$ = BS_1PIXEL; } - ; + TOK_NORMAL { $$ = BS_NORMAL; } + | TOK_NONE { $$ = BS_NONE; } + | TOK_1PIXEL { $$ = BS_1PIXEL; } + ; bool: - NUMBER - { - $$ = ($1 == 1); - } - | WORD - { - DLOG("checking word \"%s\"\n", $1); - $$ = (strcasecmp($1, "yes") == 0 || - strcasecmp($1, "true") == 0 || - strcasecmp($1, "on") == 0 || - strcasecmp($1, "enable") == 0 || - strcasecmp($1, "active") == 0); - } - ; + NUMBER + { + $$ = ($1 == 1); + } + | WORD + { + DLOG("checking word \"%s\"\n", $1); + $$ = (strcasecmp($1, "yes") == 0 || + strcasecmp($1, "true") == 0 || + strcasecmp($1, "on") == 0 || + strcasecmp($1, "enable") == 0 || + strcasecmp($1, "active") == 0); + } + ; focus_follows_mouse: - TOKFOCUSFOLLOWSMOUSE WHITESPACE bool - { - DLOG("focus follows mouse = %d\n", $3); - config.disable_focus_follows_mouse = !($3); - } - ; + TOKFOCUSFOLLOWSMOUSE WHITESPACE bool + { + DLOG("focus follows mouse = %d\n", $3); + config.disable_focus_follows_mouse = !($3); + } + ; workspace_bar: - TOKWORKSPACEBAR WHITESPACE bool - { - DLOG("workspace bar = %d\n", $3); - config.disable_workspace_bar = !($3); - } - ; + TOKWORKSPACEBAR WHITESPACE bool + { + DLOG("workspace bar = %d\n", $3); + config.disable_workspace_bar = !($3); + } + ; workspace: - TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKOUTPUT WHITESPACE OUTPUT optional_workspace_name - { - int ws_num = $3; - if (ws_num < 1) { - DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); - } else { + TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKOUTPUT WHITESPACE OUTPUT optional_workspace_name + { + int ws_num = $3; + if (ws_num < 1) { + DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); + } else { #if 0 - Workspace *ws = workspace_get(ws_num - 1); - ws->preferred_output = $7; - if ($8 != NULL) { - workspace_set_name(ws, $8); - free($8); - } + Workspace *ws = workspace_get(ws_num - 1); + ws->preferred_output = $7; + if ($8 != NULL) { + workspace_set_name(ws, $8); + free($8); + } #endif - } } - | TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name - { - int ws_num = $3; - if (ws_num < 1) { - DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); - } else { - DLOG("workspace name to: %s\n", $5); + } + | TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name + { + int ws_num = $3; + if (ws_num < 1) { + DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); + } else { + DLOG("workspace name to: %s\n", $5); #if 0 - if ($5 != NULL) { - workspace_set_name(workspace_get(ws_num - 1), $5); - free($5); - } + if ($5 != NULL) { + workspace_set_name(workspace_get(ws_num - 1), $5); + free($5); + } #endif - } } - ; + } + ; optional_workspace_name: - /* empty */ { $$ = NULL; } - | WHITESPACE workspace_name { $$ = $2; } - ; + /* empty */ { $$ = NULL; } + | WHITESPACE workspace_name { $$ = $2; } + ; workspace_name: - QUOTEDSTRING { $$ = $1; } - | STR { $$ = $1; } - | WORD { $$ = $1; } - ; + QUOTEDSTRING { $$ = $1; } + | STR { $$ = $1; } + | WORD { $$ = $1; } + ; assign: - TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target - { - printf("assignment of %s\n", $3); + TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target + { + printf("assignment of %s\n", $3); - struct Match *match = $6; + struct Match *match = $6; - char *separator = NULL; - if ((separator = strchr($3, '/')) != NULL) { - *(separator++) = '\0'; - match->title = sstrdup(separator); - } - if (*$3 != '\0') - match->class = sstrdup($3); - free($3); - - printf(" class = %s\n", match->class); - printf(" title = %s\n", match->title); - if (match->insert_where == M_ASSIGN_WS) - printf(" to ws %s\n", match->target_ws); - TAILQ_INSERT_TAIL(&assignments, match, assignments); + char *separator = NULL; + if ((separator = strchr($3, '/')) != NULL) { + *(separator++) = '\0'; + match->title = sstrdup(separator); } - ; + if (*$3 != '\0') + match->class = sstrdup($3); + free($3); + + printf(" class = %s\n", match->class); + printf(" title = %s\n", match->title); + if (match->insert_where == M_ASSIGN_WS) + printf(" to ws %s\n", match->target_ws); + TAILQ_INSERT_TAIL(&assignments, match, assignments); + } + ; assign_target: - NUMBER - { - /* TODO: named workspaces */ - Match *match = smalloc(sizeof(Match)); - match_init(match); - match->insert_where = M_ASSIGN_WS; - asprintf(&(match->target_ws), "%d", $1); - $$ = match; - } - | '~' - { - /* TODO: compatiblity */ + NUMBER + { + /* TODO: named workspaces */ + Match *match = smalloc(sizeof(Match)); + match_init(match); + match->insert_where = M_ASSIGN_WS; + asprintf(&(match->target_ws), "%d", $1); + $$ = match; + } + | '~' + { + /* TODO: compatiblity */ #if 0 - struct Assignment *new = scalloc(sizeof(struct Assignment)); - new->floating = ASSIGN_FLOATING_ONLY; - $$ = new; + struct Assignment *new = scalloc(sizeof(struct Assignment)); + new->floating = ASSIGN_FLOATING_ONLY; + $$ = new; #endif - } - | '~' NUMBER - { - /* TODO: compatiblity */ + } + | '~' NUMBER + { + /* TODO: compatiblity */ #if 0 - struct Assignment *new = scalloc(sizeof(struct Assignment)); - new->workspace = $2; - new->floating = ASSIGN_FLOATING; - $$ = new; + struct Assignment *new = scalloc(sizeof(struct Assignment)); + new->workspace = $2; + new->floating = ASSIGN_FLOATING; + $$ = new; #endif - } - ; + } + ; window_class: - QUOTEDSTRING - | STR_NG - ; + QUOTEDSTRING + | STR_NG + ; optional_arrow: - /* NULL */ - | TOKARROW WHITESPACE - ; + /* NULL */ + | TOKARROW WHITESPACE + ; ipcsocket: - TOKIPCSOCKET WHITESPACE STR - { - config.ipc_socket_path = $3; - } - ; + TOKIPCSOCKET WHITESPACE STR + { + config.ipc_socket_path = $3; + } + ; restart_state: - TOKRESTARTSTATE WHITESPACE STR - { - config.restart_state_path = $3; - } - ; + TOKRESTARTSTATE WHITESPACE STR + { + config.restart_state_path = $3; + } + ; exec: - TOKEXEC WHITESPACE STR - { - struct Autostart *new = smalloc(sizeof(struct Autostart)); - new->command = $3; - TAILQ_INSERT_TAIL(&autostarts, new, autostarts); - } - ; + TOKEXEC WHITESPACE STR + { + struct Autostart *new = smalloc(sizeof(struct Autostart)); + new->command = $3; + TAILQ_INSERT_TAIL(&autostarts, new, autostarts); + } + ; terminal: - TOKTERMINAL WHITESPACE STR - { - ELOG("The terminal option is DEPRECATED and has no effect. " - "Please remove it from your configuration file.\n"); - } - ; + TOKTERMINAL WHITESPACE STR + { + ELOG("The terminal option is DEPRECATED and has no effect. " + "Please remove it from your configuration file.\n"); + } + ; font: - TOKFONT WHITESPACE STR - { - config.font = load_font($3, true); - printf("font %s\n", $3); - } - ; + TOKFONT WHITESPACE STR + { + config.font = load_font($3, true); + printf("font %s\n", $3); + } + ; single_color: - TOKSINGLECOLOR WHITESPACE colorpixel - { - uint32_t *dest = $1; - *dest = $3; - } - ; + TOKSINGLECOLOR WHITESPACE colorpixel + { + uint32_t *dest = $1; + *dest = $3; + } + ; color: - TOKCOLOR WHITESPACE colorpixel WHITESPACE colorpixel WHITESPACE colorpixel - { - struct Colortriple *dest = $1; + TOKCOLOR WHITESPACE colorpixel WHITESPACE colorpixel WHITESPACE colorpixel + { + struct Colortriple *dest = $1; - dest->border = $3; - dest->background = $5; - dest->text = $7; - } - ; + dest->border = $3; + dest->background = $5; + dest->text = $7; + } + ; colorpixel: - '#' HEX - { - char *hex; - if (asprintf(&hex, "#%s", $2) == -1) - die("asprintf()"); - $$ = get_colorpixel(hex); - free(hex); - } - ; + '#' HEX + { + char *hex; + if (asprintf(&hex, "#%s", $2) == -1) + die("asprintf()"); + $$ = get_colorpixel(hex); + free(hex); + } + ; binding_modifiers: - /* NULL */ { $$ = 0; } - | binding_modifier - | binding_modifiers '+' binding_modifier { $$ = $1 | $3; } - | binding_modifiers '+' { $$ = $1; } - ; + /* NULL */ { $$ = 0; } + | binding_modifier + | binding_modifiers '+' binding_modifier { $$ = $1 | $3; } + | binding_modifiers '+' { $$ = $1; } + ; binding_modifier: - MODIFIER { $$ = $1; } - | TOKCONTROL { $$ = BIND_CONTROL; } - | TOKSHIFT { $$ = BIND_SHIFT; } - ; + MODIFIER { $$ = $1; } + | TOKCONTROL { $$ = BIND_CONTROL; } + | TOKSHIFT { $$ = BIND_SHIFT; } + ; popup_during_fullscreen: - TOK_POPUP_DURING_FULLSCREEN WHITESPACE popup_setting - { - DLOG("popup_during_fullscreen setting: %d\n", $3); - config.popup_during_fullscreen = $3; - } - ; + TOK_POPUP_DURING_FULLSCREEN WHITESPACE popup_setting + { + DLOG("popup_during_fullscreen setting: %d\n", $3); + config.popup_during_fullscreen = $3; + } + ; popup_setting: - TOK_IGNORE { $$ = PDF_IGNORE; } - | TOK_LEAVE_FULLSCREEN { $$ = PDF_LEAVE_FULLSCREEN; } - ; + TOK_IGNORE { $$ = PDF_IGNORE; } + | TOK_LEAVE_FULLSCREEN { $$ = PDF_LEAVE_FULLSCREEN; } + ; From 1fe5c58764de780e03b3d1c7c2c7f27da5e08fa8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 5 May 2011 21:58:28 +0200 Subject: [PATCH 603/867] cfgparse.y: define types (Thanks Merovius) --- src/cfgparse.y | 287 ++++++++++++++++++++++++++----------------------- 1 file changed, 153 insertions(+), 134 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 33b8f774..61b67687 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -196,52 +196,71 @@ void parse_file(const char *f) { struct Binding *binding; } -%token NUMBER "" -%token WORD "" -%token STR "" -%token STR_NG "" -%token HEX "" -%token OUTPUT "" -%token TOKBINDCODE -%token TOKTERMINAL -%token TOKCOMMENT "" -%token TOKFONT "font" -%token TOKBINDSYM "bindsym" -%token MODIFIER "" -%token TOKCONTROL "control" -%token TOKSHIFT "shift" -%token WHITESPACE "" -%token TOKFLOATING_MODIFIER "floating_modifier" -%token QUOTEDSTRING "" -%token TOKWORKSPACE "workspace" -%token TOKOUTPUT "output" -%token TOKASSIGN "assign" -%token TOKSET -%token TOKIPCSOCKET "ipc_socket" -%token TOKRESTARTSTATE "restart_state" -%token TOKEXEC "exec" -%token TOKSINGLECOLOR -%token TOKCOLOR -%token TOKARROW "→" -%token TOKMODE "mode" -%token TOK_ORIENTATION "default_orientation" -%token TOK_HORIZ "horizontal" -%token TOK_VERT "vertical" -%token TOK_AUTO "auto" -%token TOK_WORKSPACE_LAYOUT "workspace_layout" -%token TOKNEWWINDOW "new_window" -%token TOK_NORMAL "normal" -%token TOK_NONE "none" -%token TOK_1PIXEL "1pixel" -%token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse" -%token TOKWORKSPACEBAR "workspace_bar" -%token TOK_DEFAULT "default" -%token TOK_STACKING "stacking" -%token TOK_TABBED "tabbed" -%token TOKSTACKLIMIT "stack-limit" -%token TOK_POPUP_DURING_FULLSCREEN "popup_during_fullscreen" -%token TOK_IGNORE "ignore" -%token TOK_LEAVE_FULLSCREEN "leave_fullscreen" +%token NUMBER "" +%token WORD "" +%token STR "" +%token STR_NG "" +%token HEX "" +%token OUTPUT "" +%token TOKBINDCODE +%token TOKTERMINAL +%token TOKCOMMENT "" +%token TOKFONT "font" +%token TOKBINDSYM "bindsym" +%token MODIFIER "" +%token TOKCONTROL "control" +%token TOKSHIFT "shift" +%token WHITESPACE "" +%token TOKFLOATING_MODIFIER "floating_modifier" +%token QUOTEDSTRING "" +%token TOKWORKSPACE "workspace" +%token TOKOUTPUT "output" +%token TOKASSIGN "assign" +%token TOKSET +%token TOKIPCSOCKET "ipc_socket" +%token TOKRESTARTSTATE "restart_state" +%token TOKEXEC "exec" +%token TOKSINGLECOLOR +%token TOKCOLOR +%token TOKARROW "→" +%token TOKMODE "mode" +%token TOK_ORIENTATION "default_orientation" +%token TOK_HORIZ "horizontal" +%token TOK_VERT "vertical" +%token TOK_AUTO "auto" +%token TOK_WORKSPACE_LAYOUT "workspace_layout" +%token TOKNEWWINDOW "new_window" +%token TOK_NORMAL "normal" +%token TOK_NONE "none" +%token TOK_1PIXEL "1pixel" +%token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse" +%token TOKWORKSPACEBAR "workspace_bar" +%token TOK_DEFAULT "default" +%token TOK_STACKING "stacking" +%token TOK_TABBED "tabbed" +%token TOKSTACKLIMIT "stack-limit" +%token TOK_POPUP_DURING_FULLSCREEN "popup_during_fullscreen" +%token TOK_IGNORE "ignore" +%token TOK_LEAVE_FULLSCREEN "leave_fullscreen" + +%type binding +%type bindcode +%type bindsym +%type binding_modifiers +%type binding_modifier +%type direction +%type layout_mode +%type border_style +%type new_window +%type colorpixel +%type bool +%type popup_setting +%type command +%type word_or_number +%type optional_workspace_name +%type workspace_name +%type window_class +%type assign_target %% @@ -284,40 +303,40 @@ command: bindline: binding { - TAILQ_INSERT_TAIL(bindings, $1, bindings); + TAILQ_INSERT_TAIL(bindings, $1, bindings); } ; binding: - TOKBINDCODE WHITESPACE bindcode { $$ = $3; } - | TOKBINDSYM WHITESPACE bindsym { $$ = $3; } + TOKBINDCODE WHITESPACE bindcode { $$ = $3; } + | TOKBINDSYM WHITESPACE bindsym { $$ = $3; } ; bindcode: binding_modifiers NUMBER WHITESPACE command { - printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $4); + printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $4); Binding *new = scalloc(sizeof(Binding)); - new->keycode = $2; - new->mods = $1; - new->command = $4; + new->keycode = $2; + new->mods = $1; + new->command = $4; - $$ = new; + $$ = new; } ; bindsym: binding_modifiers word_or_number WHITESPACE command { - printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $4); + printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $4); Binding *new = scalloc(sizeof(Binding)); - new->symbol = $2; - new->mods = $1; - new->command = $4; + new->symbol = $2; + new->mods = $1; + new->command = $4; - $$ = new; + $$ = new; } ; @@ -325,18 +344,18 @@ word_or_number: WORD | NUMBER { - asprintf(&$$, "%d", $1); + asprintf(&$$, "%d", $1); } ; mode: TOKMODE WHITESPACE QUOTEDSTRING WHITESPACE '{' modelines '}' { - if (strcasecmp($3, "default") == 0) { + if (strcasecmp($3, "default") == 0) { printf("You cannot use the name \"default\" for your mode\n"); exit(1); } - printf("\t now in mode %s\n", $3); + printf("\t now in mode %s\n", $3); printf("\t current bindings = %p\n", current_bindings); Binding *binding; TAILQ_FOREACH(binding, current_bindings, bindings) { @@ -345,7 +364,7 @@ mode: } struct Mode *mode = scalloc(sizeof(struct Mode)); - mode->name = $3; + mode->name = $3; mode->bindings = current_bindings; current_bindings = NULL; SLIST_INSERT_HEAD(&modes, mode, modes); @@ -368,37 +387,37 @@ modeline: TAILQ_INIT(current_bindings); } - TAILQ_INSERT_TAIL(current_bindings, $1, bindings); + TAILQ_INSERT_TAIL(current_bindings, $1, bindings); } ; floating_modifier: TOKFLOATING_MODIFIER WHITESPACE binding_modifiers { - DLOG("floating modifier = %d\n", $3); - config.floating_modifier = $3; + DLOG("floating modifier = %d\n", $3); + config.floating_modifier = $3; } ; orientation: TOK_ORIENTATION WHITESPACE direction { - DLOG("New containers should start with split direction %d\n", $3); - config.default_orientation = $3; + DLOG("New containers should start with split direction %d\n", $3); + config.default_orientation = $3; } ; direction: - TOK_HORIZ { $$ = HORIZ; } - | TOK_VERT { $$ = VERT; } - | TOK_AUTO { $$ = NO_ORIENTATION; } + TOK_HORIZ { $$ = HORIZ; } + | TOK_VERT { $$ = VERT; } + | TOK_AUTO { $$ = NO_ORIENTATION; } ; workspace_layout: TOK_WORKSPACE_LAYOUT WHITESPACE layout_mode { - DLOG("new containers will be in mode %d\n", $3); - config.default_layout = $3; + DLOG("new containers will be in mode %d\n", $3); + config.default_layout = $3; #if 0 /* We also need to change the layout of the already existing @@ -421,9 +440,9 @@ workspace_layout: } | TOK_WORKSPACE_LAYOUT WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER { - DLOG("stack-limit %d with val %d\n", $5, $7); - config.container_stack_limit = $5; - config.container_stack_limit_value = $7; + DLOG("stack-limit %d with val %d\n", $5, $7); + config.container_stack_limit = $5; + config.container_stack_limit_value = $7; #if 0 /* See the comment above */ @@ -440,61 +459,61 @@ workspace_layout: ; layout_mode: - TOK_DEFAULT { $$ = L_DEFAULT; } - | TOK_STACKING { $$ = L_STACKED; } - | TOK_TABBED { $$ = L_TABBED; } + TOK_DEFAULT { $$ = L_DEFAULT; } + | TOK_STACKING { $$ = L_STACKED; } + | TOK_TABBED { $$ = L_TABBED; } ; new_window: TOKNEWWINDOW WHITESPACE border_style { - DLOG("new windows should start with border style %d\n", $3); - config.default_border = $3; + DLOG("new windows should start with border style %d\n", $3); + config.default_border = $3; } ; border_style: - TOK_NORMAL { $$ = BS_NORMAL; } - | TOK_NONE { $$ = BS_NONE; } - | TOK_1PIXEL { $$ = BS_1PIXEL; } + TOK_NORMAL { $$ = BS_NORMAL; } + | TOK_NONE { $$ = BS_NONE; } + | TOK_1PIXEL { $$ = BS_1PIXEL; } ; bool: NUMBER { - $$ = ($1 == 1); + $$ = ($1 == 1); } | WORD { - DLOG("checking word \"%s\"\n", $1); - $$ = (strcasecmp($1, "yes") == 0 || - strcasecmp($1, "true") == 0 || - strcasecmp($1, "on") == 0 || - strcasecmp($1, "enable") == 0 || - strcasecmp($1, "active") == 0); + DLOG("checking word \"%s\"\n", $1); + $$ = (strcasecmp($1, "yes") == 0 || + strcasecmp($1, "true") == 0 || + strcasecmp($1, "on") == 0 || + strcasecmp($1, "enable") == 0 || + strcasecmp($1, "active") == 0); } ; focus_follows_mouse: TOKFOCUSFOLLOWSMOUSE WHITESPACE bool { - DLOG("focus follows mouse = %d\n", $3); - config.disable_focus_follows_mouse = !($3); + DLOG("focus follows mouse = %d\n", $3); + config.disable_focus_follows_mouse = !($3); } ; workspace_bar: TOKWORKSPACEBAR WHITESPACE bool { - DLOG("workspace bar = %d\n", $3); - config.disable_workspace_bar = !($3); + DLOG("workspace bar = %d\n", $3); + config.disable_workspace_bar = !($3); } ; workspace: TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKOUTPUT WHITESPACE OUTPUT optional_workspace_name { - int ws_num = $3; + int ws_num = $3; if (ws_num < 1) { DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { @@ -510,11 +529,11 @@ workspace: } | TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name { - int ws_num = $3; + int ws_num = $3; if (ws_num < 1) { DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { - DLOG("workspace name to: %s\n", $5); + DLOG("workspace name to: %s\n", $5); #if 0 if ($5 != NULL) { workspace_set_name(workspace_get(ws_num - 1), $5); @@ -526,31 +545,31 @@ workspace: ; optional_workspace_name: - /* empty */ { $$ = NULL; } - | WHITESPACE workspace_name { $$ = $2; } + /* empty */ { $$ = NULL; } + | WHITESPACE workspace_name { $$ = $2; } ; workspace_name: - QUOTEDSTRING { $$ = $1; } - | STR { $$ = $1; } - | WORD { $$ = $1; } + QUOTEDSTRING { $$ = $1; } + | STR { $$ = $1; } + | WORD { $$ = $1; } ; assign: TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target { - printf("assignment of %s\n", $3); + printf("assignment of %s\n", $3); - struct Match *match = $6; + struct Match *match = $6; char *separator = NULL; - if ((separator = strchr($3, '/')) != NULL) { + if ((separator = strchr($3, '/')) != NULL) { *(separator++) = '\0'; match->title = sstrdup(separator); } - if (*$3 != '\0') - match->class = sstrdup($3); - free($3); + if (*$3 != '\0') + match->class = sstrdup($3); + free($3); printf(" class = %s\n", match->class); printf(" title = %s\n", match->title); @@ -567,8 +586,8 @@ assign_target: Match *match = smalloc(sizeof(Match)); match_init(match); match->insert_where = M_ASSIGN_WS; - asprintf(&(match->target_ws), "%d", $1); - $$ = match; + asprintf(&(match->target_ws), "%d", $1); + $$ = match; } | '~' { @@ -604,14 +623,14 @@ optional_arrow: ipcsocket: TOKIPCSOCKET WHITESPACE STR { - config.ipc_socket_path = $3; + config.ipc_socket_path = $3; } ; restart_state: TOKRESTARTSTATE WHITESPACE STR { - config.restart_state_path = $3; + config.restart_state_path = $3; } ; @@ -619,7 +638,7 @@ exec: TOKEXEC WHITESPACE STR { struct Autostart *new = smalloc(sizeof(struct Autostart)); - new->command = $3; + new->command = $3; TAILQ_INSERT_TAIL(&autostarts, new, autostarts); } ; @@ -635,27 +654,27 @@ terminal: font: TOKFONT WHITESPACE STR { - config.font = load_font($3, true); - printf("font %s\n", $3); + config.font = load_font($3, true); + printf("font %s\n", $3); } ; single_color: TOKSINGLECOLOR WHITESPACE colorpixel { - uint32_t *dest = $1; - *dest = $3; + uint32_t *dest = $1; + *dest = $3; } ; color: TOKCOLOR WHITESPACE colorpixel WHITESPACE colorpixel WHITESPACE colorpixel { - struct Colortriple *dest = $1; + struct Colortriple *dest = $1; - dest->border = $3; - dest->background = $5; - dest->text = $7; + dest->border = $3; + dest->background = $5; + dest->text = $7; } ; @@ -663,36 +682,36 @@ colorpixel: '#' HEX { char *hex; - if (asprintf(&hex, "#%s", $2) == -1) + if (asprintf(&hex, "#%s", $2) == -1) die("asprintf()"); - $$ = get_colorpixel(hex); + $$ = get_colorpixel(hex); free(hex); } ; binding_modifiers: - /* NULL */ { $$ = 0; } + /* NULL */ { $$ = 0; } | binding_modifier - | binding_modifiers '+' binding_modifier { $$ = $1 | $3; } - | binding_modifiers '+' { $$ = $1; } + | binding_modifiers '+' binding_modifier { $$ = $1 | $3; } + | binding_modifiers '+' { $$ = $1; } ; binding_modifier: - MODIFIER { $$ = $1; } - | TOKCONTROL { $$ = BIND_CONTROL; } - | TOKSHIFT { $$ = BIND_SHIFT; } + MODIFIER { $$ = $1; } + | TOKCONTROL { $$ = BIND_CONTROL; } + | TOKSHIFT { $$ = BIND_SHIFT; } ; popup_during_fullscreen: TOK_POPUP_DURING_FULLSCREEN WHITESPACE popup_setting { - DLOG("popup_during_fullscreen setting: %d\n", $3); - config.popup_during_fullscreen = $3; + DLOG("popup_during_fullscreen setting: %d\n", $3); + config.popup_during_fullscreen = $3; } ; popup_setting: - TOK_IGNORE { $$ = PDF_IGNORE; } - | TOK_LEAVE_FULLSCREEN { $$ = PDF_LEAVE_FULLSCREEN; } + TOK_IGNORE { $$ = PDF_IGNORE; } + | TOK_LEAVE_FULLSCREEN { $$ = PDF_LEAVE_FULLSCREEN; } ; From e73c171e0d1f7800138314fb3112cf92e31fc3ae Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 6 May 2011 13:09:50 +0200 Subject: [PATCH 604/867] Bugfix: assign BORDER_BOTTOM instead of BORDER_RIGHT (Thanks Jan) --- src/floating.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/floating.c b/src/floating.c index 85aaa6e7..2ac4afda 100644 --- a/src/floating.c +++ b/src/floating.c @@ -381,7 +381,7 @@ void floating_resize_window(Con *con, bool proportional, if (event->event_y <= (con->rect.height / 2)) corner |= BORDER_TOP; - else corner |= BORDER_RIGHT; + else corner |= BORDER_BOTTOM; struct resize_window_callback_params params = { corner, proportional, event }; From 78264958c0451dd7e28743efed36733743ae761e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 8 May 2011 19:56:11 +0200 Subject: [PATCH 605/867] makefile: add COVERAGE flag --- common.mk | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/common.mk b/common.mk index 86585e59..fed147b8 100644 --- a/common.mk +++ b/common.mk @@ -1,5 +1,6 @@ UNAME=$(shell uname) DEBUG=1 +COVERAGE=0 INSTALL=install PREFIX=/usr ifeq ($(PREFIX),/usr) @@ -97,6 +98,11 @@ CFLAGS += -O2 CFLAGS += -freorder-blocks-and-partition endif +ifeq ($(COVERAGE),1) +CFLAGS += -fprofile-arcs -ftest-coverage +LDFLAGS += -lgcov +endif + # Don’t print command lines which are run .SILENT: From 098fc0694283a42253f4f6e1b274f55eb43c49d0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 8 May 2011 20:08:35 +0200 Subject: [PATCH 606/867] tests: add --coverage-testing option to complete-run.pl --- testcases/complete-run.pl | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 9f201e1b..c3a65480 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -11,6 +11,9 @@ use Proc::Background; use TAP::Harness; use TAP::Parser::Aggregator; use File::Basename qw(basename); +use AnyEvent::I3 qw(:all); +use Try::Tiny; +use Getopt::Long; # reads in a whole file sub slurp { @@ -19,6 +22,12 @@ sub slurp { <$fh>; } +my $coverage_testing = 0; + +my $result = GetOptions( + "coverage-testing" => \$coverage_testing +); + my $i3cmd = "export DISPLAY=:0; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c " . abs_path("../i3.config"); # 1: get a list of all testcases @@ -51,7 +60,24 @@ for my $t (@testfiles) { my $process = Proc::Background->new($cmd) unless $dont_start; say "testing $t with logfile $logpath"; $harness->aggregate_tests($aggregator, [ $t ]); - kill(9, $process->pid) or die "could not kill i3" unless $dont_start; + + # Don’t bother killing i3 when we haven’t started it + next if $dont_start; + + # When measuring code coverage, try to exit i3 cleanly (otherwise, .gcda + # files are not written) and fallback to killing it + if ($coverage_testing) { + my $exited = 0; + try { + say "Exiting i3 cleanly..."; + i3("/tmp/nestedcons")->command('exit')->recv; + $exited = 1; + }; + next if $exited; + } + + say "Killing i3"; + kill(9, $process->pid) or die "could not kill i3"; } $aggregator->stop(); From 7e587f3570690f5e1a12017965ac23fc866c2663 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 8 May 2011 20:08:46 +0200 Subject: [PATCH 607/867] add coverage target to makefile to generate a coverage report --- Makefile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 991fb49f..c9359142 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,8 @@ dist: distclean rm -rf i3-${VERSION} clean: - rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.yy.c src/cfgparse.output src/cmdparse.tab.{c,h} src/cmdparse.yy.c src/cmdparse.output loglevels.tmp include/loglevels.h + rm -f src/*.o src/*.gcno src/cfgparse.tab.{c,h} src/cfgparse.yy.c src/cfgparse.output src/cmdparse.tab.{c,h} src/cmdparse.yy.c src/cmdparse.output loglevels.tmp include/loglevels.h + (which lcov >/dev/null && lcov -d . --zerocounters) || true $(MAKE) -C docs clean $(MAKE) -C man clean $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg clean @@ -105,3 +106,9 @@ distclean: clean rm -f i3 $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg distclean $(MAKE) TOPDIR=$(TOPDIR) -C i3-input distclean + +coverage: + rm -f /tmp/i3-coverage.info + rm -rf /tmp/i3-coverage + lcov -d . -b . --capture -o /tmp/i3-coverage.info + genhtml -o /tmp/i3-coverage/ /tmp/i3-coverage.info From eb8ad348b28e243cba1972e802ca8ee636472fc9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 11 May 2011 20:22:47 +0200 Subject: [PATCH 608/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20run=20into=20?= =?UTF-8?q?an=20endless=20loop=20when=20killing=20con=20with=20children=20?= =?UTF-8?q?(Thanks=20mseed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a tabbed container had more than one child and at least the first one supported WM_DELETE, i3 entered an endless loop when killing that tabbed container. This was due to tree_close only sending WM_DELETE without actually removing the child, while the loop in tree_close assumed that with every call of tree_close one child would be removed. --- include/tree.h | 6 ++++-- src/tree.c | 28 ++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/include/tree.h b/include/tree.h index 40d9a541..8ffeca0d 100644 --- a/include/tree.h +++ b/include/tree.h @@ -66,10 +66,12 @@ void tree_close_con(); void tree_next(char way, orientation_t orientation); /** - * Closes the given container including all children + * Closes the given container including all children. + * Returns true if the container was killed or false if just WM_DELETE was sent + * and the window is expected to kill itself. * */ -void tree_close(Con *con, bool kill_window, bool dont_kill_parent); +bool tree_close(Con *con, bool kill_window, bool dont_kill_parent); /** * Loads tree from ~/.i3/_restart.json (used for in-place restarts). diff --git a/src/tree.c b/src/tree.c index b68af36b..f5024a55 100644 --- a/src/tree.c +++ b/src/tree.c @@ -94,10 +94,12 @@ static bool _is_con_mapped(Con *con) { } /* - * Closes the given container including all children + * Closes the given container including all children. + * Returns true if the container was killed or false if just WM_DELETE was sent + * and the window is expected to kill itself. * */ -void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { +bool tree_close(Con *con, bool kill_window, bool dont_kill_parent) { bool was_mapped = con->mapped; Con *parent = con->parent; @@ -113,19 +115,27 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { DLOG("next = %p, focused = %p\n", next, focused); DLOG("closing %p, kill_window = %d\n", con, kill_window); - Con *child; + Con *child, *nextchild; + bool abort_kill = false; /* We cannot use TAILQ_FOREACH because the children get deleted * in their parent’s nodes_head */ - while (!TAILQ_EMPTY(&(con->nodes_head))) { - child = TAILQ_FIRST(&(con->nodes_head)); + for (child = TAILQ_FIRST(&(con->nodes_head)); child; ) { + nextchild = TAILQ_NEXT(child, nodes); DLOG("killing child=%p\n", child); - tree_close(child, kill_window, true); + if (!tree_close(child, kill_window, true)) + abort_kill = true; + child = nextchild; + } + + if (abort_kill) { + DLOG("One of the children could not be killed immediately (WM_DELETE sent), aborting.\n"); + return false; } if (con->window != NULL) { if (kill_window) { x_window_kill(con->window->id); - return; + return false; } else { /* un-parent the window */ xcb_reparent_window(conn, con->window->id, root, 0, 0); @@ -175,7 +185,7 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { * when closing the parent, so we can exit now. */ if (!next) { DLOG("No next container, i will just exit now\n"); - return; + return true; } if (was_mapped || con == focused) { @@ -199,6 +209,8 @@ void tree_close(Con *con, bool kill_window, bool dont_kill_parent) { /* check if the parent container is empty now and close it */ if (!dont_kill_parent) CALL(parent, on_remove_child); + + return true; } /* From 9c05c1815625b10ec397c12730b3ce4ad4986d84 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 11 May 2011 20:39:18 +0200 Subject: [PATCH 609/867] ipc: change border_style to human-readable string instead of enum value --- src/ipc.c | 12 +++++++++++- testcases/t/16-nestedcons.t | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index d0dc1920..0535d122 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -206,7 +206,17 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { y(integer, con->layout); ystr("border"); - y(integer, con->border_style); + switch (con->border_style) { + case BS_NORMAL: + ystr("normal"); + break; + case BS_NONE: + ystr("none"); + break; + case BS_1PIXEL: + ystr("1pixel"); + break; + } dump_rect(gen, "rect", con->rect); dump_rect(gen, "window_rect", con->window_rect); diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t index e8a124f9..954732dd 100644 --- a/testcases/t/16-nestedcons.t +++ b/testcases/t/16-nestedcons.t @@ -30,7 +30,7 @@ my $expected = { focus => ignore(), focused => 0, urgent => 0, - border => 0, + border => 'normal', 'floating_nodes' => ignore(), }; From 4da6fc7ba36d8d2fb8e466e672e7c4fb953ffa9f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 11 May 2011 20:45:56 +0200 Subject: [PATCH 610/867] Bugfix: Restore border_style when restarting inplace (Thanks aniou) Fixes #385. --- src/load_layout.c | 11 +++++++ testcases/t/61-regress-borders-restart.t | 39 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 testcases/t/61-regress-borders-restart.t diff --git a/src/load_layout.c b/src/load_layout.c index f946c666..c5b6bcf1 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -120,6 +120,17 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) { json_node->orientation = VERT; else LOG("Unhandled orientation: %s\n", buf); free(buf); + } else if (strcasecmp(last_key, "border") == 0) { + char *buf = NULL; + asprintf(&buf, "%.*s", (int)len, val); + if (strcasecmp(buf, "none") == 0) + json_node->border_style = BS_NONE; + else if (strcasecmp(buf, "1pixel") == 0) + json_node->border_style = BS_1PIXEL; + else if (strcasecmp(buf, "normal") == 0) + json_node->border_style = BS_NORMAL; + else LOG("Unhandled \"border\": %s\n", buf); + free(buf); } } return 1; diff --git a/testcases/t/61-regress-borders-restart.t b/testcases/t/61-regress-borders-restart.t new file mode 100644 index 00000000..25a73b5d --- /dev/null +++ b/testcases/t/61-regress-borders-restart.t @@ -0,0 +1,39 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Regression test to check if borders are correctly restored after an inplace +# restart. +# found in eb8ad348b28e243cba1972e802ca8ee636472fc9 +# +use X11::XCB qw(:all); +use List::Util qw(first); +use i3test; + +my $x = X11::XCB::Connection->new; +my $i3 = i3("/tmp/nestedcons"); +my $tmp = fresh_workspace; +my $window = open_standard_window($x); + +sub get_border_style { + my @content = @{get_ws_content($tmp)}; + my $wininfo = first { $_->{window} == $window->id } @content; + + return $wininfo->{border}; +} + +is(get_border_style(), 'normal', 'border style normal'); + +cmd 'border 1pixel'; + +is(get_border_style(), '1pixel', 'border style 1pixel after changing'); + +# perform an inplace-restart +cmd 'restart'; + +sleep 0.25; + +does_i3_live; + +is(get_border_style(), '1pixel', 'border style still 1pixel after restart'); + +done_testing; From 4be3178d4d360c2996217d811e61161c84d25898 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 11 May 2011 22:01:09 +0200 Subject: [PATCH 611/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20fill=20split?= =?UTF-8?q?=20cons=20etc.=20with=20client=20background=20color=20(fixes=20?= =?UTF-8?q?nested=20decoration=20rendering)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: #359 --- src/x.c | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/x.c b/src/x.c index 358a2201..76add0ff 100644 --- a/src/x.c +++ b/src/x.c @@ -303,28 +303,30 @@ void x_draw_decoration(Con *con) { deco_rect.height = 0; /* 2: draw the client.background, but only for the parts around the client_rect */ - xcb_rectangle_t background[] = { - /* top area */ - { 0, 0, r->width, w->y }, - /* bottom area */ - { 0, (w->y + w->height), r->width, r->height - (w->y + w->height) }, - /* left area */ - { 0, 0, w->x, r->height }, - /* right area */ - { w->x + w->width, 0, r->width - (w->x + w->width), r->height } - }; + if (con->window != NULL) { + xcb_rectangle_t background[] = { + /* top area */ + { 0, 0, r->width, w->y }, + /* bottom area */ + { 0, (w->y + w->height), r->width, r->height - (w->y + w->height) }, + /* left area */ + { 0, 0, w->x, r->height }, + /* right area */ + { w->x + w->width, 0, r->width - (w->x + w->width), r->height } + }; #if 0 - for (int i = 0; i < 4; i++) - DLOG("rect is (%d, %d) with %d x %d\n", - background[i].x, - background[i].y, - background[i].width, - background[i].height - ); + for (int i = 0; i < 4; i++) + DLOG("rect is (%d, %d) with %d x %d\n", + background[i].x, + background[i].y, + background[i].width, + background[i].height + ); #endif - xcb_change_gc_single(conn, con->pm_gc, XCB_GC_FOREGROUND, config.client.background); - xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, sizeof(background) / sizeof(xcb_rectangle_t), background); + xcb_change_gc_single(conn, con->pm_gc, XCB_GC_FOREGROUND, config.client.background); + xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, sizeof(background) / sizeof(xcb_rectangle_t), background); + } /* 3: draw a rectangle in border color around the client */ if (p->border_style != BS_NONE && p->con_is_leaf) { From c62f70856f04e6053bb57b39e3bb82e52fb95955 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 11 May 2011 22:45:20 +0200 Subject: [PATCH 612/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20crash=20when?= =?UTF-8?q?=20dock=20clients=20set=20the=20urgency=20hint=20(+testcase)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handlers.c | 6 ++- testcases/t/62-regress-dock-urgent.t | 59 ++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 testcases/t/62-regress-dock-urgent.t diff --git a/src/handlers.c b/src/handlers.c index 2f954823..589b81f6 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -813,7 +813,11 @@ static int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w //CLIENT_LOG(con); LOG("Urgency flag changed to %d\n", con->urgent); - workspace_update_urgent_flag(con_get_workspace(con)); + Con *ws; + /* Set the urgency flag on the workspace, if a workspace could be found + * (for dock clients, that is not the case). */ + if ((ws = con_get_workspace(con)) != NULL) + workspace_update_urgent_flag(ws); tree_render(); diff --git a/testcases/t/62-regress-dock-urgent.t b/testcases/t/62-regress-dock-urgent.t new file mode 100644 index 00000000..78bf3e80 --- /dev/null +++ b/testcases/t/62-regress-dock-urgent.t @@ -0,0 +1,59 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Regression test for setting the urgent hint on dock clients. +# found in 4be3178d4d360c2996217d811e61161c84d25898 +# +use X11::XCB qw(:all); +use i3test; + +BEGIN { + use_ok('X11::XCB::Window'); +} + +my $x = X11::XCB::Connection->new; +my $i3 = i3("/tmp/nestedcons"); + +my $tmp = fresh_workspace; + +##################################################################### +# verify that there is no dock window yet +##################################################################### + +# Children of all dockareas +my @docked = get_dock_clients; + +is(@docked, 0, 'no dock clients yet'); + +# open a dock client + +my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), +); + +$window->map; + +sleep 0.25; + +##################################################################### +# check that we can find it in the layout tree at the expected position +##################################################################### + +@docked = get_dock_clients; +is(@docked, 1, 'one dock client found'); + +# verify the height +my $docknode = $docked[0]; + +is($docknode->{rect}->{height}, 30, 'dock node has unchanged height'); + +$window->add_hint('urgency'); + +sleep 0.25; + +does_i3_live; + +done_testing; From 2e75d934a4c16f8741956285063ea8a46b7728fb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 12 May 2011 06:58:09 +0200 Subject: [PATCH 613/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20append=20the?= =?UTF-8?q?=20--restart=20parameter=20on=20each=20restart=20(Thanks=20anio?= =?UTF-8?q?u)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: #384 --- src/util.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/util.c b/src/util.c index d9de98e8..f95ccaf3 100644 --- a/src/util.c +++ b/src/util.c @@ -379,7 +379,8 @@ void i3_restart(bool forget_layout) { for (int i = 0; i < num_args; ++i) { if (skip_next) skip_next = false; - else if (!strcmp(start_argv[i], "-r")) + else if (!strcmp(start_argv[i], "-r") || + !strcmp(start_argv[i], "--restart")) skip_next = true; else new_argv[write_index++] = start_argv[i]; From 94646190aaddc1285d06cd7f90721230246caa85 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 12 May 2011 07:09:06 +0200 Subject: [PATCH 614/867] Bugfix: Correct string/quoted string parsing for the commands exec, workspace, nop, restore and mark (Thanks SardemFF7) Fixes: #380 --- src/cmdparse.l | 6 +++--- testcases/t/20-multiple-cmds.t | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/cmdparse.l b/src/cmdparse.l index ebd466af..f07420d3 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -65,16 +65,16 @@ EOL (\r?\n) cmdyycolumn = 1; } -[^\n]+ { BEGIN(INITIAL); cmdyylval.string = sstrdup(yytext); return STR; } [ \t]* { BEGIN(WANT_STRING); return WHITESPACE; } -\"[^\"]+\" { +\"[^\"]+\" { BEGIN(INITIAL); /* strip quotes */ char *copy = sstrdup(yytext+1); copy[strlen(copy)-1] = '\0'; cmdyylval.string = copy; return STR; - } + } +[^;\n]+ { BEGIN(INITIAL); cmdyylval.string = sstrdup(yytext); return STR; } [ \t]* { return WHITESPACE; } attach { return TOK_ATTACH; } diff --git a/testcases/t/20-multiple-cmds.t b/testcases/t/20-multiple-cmds.t index 379d08ee..784329fb 100644 --- a/testcases/t/20-multiple-cmds.t +++ b/testcases/t/20-multiple-cmds.t @@ -30,6 +30,27 @@ multiple_cmds("kill\t ;\tkill"); multiple_cmds("kill\t ;\t kill"); multiple_cmds("kill \t ; \t kill"); +##################################################################### +# test if un-quoted strings are handled correctly +##################################################################### + +$tmp = fresh_workspace; +cmd 'open'; +my $unused = get_unused_workspace; +ok(!($unused ~~ @{get_workspace_names()}), 'workspace does not exist yet'); +cmd "move workspace $unused; nop parser test"; +ok(($unused ~~ @{get_workspace_names()}), 'workspace exists after moving'); + +##################################################################### +# quote the workspace name and use a ; (command separator) in its name +##################################################################### + +$unused = get_unused_workspace; +$unused .= ';a'; +ok(!($unused ~~ @{get_workspace_names()}), 'workspace does not exist yet'); +cmd qq|move workspace "$unused"; nop parser test|; +ok(($unused ~~ @{get_workspace_names()}), 'workspace exists after moving'); + # TODO: need a non-invasive command before implementing a test which uses ',' done_testing; From 6e32e6123dbc913c77c0bd576ec11644730bd027 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 12 May 2011 07:22:17 +0200 Subject: [PATCH 615/867] Bugfix: Ignore focus when attaching cons while restoring the layout Fixes: #369 --- src/load_layout.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/load_layout.c b/src/load_layout.c index c5b6bcf1..7e57e94d 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -49,7 +49,7 @@ static int json_end_map(void *ctx) { LOG("end of map\n"); if (!parsing_swallows && !parsing_rect && !parsing_window_rect && !parsing_geometry) { LOG("attaching\n"); - con_attach(json_node, json_node->parent, false); + con_attach(json_node, json_node->parent, true); json_node = json_node->parent; } if (parsing_rect) From 15c288f7d75d363a5e11946b14b69b54370993c6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 12 May 2011 17:20:00 +0200 Subject: [PATCH 616/867] s/seperate/separate (Thanks Jon) --- docs/tree-migrating | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tree-migrating b/docs/tree-migrating index 15ea54e4..d356bbea 100644 --- a/docs/tree-migrating +++ b/docs/tree-migrating @@ -7,7 +7,7 @@ November 2010 The tree branch (referring to a branch of i3 in the git repository) is the new version of i3. Due to the very deep changes and heavy refactoring of the source -source, we decided to develop it in a seperate branch (instead of using the +source, we decided to develop it in a separate branch (instead of using the next/master-branch system like before). == Current status From b0e871e0cfdc35f2147c4497136c5b74c9ebafe7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 12 May 2011 22:24:52 +0200 Subject: [PATCH 617/867] Bugfix: Fix focus follows mouse for non-default layout cons (Thanks phnom) Fixes: #361 --- src/handlers.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 589b81f6..c63f236c 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -184,19 +184,14 @@ static int handle_enter_notify(xcb_enter_notify_event_t *event) { /* see if the user entered the window on a certain window decoration */ int layout = (enter_child ? con->parent->layout : con->layout); - Con *child; - TAILQ_FOREACH(child, &(con->nodes_head), nodes) - if (rect_contains(child->deco_rect, event->event_x, event->event_y)) { - LOG("using child %p / %s instead!\n", child, child->name); - con = child; - break; - } - - /* for stacked/tabbed layout we do not want to change focus when the user - * enters the window at the decoration of any child window. */ - if (layout == L_STACKED || layout == L_TABBED) { - con = TAILQ_FIRST(&(con->parent->focus_head)); - LOG("using focused %p / %s instead\n", con, con->name); + if (layout == L_DEFAULT) { + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) + if (rect_contains(child->deco_rect, event->event_x, event->event_y)) { + LOG("using child %p / %s instead!\n", child, child->name); + con = child; + break; + } } #if 0 From 62e977102b71941f83e267754459bdc93d696c89 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 13 May 2011 17:03:15 +0200 Subject: [PATCH 618/867] Bugfix: Fix the WANT_QSTRING state --- src/cmdparse.l | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/cmdparse.l b/src/cmdparse.l index f07420d3..3e5dc889 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -74,6 +74,15 @@ EOL (\r?\n) cmdyylval.string = copy; return STR; } +\"[^\"]+\" { + BEGIN(INITIAL); + /* strip quotes */ + char *copy = sstrdup(yytext+1); + copy[strlen(copy)-1] = '\0'; + cmdyylval.string = copy; + return STR; + } + [^;\n]+ { BEGIN(INITIAL); cmdyylval.string = sstrdup(yytext); return STR; } [ \t]* { return WHITESPACE; } From 836a3ad615447e6619f03d5aa1128f5604398069 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 13 May 2011 17:03:52 +0200 Subject: [PATCH 619/867] Bugfix: set WM_STATE to WITHDRAWN when an app unmaps their window(s) (+test) Fixes: #362 --- src/tree.c | 7 +++++-- testcases/t/63-wm-state.t | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 testcases/t/63-wm-state.t diff --git a/src/tree.c b/src/tree.c index f5024a55..411e5251 100644 --- a/src/tree.c +++ b/src/tree.c @@ -139,8 +139,11 @@ bool tree_close(Con *con, bool kill_window, bool dont_kill_parent) { } else { /* un-parent the window */ xcb_reparent_window(conn, con->window->id, root, 0, 0); - /* TODO: client_unmap to set state to withdrawn */ - + /* We are no longer handling this window, thus set WM_STATE to + * WM_STATE_WITHDRAWN (see ICCCM 4.1.3.1) */ + long data[] = { XCB_ICCCM_WM_STATE_WITHDRAWN, XCB_NONE }; + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, + A_WM_STATE, A_WM_STATE, 32, 2, data); } FREE(con->window->class_class); FREE(con->window->class_instance); diff --git a/testcases/t/63-wm-state.t b/testcases/t/63-wm-state.t new file mode 100644 index 00000000..7e983289 --- /dev/null +++ b/testcases/t/63-wm-state.t @@ -0,0 +1,41 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when +# unmapped. +# +use X11::XCB qw(:all); +use i3test; + +BEGIN { + use_ok('X11::XCB::Window'); + use_ok('X11::XCB::Event::Generic'); + use_ok('X11::XCB::Event::MapNotify'); + use_ok('X11::XCB::Event::ClientMessage'); +} + +my $x = X11::XCB::Connection->new; + +my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#00ff00', + event_mask => [ 'structure_notify' ], +); + +$window->name('Window 1'); +$window->map; + +diag('window mapped'); + +sleep 0.5; + +is($window->state, ICCCM_WM_STATE_NORMAL, 'WM_STATE normal'); + +$window->unmap; + +sleep 0.5; + +is($window->state, ICCCM_WM_STATE_WITHDRAWN, 'WM_STATE withdrawn'); + +done_testing; From 5eef824495d99ff22c2054dea56a0f75791df2f5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 13 May 2011 19:27:18 +0200 Subject: [PATCH 620/867] t/35-floating-focus: rewrite testcase to use windows instead of empty cons --- testcases/t/35-floating-focus.t | 93 ++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/testcases/t/35-floating-focus.t b/testcases/t/35-floating-focus.t index e07b91bd..83c1c90c 100644 --- a/testcases/t/35-floating-focus.t +++ b/testcases/t/35-floating-focus.t @@ -2,6 +2,10 @@ # vim:ts=4:sw=4:expandtab use i3test; +use X11::XCB qw(:all); +use X11::XCB::Connection; + +my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; @@ -9,61 +13,78 @@ my $tmp = fresh_workspace; # 1: see if focus stays the same when toggling tiling/floating mode ############################################################################# -cmd "open"; -cmd "open"; +my $first = open_standard_window($x); +my $second = open_standard_window($x); -my @content = @{get_ws_content($tmp)}; -cmp_ok(@content, '==', 2, 'two containers opened'); -cmp_ok($content[1]->{focused}, '==', 1, 'Second container focused'); +is($x->input_focus, $second->id, 'second window focused'); -cmd "mode floating"; -cmd "mode tiling"; +cmd 'mode floating'; +cmd 'mode tiling'; -@content = @{get_ws_content($tmp)}; -cmp_ok($content[1]->{focused}, '==', 1, 'Second container still focused after mode toggle'); +is($x->input_focus, $second->id, 'second window still focused after mode toggle'); ############################################################################# -# 2: see if the focus gets reverted correctly when closing floating clients +# 2: see if focus stays on the current floating window if killing another +# floating window +############################################################################# + +$tmp = fresh_workspace; + +$first = open_standard_window($x); # window 2 +$second = open_standard_window($x); # window 3 +my $third = open_standard_window($x); # window 4 + +is($x->input_focus, $third->id, 'last container focused'); + +cmd 'mode floating'; + +cmd '[id="' . $second->id . '"] focus'; + +is($x->input_focus, $second->id, 'second con focused'); + +cmd 'mode floating'; + +# now kill the third one (it's floating). focus should stay unchanged +cmd '[id="' . $third->id . '"] kill'; + +sleep 0.25; + +is($x->input_focus, $second->id, 'second con still focused after killing third'); + + +############################################################################# +# 3: see if the focus gets reverted correctly when closing floating clients # (first to the next floating client, then to the last focused tiling client) ############################################################################# $tmp = fresh_workspace; -cmd "open"; -cmd "open"; -cmd "open"; +$first = open_standard_window($x); # window 2 +$second = open_standard_window($x); # window 3 +my $third = open_standard_window($x); # window 4 -@content = @{get_ws_content($tmp)}; -cmp_ok(@content, '==', 3, 'two containers opened'); -cmp_ok($content[2]->{focused}, '==', 1, 'Last container focused'); +is($x->input_focus, $third->id, 'last container focused'); -my $last_id = $content[2]->{id}; -my $second_id = $content[1]->{id}; -my $first_id = $content[0]->{id}; -diag("last_id = $last_id, second_id = $second_id, first_id = $first_id"); +cmd 'mode floating'; -cmd qq|[con_id="$second_id"] focus|; -@content = @{get_ws_content($tmp)}; -cmp_ok($content[1]->{focused}, '==', 1, 'Second container focused'); +cmd '[id="' . $second->id . '"] focus'; -cmd "mode floating"; +is($x->input_focus, $second->id, 'second con focused'); -cmd qq|[con_id="$last_id"] focus|; -@content = @{get_ws_content($tmp)}; -cmp_ok($content[1]->{focused}, '==', 1, 'Last container focused'); +cmd 'mode floating'; -cmd "mode floating"; +# now kill the second one. focus should fall back to the third one, which is +# also floating +cmd 'kill'; -diag("focused = " . get_focused($tmp)); +sleep 0.25; -cmd "kill"; +is($x->input_focus, $third->id, 'third con focused'); -diag("focused = " . get_focused($tmp)); -# TODO: this test result is actually not right. the focus reverts to where the mouse pointer is. -cmp_ok(get_focused($tmp), '==', $second_id, 'Focus reverted to second floating container'); +cmd 'kill'; -cmd "kill"; -@content = @{get_ws_content($tmp)}; -cmp_ok($content[0]->{focused}, '==', 1, 'Focus reverted to tiling container'); +sleep 0.25; + +is($x->input_focus, $first->id, 'first con focused after killing all floating cons'); done_testing; From 44c2555e670f7a96038dca62bdb9850daf06dd99 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 13 May 2011 19:53:19 +0200 Subject: [PATCH 621/867] Bugfix: When focusing the next floating window, descend the CT_FLOATING_CON makes t/35-floating-focus.t pass again --- src/con.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/con.c b/src/con.c index 1adfcf35..4bfd5b07 100644 --- a/src/con.c +++ b/src/con.c @@ -642,6 +642,10 @@ Con *con_next_focused(Con *con) { DLOG("Focus list empty, returning ws\n"); next = ws; } + } else { + /* Instead of returning the next CT_FLOATING_CON, we descend it to + * get an actual window to focus. */ + next = con_descend_focused(next); } return next; } From 167bdd26b71789b9b1ad28883fbd2d3998944080 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 13 May 2011 20:41:03 +0200 Subject: [PATCH 622/867] Argument for 'kill' for killing a specific window (now default) or the whole client (+test) Use 'kill window' to kill a specific window (for example only one specific popup), use 'kill client' to kill the whole application (or X11 connection to be specific). --- include/data.h | 4 ++ include/tree.h | 4 +- include/x.h | 2 +- src/cmdparse.l | 4 +- src/cmdparse.y | 17 ++++++-- src/con.c | 2 +- src/floating.c | 4 +- src/handlers.c | 2 +- src/randr.c | 2 +- src/tree.c | 16 +++---- src/workspace.c | 2 +- src/x.c | 11 +++-- testcases/t/64-kill-win-vs-client.t | 66 +++++++++++++++++++++++++++++ 13 files changed, 111 insertions(+), 25 deletions(-) create mode 100644 testcases/t/64-kill-win-vs-client.t diff --git a/include/data.h b/include/data.h index d4836dfe..1165da99 100644 --- a/include/data.h +++ b/include/data.h @@ -43,6 +43,10 @@ typedef enum { D_LEFT, D_RIGHT, D_UP, D_DOWN } direction_t; typedef enum { NO_ORIENTATION = 0, HORIZ, VERT } orientation_t; typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_1PIXEL = 2 } border_style_t; +/** parameter to specify whether tree_close() and x_window_kill() should kill + * only this specific window or the whole X11 client */ +typedef enum { DONT_KILL_WINDOW = 0, KILL_WINDOW = 1, KILL_CLIENT = 2 } kill_window_t; + enum { BIND_NONE = 0, BIND_SHIFT = XCB_MOD_MASK_SHIFT, /* (1 << 0) */ diff --git a/include/tree.h b/include/tree.h index 8ffeca0d..98358edd 100644 --- a/include/tree.h +++ b/include/tree.h @@ -56,7 +56,7 @@ void tree_render(); * Closes the current container using tree_close(). * */ -void tree_close_con(); +void tree_close_con(kill_window_t kill_window); /** * Changes focus in the given way (next/previous) and given orientation @@ -71,7 +71,7 @@ void tree_next(char way, orientation_t orientation); * and the window is expected to kill itself. * */ -bool tree_close(Con *con, bool kill_window, bool dont_kill_parent); +bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent); /** * Loads tree from ~/.i3/_restart.json (used for in-place restarts). diff --git a/include/x.h b/include/x.h index d110d92c..b8ead5ee 100644 --- a/include/x.h +++ b/include/x.h @@ -52,7 +52,7 @@ bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom); * Kills the given X11 window using WM_DELETE_WINDOW (if supported). * */ -void x_window_kill(xcb_window_t window); +void x_window_kill(xcb_window_t window, kill_window_t kill_window); /** * Draws the decoration of the given container onto its parent. diff --git a/src/cmdparse.l b/src/cmdparse.l index 3e5dc889..6e33c950 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) * * cmdparse.l: the lexer for commands you send to i3 (or bind on keys) * @@ -92,6 +92,8 @@ exit { return TOK_EXIT; } reload { return TOK_RELOAD; } restart { return TOK_RESTART; } kill { return TOK_KILL; } +window { return TOK_WINDOW; } +client { return TOK_CLIENT; } fullscreen { return TOK_FULLSCREEN; } global { return TOK_GLOBAL; } layout { return TOK_LAYOUT; } diff --git a/src/cmdparse.y b/src/cmdparse.y index 5dacc64f..73658eb2 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -91,7 +91,7 @@ char *parse_cmd(const char *new) { %} -%expect 4 +%expect 5 %error-verbose %lex-param { struct context *context } @@ -107,6 +107,8 @@ char *parse_cmd(const char *new) { %token TOK_RELOAD "reload" %token TOK_RESTART "restart" %token TOK_KILL "kill" +%token TOK_WINDOW "window" +%token TOK_CLIENT "client" %token TOK_FULLSCREEN "fullscreen" %token TOK_GLOBAL "global" %token TOK_LAYOUT "layout" @@ -161,6 +163,7 @@ char *parse_cmd(const char *new) { %type resize_px %type resize_way %type resize_tiling +%type optional_kill_mode %% @@ -398,24 +401,30 @@ focus: ; kill: - TOK_KILL + TOK_KILL optional_kill_mode { owindow *current; printf("killing!\n"); /* check if the match is empty, not if the result is empty */ if (match_is_empty(¤t_match)) - tree_close_con(); + tree_close_con($2); else { TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); - tree_close(current->con, true, false); + tree_close(current->con, $2, false); } } } ; +optional_kill_mode: + /* empty */ { $$ = KILL_WINDOW; } + | WHITESPACE TOK_WINDOW { $$ = KILL_WINDOW; } + | WHITESPACE TOK_CLIENT { $$ = KILL_CLIENT; } + ; + workspace: TOK_WORKSPACE WHITESPACE STR { diff --git a/src/con.c b/src/con.c index 4bfd5b07..45021f5f 100644 --- a/src/con.c +++ b/src/con.c @@ -885,7 +885,7 @@ static void con_on_remove_child(Con *con) { int children = con_num_children(con); if (children == 0) { DLOG("Container empty, closing\n"); - tree_close(con, false, false); + tree_close(con, DONT_KILL_WINDOW, false); return; } } diff --git a/src/floating.c b/src/floating.c index 2ac4afda..2f3f9bac 100644 --- a/src/floating.c +++ b/src/floating.c @@ -85,7 +85,7 @@ void floating_enable(Con *con, bool automatic) { /* check if the parent container is empty and close it if so */ if ((con->parent->type == CT_CON || con->parent->type == CT_FLOATING_CON) && con_num_children(con->parent) == 0) { DLOG("Old container empty after setting this child to floating, closing\n"); - tree_close(con->parent, false, false); + tree_close(con->parent, DONT_KILL_WINDOW, false); } char *name; @@ -186,7 +186,7 @@ void floating_disable(Con *con, bool automatic) { /* 2: kill parent container */ TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows); TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused); - tree_close(con->parent, false, false); + tree_close(con->parent, DONT_KILL_WINDOW, false); /* 3: re-attach to the parent of the currently focused con on the workspace * this floating con was on */ diff --git a/src/handlers.c b/src/handlers.c index c63f236c..f1e1c4b0 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -466,7 +466,7 @@ static int handle_unmap_notify_event(xcb_unmap_notify_event_t *event) { return 1; } - tree_close(con, false, false); + tree_close(con, DONT_KILL_WINDOW, false); tree_render(); x_push_changes(croot); return 1; diff --git a/src/randr.c b/src/randr.c index 0c43670d..b61e3090 100644 --- a/src/randr.c +++ b/src/randr.c @@ -709,7 +709,7 @@ void randr_query_outputs() { } DLOG("destroying disappearing con %p\n", output->con); - tree_close(output->con, false, true); + tree_close(output->con, DONT_KILL_WINDOW, true); DLOG("Done. Should be fine now\n"); output->con = NULL; } diff --git a/src/tree.c b/src/tree.c index 411e5251..8d55c14b 100644 --- a/src/tree.c +++ b/src/tree.c @@ -99,7 +99,7 @@ static bool _is_con_mapped(Con *con) { * and the window is expected to kill itself. * */ -bool tree_close(Con *con, bool kill_window, bool dont_kill_parent) { +bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent) { bool was_mapped = con->mapped; Con *parent = con->parent; @@ -133,8 +133,8 @@ bool tree_close(Con *con, bool kill_window, bool dont_kill_parent) { } if (con->window != NULL) { - if (kill_window) { - x_window_kill(con->window->id); + if (kill_window != DONT_KILL_WINDOW) { + x_window_kill(con->window->id, kill_window); return false; } else { /* un-parent the window */ @@ -165,7 +165,7 @@ bool tree_close(Con *con, bool kill_window, bool dont_kill_parent) { if (con_is_floating(con)) { Con *ws = con_get_workspace(con); DLOG("Container was floating, killing floating container\n"); - tree_close(parent, false, false); + tree_close(parent, DONT_KILL_WINDOW, false); DLOG("parent container killed\n"); if (con == focused) { DLOG("This is the focused container, i need to find another one to focus. I start looking at ws = %p\n", ws); @@ -192,7 +192,7 @@ bool tree_close(Con *con, bool kill_window, bool dont_kill_parent) { } if (was_mapped || con == focused) { - if (kill_window || !dont_kill_parent || con == focused) { + if ((kill_window != DONT_KILL_WINDOW) || !dont_kill_parent || con == focused) { DLOG("focusing %p / %s\n", next, next->name); /* TODO: check if the container (or one of its children) was focused */ if (next->type == CT_DOCKAREA) { @@ -220,7 +220,7 @@ bool tree_close(Con *con, bool kill_window, bool dont_kill_parent) { * Closes the current container using tree_close(). * */ -void tree_close_con() { +void tree_close_con(kill_window_t kill_window) { assert(focused != NULL); if (focused->type == CT_WORKSPACE) { LOG("Cannot close workspace\n"); @@ -232,7 +232,7 @@ void tree_close_con() { assert(focused->type != CT_ROOT); /* Kill con */ - tree_close(focused, true, false); + tree_close(focused, kill_window, false); } /* @@ -463,7 +463,7 @@ void tree_flatten(Con *con) { /* 4: close the redundant cons */ DLOG("closing redundant cons\n"); - tree_close(con, false, true); + tree_close(con, DONT_KILL_WINDOW, true); /* Well, we got to abort the recursion here because we destroyed the * container. However, if tree_flatten() is called sufficiently often, diff --git a/src/workspace.c b/src/workspace.c index 8104aa89..7ff31157 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -244,7 +244,7 @@ void workspace_show(const char *num) { /* check if this workspace is currently visible */ if (!workspace_is_visible(old)) { LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); - tree_close(old, false, false); + tree_close(old, DONT_KILL_WINDOW, false); ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}"); changed_num_workspaces = true; } diff --git a/src/x.c b/src/x.c index 76add0ff..4fd8a8ba 100644 --- a/src/x.c +++ b/src/x.c @@ -199,11 +199,16 @@ bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom) { * Kills the given X11 window using WM_DELETE_WINDOW (if supported). * */ -void x_window_kill(xcb_window_t window) { +void x_window_kill(xcb_window_t window, kill_window_t kill_window) { /* if this window does not support WM_DELETE_WINDOW, we kill it the hard way */ if (!window_supports_protocol(window, A_WM_DELETE_WINDOW)) { - LOG("Killing window the hard way\n"); - xcb_kill_client(conn, window); + if (kill_window == KILL_WINDOW) { + LOG("Killing specific window 0x%08x\n", window); + xcb_destroy_window(conn, window); + } else { + LOG("Killing the X11 client which owns window 0x%08x\n", window); + xcb_kill_client(conn, window); + } return; } diff --git a/testcases/t/64-kill-win-vs-client.t b/testcases/t/64-kill-win-vs-client.t new file mode 100644 index 00000000..2e0669ba --- /dev/null +++ b/testcases/t/64-kill-win-vs-client.t @@ -0,0 +1,66 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when +# unmapped. +# +use X11::XCB qw(:all); +use X11::XCB::Connection; +use i3test; + +my $x = X11::XCB::Connection->new; + +sub two_windows { + my $tmp = fresh_workspace; + + ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + + my $first = open_standard_window($x); + my $second = open_standard_window($x); + + is($x->input_focus, $second->id, 'second window focused'); + ok(@{get_ws_content($tmp)} == 2, 'two containers opened'); + + return $tmp; +} + +############################################################## +# 1: open two windows (in the same client), kill one and see if +# the other one is still there +############################################################## + +my $tmp = two_windows; + +cmd 'kill'; + +sleep 0.25; + +ok(@{get_ws_content($tmp)} == 1, 'one container left after killing'); + +############################################################## +# 2: same test case as test 1, but with the explicit variant +# 'kill window' +############################################################## + +my $tmp = two_windows; + +cmd 'kill window'; + +sleep 0.25; + +ok(@{get_ws_content($tmp)} == 1, 'one container left after killing'); + +############################################################## +# 3: open two windows (in the same client), use 'kill client' +# and check if both are gone +############################################################## + +my $tmp = two_windows; + +cmd 'kill client'; + +sleep 0.25; + +ok(@{get_ws_content($tmp)} == 0, 'no containers left after killing'); + +done_testing; From 3d2cd6abaa5138320290f45b985cf6fb00df36e8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 13 May 2011 21:18:20 +0200 Subject: [PATCH 623/867] Fix kill command with trailing whitespace --- src/cmdparse.y | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cmdparse.y b/src/cmdparse.y index 73658eb2..47c4fa37 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -421,6 +421,7 @@ kill: optional_kill_mode: /* empty */ { $$ = KILL_WINDOW; } + | WHITESPACE { $$ = KILL_WINDOW; } | WHITESPACE TOK_WINDOW { $$ = KILL_WINDOW; } | WHITESPACE TOK_CLIENT { $$ = KILL_CLIENT; } ; From aea445b690475b84051c9b3247cd8a5046dae4c7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 13 May 2011 21:29:01 +0200 Subject: [PATCH 624/867] Bugfix: Attach new cons at the correct place when a floating con is focused (+test) (Thanks fernandotcl) New containers were previously attached directly to the workspace instead of to the previously focused place in the workspace (for example a stacked con). Fixes: #376 --- src/tree.c | 10 +++++--- testcases/t/38-floating-attach.t | 44 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/tree.c b/src/tree.c index 8d55c14b..336db4fa 100644 --- a/src/tree.c +++ b/src/tree.c @@ -66,9 +66,13 @@ Con *tree_open_con(Con *con) { } /* If the currently focused container is a floating container, we - * attach the new container to the workspace */ - if (con->type == CT_FLOATING_CON) - con = con->parent; + * attach the new container to the currently focused spot in its + * workspace. */ + if (con->type == CT_FLOATING_CON) { + con = con_descend_tiling_focused(con->parent); + if (con->type != CT_WORKSPACE) + con = con->parent; + } DLOG("con = %p\n", con); } diff --git a/testcases/t/38-floating-attach.t b/testcases/t/38-floating-attach.t index a1cf4d63..9e5d7829 100644 --- a/testcases/t/38-floating-attach.t +++ b/testcases/t/38-floating-attach.t @@ -61,4 +61,48 @@ sleep 0.25; is(@{$nodes}, 1, 'one tiling node'); +############################################################################# +# 2: similar case: floating windows should be attached at the currently focused +# position in the workspace (for example a stack), not just at workspace level. +############################################################################# + +$tmp = fresh_workspace; + +my $first = open_standard_window($x); +my $second = open_standard_window($x); + +cmd 'layout stacked'; + +$ws = get_ws($tmp); +is(@{$ws->{floating_nodes}}, 0, 'no floating nodes so far'); +is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)'); + +# Create a floating window +my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#C0C0C0', + # replace the type with 'utility' as soon as the coercion works again in X11::XCB + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), +); + +isa_ok($window, 'X11::XCB::Window'); + +$window->map; + +sleep 0.25; + +ok($window->mapped, 'Window is mapped'); + +$ws = get_ws($tmp); +is(@{$ws->{floating_nodes}}, 1, 'one floating nodes'); +is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)'); + +my $third = open_standard_window($x); + + +$ws = get_ws($tmp); +is(@{$ws->{floating_nodes}}, 1, 'one floating nodes'); +is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)'); + done_testing; From 0bfab98a7f386b70676b31f610e287e382580f78 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 13 May 2011 21:57:45 +0200 Subject: [PATCH 625/867] Correctly re-implement scrolling on window decorations Got lost when refactoring the click handling in 24463718cc2ae6d44d75104bb8ade636985ff86c Fixes: #390 --- src/click.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/click.c b/src/click.c index 2092b6e7..74e59885 100644 --- a/src/click.c +++ b/src/click.c @@ -160,23 +160,35 @@ static int route_click(Con *con, xcb_button_press_event_t *event, bool mod_press /* get the floating con */ Con *floatingcon = con_inside_floating(con); const bool proportional = (event->state & BIND_SHIFT); - - /* 1: focus this con */ - con_focus(con); - const bool in_stacked = (con->parent->layout == L_STACKED || con->parent->layout == L_TABBED); - /* 2: for floating containers, we also want to raise them on click */ + /* 1: see if the user scrolled on the decoration of a stacked/tabbed con */ + if (in_stacked && + dest == CLICK_DECORATION && + (event->detail == XCB_BUTTON_INDEX_4 || + event->detail == XCB_BUTTON_INDEX_5)) { + DLOG("Scrolling on a window decoration\n"); + orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ); + if (event->detail == XCB_BUTTON_INDEX_4) + tree_next('p', orientation); + else tree_next('n', orientation); + goto done; + } + + /* 2: focus this con */ + con_focus(con); + + /* 3: for floating containers, we also want to raise them on click */ if (floatingcon != NULL) { floating_raise_con(floatingcon); - /* 3: floating_modifier plus left mouse button drags */ + /* 4: floating_modifier plus left mouse button drags */ if (mod_pressed && event->detail == 1) { floating_drag_window(floatingcon, event); return 1; } - /* 3: resize (floating) if this was a click on the left/right/bottom + /* 5: resize (floating) if this was a click on the left/right/bottom * border. also try resizing (tiling) if it was a click on the top * border, but continue if that does not work */ if (mod_pressed && event->detail == 3) { @@ -198,7 +210,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, bool mod_press return 1; } - /* 4: dragging, if this was a click on a decoration (which did not lead + /* 6: dragging, if this was a click on a decoration (which did not lead * to a resize) */ if (!in_stacked && dest == CLICK_DECORATION) { floating_drag_window(floatingcon, event); @@ -217,12 +229,12 @@ static int route_click(Con *con, xcb_button_press_event_t *event, bool mod_press goto done; } - /* 3: floating modifier pressed, initiate a resize */ + /* 7: floating modifier pressed, initiate a resize */ if (mod_pressed && event->detail == 3) { if (floating_mod_on_tiled_client(con, event)) return 1; } - /* 4: otherwise, check for border/decoration clicks and resize */ + /* 8: otherwise, check for border/decoration clicks and resize */ else if (dest == CLICK_BORDER || dest == CLICK_DECORATION) { DLOG("Should trry resizing (tiling)\n"); tiling_resize(con, event, dest); From 443753bea6ae30238a3c1af29ec9c60824dca7a4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 14 May 2011 20:04:34 +0200 Subject: [PATCH 626/867] x.c: disable some of the debug output --- src/x.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/x.c b/src/x.c index 4fd8a8ba..cfbf5a3d 100644 --- a/src/x.c +++ b/src/x.c @@ -461,7 +461,7 @@ void x_push_node(Con *con) { con_state *state; Rect rect = con->rect; - DLOG("Pushing changes for node %p / %s\n", con, con->name); + //DLOG("Pushing changes for node %p / %s\n", con, con->name); state = state_for_frame(con->frame); if (state->name != NULL) { @@ -634,7 +634,7 @@ static void x_push_node_unmaps(Con *con) { Con *current; con_state *state; - DLOG("Pushing changes (with unmaps) for node %p / %s\n", con, con->name); + //DLOG("Pushing changes (with unmaps) for node %p / %s\n", con, con->name); state = state_for_frame(con->frame); /* map/unmap if map state changed, also ensure that the child window @@ -687,16 +687,16 @@ void x_push_changes(Con *con) { con_state *state; DLOG("-- PUSHING WINDOW STACK --\n"); - DLOG("Disabling EnterNotify\n"); + //DLOG("Disabling EnterNotify\n"); uint32_t values[1] = { XCB_NONE }; CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); } - DLOG("Done, EnterNotify disabled\n"); + //DLOG("Done, EnterNotify disabled\n"); bool order_changed = false; /* X11 correctly represents the stack if we push it from bottom to top */ CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { - DLOG("stack: 0x%08x\n", state->id); + //DLOG("stack: 0x%08x\n", state->id); con_state *prev = CIRCLEQ_PREV(state, state); con_state *old_prev = CIRCLEQ_PREV(state, old_state); if (prev != old_prev) @@ -712,12 +712,12 @@ void x_push_changes(Con *con) { } state->initial = false; } - DLOG("Re-enabling EnterNotify\n"); + //DLOG("Re-enabling EnterNotify\n"); values[0] = FRAME_EVENT_MASK; CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); } - DLOG("Done, EnterNotify re-enabled\n"); + //DLOG("Done, EnterNotify re-enabled\n"); DLOG("\n\n PUSHING CHANGES\n\n"); x_push_node(con); @@ -774,9 +774,9 @@ void x_push_changes(Con *con) { CIRCLEQ_REMOVE(&old_state_head, state, old_state); CIRCLEQ_INSERT_TAIL(&old_state_head, state, old_state); } - CIRCLEQ_FOREACH(state, &old_state_head, old_state) { - DLOG("old stack: 0x%08x\n", state->id); - } + //CIRCLEQ_FOREACH(state, &old_state_head, old_state) { + // DLOG("old stack: 0x%08x\n", state->id); + //} xcb_flush(conn); } @@ -789,7 +789,7 @@ void x_push_changes(Con *con) { void x_raise_con(Con *con) { con_state *state; state = state_for_frame(con->frame); - DLOG("raising in new stack: %p / %s / %s / xid %08x\n", con, con->name, con->window ? con->window->name_json : "", state->id); + //DLOG("raising in new stack: %p / %s / %s / xid %08x\n", con, con->name, con->window ? con->window->name_json : "", state->id); CIRCLEQ_REMOVE(&state_head, state, state); CIRCLEQ_INSERT_HEAD(&state_head, state, state); From 0e2d58347c0290cb4dbed92e3b47dbabd0005318 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 14 May 2011 22:11:09 +0200 Subject: [PATCH 627/867] introduce the NODES_FOREACH and GREP_FIRST macros --- include/util.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/include/util.h b/include/util.h index 610e701e..df0e3065 100644 --- a/include/util.h +++ b/include/util.h @@ -25,6 +25,21 @@ #define FOR_TABLE(workspace) \ for (int cols = 0; cols < (workspace)->cols; cols++) \ for (int rows = 0; rows < (workspace)->rows; rows++) + +#define NODES_FOREACH(head) \ + for (Con *child = (Con*)-1; (child == (Con*)-1) && ((child = 0), true);) \ + TAILQ_FOREACH(child, &((head)->nodes_head), nodes) + +/* greps the ->nodes of the given head and returns the first node that matches the given condition */ +#define GREP_FIRST(dest, head, condition) \ + NODES_FOREACH(head) { \ + if (!(condition)) \ + continue; \ + \ + (dest) = child; \ + break; \ + } + #define FREE(pointer) do { \ if (pointer != NULL) { \ free(pointer); \ From 3f45d3c44785c552b28f079df1888de52cd34a19 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 14 May 2011 22:13:29 +0200 Subject: [PATCH 628/867] re-implement assignments of workspace to specific outputs --- include/data.h | 11 ++++++ include/i3.h | 1 + src/cfgparse.y | 18 ++++++---- src/main.c | 4 +++ src/randr.c | 92 +++++++++++++++++++++++++++++++++++++++++++------ src/workspace.c | 26 +++++++------- 6 files changed, 122 insertions(+), 30 deletions(-) diff --git a/include/data.h b/include/data.h index 1165da99..c408d3f6 100644 --- a/include/data.h +++ b/include/data.h @@ -117,6 +117,17 @@ struct deco_render_params { xcb_font_t font; }; +/** + * Stores which workspace (by name) goes to which output. + * + */ +struct Workspace_Assignment { + char *name; + char *output; + + TAILQ_ENTRY(Workspace_Assignment) ws_assignments; +}; + struct Ignore_Event { int sequence; int response_type; diff --git a/include/i3.h b/include/i3.h index 2f18ce70..a18cd6bc 100644 --- a/include/i3.h +++ b/include/i3.h @@ -27,6 +27,7 @@ extern int xkb_current_group; extern TAILQ_HEAD(bindings_head, Binding) *bindings; extern TAILQ_HEAD(autostarts_head, Autostart) autostarts; extern TAILQ_HEAD(assignments_head, Match) assignments; +extern TAILQ_HEAD(ws_assignments_head, Workspace_Assignment) ws_assignments; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; extern uint8_t root_depth; extern bool xcursor_supported, xkb_supported; diff --git a/src/cfgparse.y b/src/cfgparse.y index 61b67687..a5e0f0ba 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -517,14 +517,18 @@ workspace: if (ws_num < 1) { DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { -#if 0 - Workspace *ws = workspace_get(ws_num - 1); - ws->preferred_output = $7; - if ($8 != NULL) { - workspace_set_name(ws, $8); - free($8); + char *ws_name = NULL; + if ($8 == NULL) { + asprintf(&ws_name, "%d", ws_num); + } else { + ws_name = $8; } -#endif + + DLOG("Should assign workspace %s to output %s\n", ws_name, $7); + struct Workspace_Assignment *assignment = scalloc(sizeof(struct Workspace_Assignment)); + assignment->name = ws_name; + assignment->output = $7; + TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments); } } | TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name diff --git a/src/main.c b/src/main.c index 91bd5d68..c079d8dc 100644 --- a/src/main.c +++ b/src/main.c @@ -33,6 +33,10 @@ struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts); /* The list of assignments */ struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments); +/* The list of workspace assignments (which workspace should end up on which + * output) */ +struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignments); + /* We hope that those are supported and set them to true */ bool xcursor_supported = true; bool xkb_supported = true; diff --git a/src/randr.c b/src/randr.c index b61e3090..af0af334 100644 --- a/src/randr.c +++ b/src/randr.c @@ -223,9 +223,6 @@ void disable_randr(xcb_connection_t *conn) { * Initializes a CT_OUTPUT Con (searches existing ones from inplace restart * before) to use for the given Output. * - * XXX: for assignments, we probably need to move workspace creation from here - * to after the loop in randr_query_outputs(). - * */ void output_init_con(Output *output) { Con *con = NULL, *current; @@ -316,7 +313,81 @@ void output_init_con(Output *output) { FREE(name); DLOG("attaching\n"); con_attach(bottomdock, con, false); +} +/* + * Initializes at least one workspace for this output, trying the following + * steps until there is at least one workspace: + * + * • Move existing workspaces, which are assigned to be on the given output, to + * the output. + * • Create the first assigned workspace for this output. + * • Create the first unused workspace. + * + */ +static void init_ws_for_output(Output *output, Con *content) { + char *name; + + /* go through all assignments and move the existing workspaces to this output */ + struct Workspace_Assignment *assignment; + TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { + if (strcmp(assignment->output, output->name) != 0) + continue; + + /* check if this workspace actually exists */ + Con *workspace = NULL, *out; + TAILQ_FOREACH(out, &(croot->nodes_head), nodes) + GREP_FIRST(workspace, output_get_content(out), + !strcasecmp(child->name, assignment->name)); + if (workspace == NULL) + continue; + + /* check that this workspace is not already attached (that means the + * user configured this assignment twice) */ + Con *workspace_out = con_get_output(workspace); + if (workspace_out == output->con) { + LOG("Workspace \"%s\" assigned to output \"%s\", but it is already " + "there. Do you have two assignment directives for the same " + "workspace in your configuration file?\n", + workspace->name, output->name); + continue; + } + + /* if so, move it over */ + LOG("Moving workspace \"%s\" from output \"%s\" to \"%s\" due to assignment\n", + workspace->name, workspace_out->name, output->name); + DLOG("Detaching workspace = %p / %s\n", workspace, workspace->name); + con_detach(workspace); + DLOG("Re-attaching current = %p / %s\n", workspace, workspace->name); + con_attach(workspace, content, false); + DLOG("Done, next\n"); + } + + /* if a workspace exists, we are done now */ + if (!TAILQ_EMPTY(&(content->nodes_head))) { + /* ensure that one of the workspaces is actually visible (in fullscreen + * mode), if they were invisible before, this might not be the case. */ + Con *visible = NULL; + GREP_FIRST(visible, content, child->fullscreen_mode == CF_OUTPUT); + if (!visible) { + visible = TAILQ_FIRST(&(content->nodes_head)); + workspace_show(visible->name); + } + return; + } + + /* otherwise, we create the first assigned ws for this output */ + TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { + if (strcmp(assignment->output, output->name) != 0) + continue; + + LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n", + assignment->name, assignment->output); + workspace_show(assignment->name); + return; + } + + /* if there is still no workspace, we create the first free workspace */ DLOG("Now adding a workspace\n"); /* add a workspace to this output */ @@ -731,15 +802,16 @@ void randr_query_outputs() { ewmh_update_workarea(); -#if 0 - /* Just go through each active output and associate one workspace */ + /* Just go through each active output and assign one workspace */ TAILQ_FOREACH(output, &outputs, outputs) { - if (!output->active || output->current_workspace != NULL) - continue; - ws = get_first_workspace_for_output(output); - initialize_output(conn, output, ws); + if (!output->active) + continue; + Con *content = output_get_content(output->con); + if (!TAILQ_EMPTY(&(content->nodes_head))) + continue; + DLOG("Should add ws for output %s\n", output->name); + init_ws_for_output(output, content); } -#endif /* Focus the primary screen, if possible */ TAILQ_FOREACH(output, &outputs, outputs) { diff --git a/src/workspace.c b/src/workspace.c index 7ff31157..5127fb00 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -18,24 +18,25 @@ * */ Con *workspace_get(const char *num, bool *created) { - Con *output, *workspace = NULL, *child; + Con *output, *workspace = NULL; - /* TODO: could that look like this in the future? - GET_MATCHING_NODE(workspace, croot, strcasecmp(current->name, num) != 0); - */ TAILQ_FOREACH(output, &(croot->nodes_head), nodes) - TAILQ_FOREACH(child, &(output_get_content(output)->nodes_head), nodes) { - if (strcasecmp(child->name, num) != 0) + GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num)); + + if (workspace == NULL) { + LOG("Creating new workspace \"%s\"\n", num); + /* unless an assignment is found, we will create this workspace on the current output */ + output = con_get_output(focused); + /* look for assignments */ + struct Workspace_Assignment *assignment; + TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { + if (strcmp(assignment->name, num) != 0) continue; - workspace = child; + LOG("Found workspace assignment to output \"%s\"\n", assignment->output); + GREP_FIRST(output, croot, !strcmp(child->name, assignment->output)); break; } - - LOG("getting ws %s\n", num); - if (workspace == NULL) { - LOG("need to create this one\n"); - output = con_get_output(focused); Con *content = output_get_content(output); LOG("got output %p with content %p\n", output, content); /* We need to attach this container after setting its type. con_attach @@ -225,7 +226,6 @@ void workspace_show(const char *num) { old = current; current->fullscreen_mode = CF_NONE; } - assert(old != NULL); /* enable fullscreen for the target workspace. If it happens to be the * same one we are currently on anyways, we can stop here. */ From 5db97dc47368029d82827239891cf4f414eb5748 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 14 May 2011 22:34:34 +0200 Subject: [PATCH 629/867] Bugfix: Fix initialization / assignments when RandR is missing --- include/randr.h | 15 ++++++++++++--- src/randr.c | 5 ++++- src/xinerama.c | 1 + 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/include/randr.h b/include/randr.h index 779a1316..d14d0478 100644 --- a/include/randr.h +++ b/include/randr.h @@ -35,12 +35,21 @@ void disable_randr(xcb_connection_t *conn); * Initializes a CT_OUTPUT Con (searches existing ones from inplace restart * before) to use for the given Output. * - * XXX: for assignments, we probably need to move workspace creation from here - * to after the loop in randr_query_outputs(). - * */ void output_init_con(Output *output); +/** + * Initializes at least one workspace for this output, trying the following + * steps until there is at least one workspace: + * + * • Move existing workspaces, which are assigned to be on the given output, to + * the output. + * • Create the first assigned workspace for this output. + * • Create the first unused workspace. + * + */ +void init_ws_for_output(Output *output, Con *content); + /** * Initializes the specified output, assigning the specified workspace to it. * diff --git a/src/randr.c b/src/randr.c index af0af334..56254299 100644 --- a/src/randr.c +++ b/src/randr.c @@ -213,6 +213,7 @@ void disable_randr(xcb_connection_t *conn) { s->rect.height = root_screen->height_in_pixels; s->name = "xroot-0"; output_init_con(s); + init_ws_for_output(s, output_get_content(s->con)); TAILQ_INSERT_TAIL(&outputs, s, outputs); @@ -325,7 +326,7 @@ void output_init_con(Output *output) { * • Create the first unused workspace. * */ -static void init_ws_for_output(Output *output, Con *content) { +void init_ws_for_output(Output *output, Con *content) { char *name; /* go through all assignments and move the existing workspaces to this output */ @@ -371,6 +372,7 @@ static void init_ws_for_output(Output *output, Con *content) { GREP_FIRST(visible, content, child->fullscreen_mode == CF_OUTPUT); if (!visible) { visible = TAILQ_FIRST(&(content->nodes_head)); + focused = content; workspace_show(visible->name); } return; @@ -383,6 +385,7 @@ static void init_ws_for_output(Output *output, Con *content) { LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n", assignment->name, assignment->output); + focused = content; workspace_show(assignment->name); return; } diff --git a/src/xinerama.c b/src/xinerama.c index 8bb1b43f..c116deaa 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -71,6 +71,7 @@ static void query_screens(xcb_connection_t *conn) { TAILQ_INSERT_HEAD(&outputs, s, outputs); else TAILQ_INSERT_TAIL(&outputs, s, outputs); output_init_con(s); + init_ws_for_output(s, output_get_content(s->con)); num_screens++; } From d3e458bc7892280798eafb281a5be7106babc14c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 14 May 2011 22:38:19 +0200 Subject: [PATCH 630/867] Remove old code from randr.c and workspace.c --- src/randr.c | 95 ------------------- src/workspace.c | 248 ------------------------------------------------ 2 files changed, 343 deletions(-) diff --git a/src/randr.c b/src/randr.c index 56254299..a6b84ddc 100644 --- a/src/randr.c +++ b/src/randr.c @@ -143,57 +143,6 @@ Output *get_output_most(direction_t direction, Output *current) { return candidate; } -#if 0 -/* - * Initializes the specified output, assigning the specified workspace to it. - * - */ -void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace) { - i3Font *font = load_font(conn, config.font); - - workspace->output = output; - output->current_workspace = workspace; - - /* Copy rect for the workspace */ - memcpy(&(workspace->rect), &(output->rect), sizeof(Rect)); - - /* Map clients on the workspace, if any */ - workspace_map_clients(conn, workspace); - - /* Create a bar window on each output */ - if (!config.disable_workspace_bar) { - Rect bar_rect = {output->rect.x, - output->rect.y + output->rect.height - (font->height + 6), - output->rect.x + output->rect.width, - font->height + 6}; - uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; - uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS}; - output->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values); - output->bargc = xcb_generate_id(conn); - xcb_create_gc(conn, output->bargc, output->bar, 0, 0); - } - - SLIST_INIT(&(output->dock_clients)); - - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); - DLOG("initialized output at (%d, %d) with %d x %d\n", - output->rect.x, output->rect.y, output->rect.width, output->rect.height); - - DLOG("assigning configured workspaces to this output...\n"); - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws == workspace) - continue; - if (ws->preferred_output == NULL || - get_output_by_name(ws->preferred_output) != output) - continue; - - DLOG("assigning ws %d\n", ws->num + 1); - workspace_assign_to(ws, output, true); - } -} -#endif - /* * Disables RandR support by creating exactly one output with the size of the * X11 screen. @@ -492,50 +441,6 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { } } } - -#if 0 - Rect bar_rect = {output->rect.x, - output->rect.y + output->rect.height - (font->height + 6), - output->rect.x + output->rect.width, - font->height + 6}; - - xcb_set_window_rect(conn, output->bar, bar_rect); - - /* go through all workspaces and set force_reconfigure */ - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->output != output) - continue; - - SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) { - client->force_reconfigure = true; - if (!client_is_floating(client)) - continue; - /* For floating clients we need to translate the - * coordinates (old workspace to new workspace) */ - DLOG("old: (%x, %x)\n", client->rect.x, client->rect.y); - client->rect.x -= ws->rect.x; - client->rect.y -= ws->rect.y; - client->rect.x += ws->output->rect.x; - client->rect.y += ws->output->rect.y; - DLOG("new: (%x, %x)\n", client->rect.x, client->rect.y); - } - - /* Update dimensions from output */ - memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect)); - - /* Update the dimensions of a fullscreen client, if any */ - if (ws->fullscreen_client != NULL) { - DLOG("Updating fullscreen client size\n"); - client = ws->fullscreen_client; - Rect r = ws->rect; - xcb_set_window_rect(conn, client->frame, r); - - r.x = 0; - r.y = 0; - xcb_set_window_rect(conn, client->child, r); - } - } -#endif } /* diff --git a/src/workspace.c b/src/workspace.c index 5127fb00..385921a4 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -85,37 +85,6 @@ Con *workspace_get(const char *num, bool *created) { return workspace; } -#if 0 - -/* - * Sets the name (or just its number) for the given workspace. This has to - * be called for every workspace as the rendering function - * (render_internal_bar) relies on workspace->name and workspace->name_len - * being ready-to-use. - * - */ -void workspace_set_name(Workspace *ws, const char *name) { - char *label; - int ret; - - if (name != NULL) - ret = asprintf(&label, "%d: %s", ws->num + 1, name); - else ret = asprintf(&label, "%d", ws->num + 1); - - if (ret == -1) - errx(1, "asprintf() failed"); - - FREE(ws->name); - FREE(ws->utf8_name); - - ws->name = convert_utf8_to_ucs2(label, &(ws->name_len)); - if (config.font != NULL) - ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len); - else ws->text_width = 0; - ws->utf8_name = label; -} -#endif - /* * Returns true if the workspace is currently visible. Especially important for * multi-monitor environments, as they can have multiple currenlty active @@ -260,225 +229,8 @@ void workspace_show(const char *num) { ewmh_update_current_desktop(); ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); -#if 0 - - /* Check if the workspace has not been used yet */ - workspace_initialize(t_ws, c_ws->output, false); - - if (c_ws->output != t_ws->output) { - /* We need to switch to the other output first */ - DLOG("moving over to other output.\n"); - - /* Store the old client */ - Client *old_client = CUR_CELL->currently_focused; - - c_ws = t_ws->output->current_workspace; - current_col = c_ws->current_col; - current_row = c_ws->current_row; - if (CUR_CELL->currently_focused != NULL) - need_warp = true; - else { - Rect *dims = &(c_ws->output->rect); - xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, - dims->x + (dims->width / 2), dims->y + (dims->height / 2)); - } - - /* Re-decorate the old client, it’s not focused anymore */ - if ((old_client != NULL) && !old_client->dock) - redecorate_window(conn, old_client); - else xcb_flush(conn); - - /* We need to check if a global fullscreen-client is blocking - * the t_ws and if necessary switch that to local fullscreen */ - Client* client = c_ws->fullscreen_client; - if (client != NULL && client->workspace != c_ws) { - if (c_ws->fullscreen_client->workspace != c_ws) - c_ws->fullscreen_client = NULL; - client_enter_fullscreen(conn, client, false); - } - } - - /* Check if we need to change something or if we’re already there */ - if (c_ws->output->current_workspace->num == (workspace-1)) { - Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); - if (last_focused != SLIST_END(&(c_ws->focus_stack))) - set_focus(conn, last_focused, true); - if (need_warp) { - client_warp_pointer_into(conn, last_focused); - xcb_flush(conn); - } - - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); - - return; - } - - Workspace *old_workspace = c_ws; - c_ws = t_ws->output->current_workspace = workspace_get(workspace-1); - - /* Unmap all clients of the old workspace */ - workspace_unmap_clients(conn, old_workspace); - - current_row = c_ws->current_row; - current_col = c_ws->current_col; - DLOG("new current row = %d, current col = %d\n", current_row, current_col); - - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); - - workspace_map_clients(conn, c_ws); - - /* POTENTIAL TO IMPROVE HERE: due to the call to _map_clients first and - * render_layout afterwards, there is a short flickering on the source - * workspace (assign ws 3 to output 0, ws 4 to output 1, create single - * client on ws 4, move it to ws 3, switch to ws 3, you’ll see the - * flickering). */ - - /* Restore focus on the new workspace */ - Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); - if (last_focused != SLIST_END(&(c_ws->focus_stack))) - set_focus(conn, last_focused, true); - else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME); - - render_layout(conn); - - /* We can warp the pointer only after the window has been - * reconfigured in render_layout, otherwise the pointer will - * be warped to the old position, which will not work when we - * moved it to another output. */ - if (last_focused != SLIST_END(&(c_ws->focus_stack)) && need_warp) { - client_warp_pointer_into(conn, last_focused); - xcb_flush(conn); - } -#endif } -#if 0 -/* - * Assigns the given workspace to the given output by correctly updating its - * state and reconfiguring all the clients on this workspace. - * - * This is called when initializing a output and when re-assigning it to a - * different output which just got available (if you configured it to be on - * output 1 and you just plugged in output 1). - * - */ -void workspace_assign_to(Workspace *ws, Output *output, bool hide_it) { - Client *client; - bool empty = true; - bool visible = workspace_is_visible(ws); - - ws->output = output; - - /* Copy the dimensions from the virtual output */ - memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect)); - - ewmh_update_workarea(); - - /* Force reconfiguration for each client on that workspace */ - SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) { - client->force_reconfigure = true; - empty = false; - } - - if (empty) - return; - - /* Render the workspace to reconfigure the clients. However, they will be visible now, so… */ - render_workspace(global_conn, output, ws); - - /* …unless we want to see them at the moment, we should hide that workspace */ - if (visible && !hide_it) - return; - - /* however, if this is the current workspace, we only need to adjust - * the output’s current_workspace pointer (and must not unmap the - * windows) */ - if (c_ws == ws) { - DLOG("Need to adjust output->current_workspace...\n"); - output->current_workspace = c_ws; - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); - return; - } - - workspace_unmap_clients(global_conn, ws); -} - -/* - * Initializes the given workspace if it is not already initialized. The given - * screen is to be understood as a fallback, if the workspace itself either - * was not assigned to a particular screen or cannot be placed there because - * the screen is not attached at the moment. - * - */ -void workspace_initialize(Workspace *ws, Output *output, bool recheck) { - Output *old_output; - - if (ws->output != NULL && !recheck) { - DLOG("Workspace already initialized\n"); - return; - } - - old_output = ws->output; - - /* If this workspace has no preferred output or if the output it wants - * to be on is not available at the moment, we initialize it with - * the output which was given */ - if (ws->preferred_output == NULL || - (ws->output = get_output_by_name(ws->preferred_output)) == NULL) - ws->output = output; - - DLOG("old_output = %p, ws->output = %p\n", old_output, ws->output); - /* If the assignment did not change, we do not need to update anything */ - if (old_output != NULL && ws->output == old_output) - return; - - workspace_assign_to(ws, ws->output, false); -} - -/* - * Gets the first unused workspace for the given screen, taking into account - * the preferred_output setting of every workspace (workspace assignments). - * - */ -Workspace *get_first_workspace_for_output(Output *output) { - Workspace *result = NULL; - - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->preferred_output == NULL || - get_output_by_name(ws->preferred_output) != output) - continue; - - result = ws; - break; - } - - if (result == NULL) { - /* No assignment found, returning first unused workspace */ - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->output != NULL) - continue; - - result = ws; - break; - } - } - - if (result == NULL) { - DLOG("No existing free workspace found to assign, creating a new one\n"); - - int last_ws = 0; - TAILQ_FOREACH(ws, workspaces, workspaces) - last_ws = ws->num; - result = workspace_get(last_ws + 1, NULL); - } - - workspace_initialize(result, output, false); - return result; -} - -#endif - static bool get_urgency_flag(Con *con) { Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) From ca2e4199b5f0c9f3ba6488105e3dc59bd9c2d71e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 15 May 2011 19:43:35 +0200 Subject: [PATCH 631/867] Introduce HANDLE_EMPTY_MATCH macro to simplify command handlers in cmdparse.y --- src/cmdparse.y | 82 ++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index 47c4fa37..eb41c820 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -17,6 +17,20 @@ #include "all.h" +/** When the command did not include match criteria (!), we use the currently + * focused command. Do not confuse this case with a command which included + * criteria but which did not match any windows. This macro has to be called in + * every command. + */ +#define HANDLE_EMPTY_MATCH do { \ + if (match_is_empty(¤t_match)) { \ + owindow *ow = smalloc(sizeof(owindow)); \ + ow->con = focused; \ + TAILQ_INIT(&owindows); \ + TAILQ_INSERT_TAIL(&owindows, ow, owindows); \ + } \ +} while (0) + typedef struct yy_buffer_state *YY_BUFFER_STATE; extern int cmdyylex(struct context *context); extern int cmdyyparse(void); @@ -451,16 +465,13 @@ fullscreen: printf("toggling fullscreen\n"); owindow *current; - /* check if the match is empty, not if the result is empty */ - if (match_is_empty(¤t_match)) - con_toggle_fullscreen(focused); - else { - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - con_toggle_fullscreen(current->con); - } - } + HANDLE_EMPTY_MATCH; + + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + con_toggle_fullscreen(current->con); + } } ; @@ -527,14 +538,11 @@ border: printf("border style should be changed to %d\n", $3); owindow *current; - /* check if the match is empty, not if the result is empty */ - if (match_is_empty(¤t_match)) - focused->border_style = $3; - else { - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - current->con->border_style = $3; - } + HANDLE_EMPTY_MATCH; + + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + current->con->border_style = $3; } } ; @@ -576,14 +584,11 @@ move: Con *ws = workspace_get($5, NULL); free($5); - /* check if the match is empty, not if the result is empty */ - if (match_is_empty(¤t_match)) - con_move_to_workspace(focused, ws); - else { - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, ws); - } + HANDLE_EMPTY_MATCH; + + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + con_move_to_workspace(current->con, ws); } } ; @@ -603,16 +608,12 @@ layout: printf("changing layout to %d\n", $3); owindow *current; - /* check if the match is empty, not if the result is empty */ - if (match_is_empty(¤t_match)) - con_set_layout(focused->parent, $3); - else { - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - con_set_layout(current->con, $3); - } - } + HANDLE_EMPTY_MATCH; + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + con_set_layout(current->con, $3); + } } ; @@ -628,14 +629,11 @@ mark: printf("marking window with str %s\n", $3); owindow *current; - /* check if the match is empty, not if the result is empty */ - if (match_is_empty(¤t_match)) - focused->mark = sstrdup($3); - else { - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - current->con->mark = sstrdup($3); - } + HANDLE_EMPTY_MATCH; + + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + current->con->mark = sstrdup($3); } free($3); From 5ae4620a2484a390dd63642ba66fbb4ca09cbd21 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 15 May 2011 20:10:25 +0200 Subject: [PATCH 632/867] Time Lord technology: for_window config directive to run arbitrary cmds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An example to set all XTerms floating: for_window [class="XTerm"] mode floating To make all urxvts use a 1-pixel border: for_window [class="urxvt"] border 1pixel A less useful, but rather funny example: for_window [title="x200: ~/work"] mode floating The commands are not completely arbitrary. The commands above were tested, others may need some fixing. Internally, windows are compared against your criteria (class, title, …) when they are initially managed and whenever one of the relevant values change. Then, the specified command is run *once* (per window). It gets prefixed with a criteria to make it match only the specific window that triggered it. So, if you configure "mode floating", i3 runs something like '[id="8393923"] mode floating'. --- Makefile | 2 +- include/all.h | 1 + include/assignments.h | 15 +++++++ include/data.h | 35 ++++++++++++--- include/i3.h | 1 + include/window.h | 6 +-- src/assignments.c | 50 ++++++++++++++++++++++ src/cfgparse.l | 32 ++++++++++++++ src/cfgparse.y | 99 +++++++++++++++++++++++++++++++++++++++++++ src/cmdparse.y | 38 +++++++++++------ src/handlers.c | 6 +-- src/main.c | 2 + src/manage.c | 9 ++-- src/match.c | 6 +++ src/window.c | 23 ++++++++-- 15 files changed, 291 insertions(+), 34 deletions(-) create mode 100644 include/assignments.h create mode 100644 src/assignments.c diff --git a/Makefile b/Makefile index c9359142..6e032f41 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c -FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c src/move.c src/output.c src/ewmh.c +FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c src/move.c src/output.c src/ewmh.c src/assignments.c FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) diff --git a/include/all.h b/include/all.h index f9f12a70..ba582a80 100644 --- a/include/all.h +++ b/include/all.h @@ -62,5 +62,6 @@ #include "move.h" #include "output.h" #include "ewmh.h" +#include "assignments.h" #endif diff --git a/include/assignments.h b/include/assignments.h new file mode 100644 index 00000000..ecd8b808 --- /dev/null +++ b/include/assignments.h @@ -0,0 +1,15 @@ +/* + * vim:ts=4:sw=4:expandtab + * + */ +#ifndef _ASSIGNMENTS_H +#define _ASSIGNMENTS_H + +/** + * Checks the list of assignments for the given window and runs all matching + * ones (unless they have already been run for this specific window). + * + */ +void run_assignments(i3Window *window); + +#endif diff --git a/include/data.h b/include/data.h index c408d3f6..832200ee 100644 --- a/include/data.h +++ b/include/data.h @@ -33,6 +33,7 @@ typedef struct Rect Rect; typedef struct xoutput Output; typedef struct Con Con; typedef struct Match Match; +typedef struct Assignment Assignment; typedef struct Window i3Window; @@ -274,11 +275,14 @@ struct Window { /** Pixels the window reserves. left/right/top/bottom */ struct reservedpx reserved; + + /** Pointers to the Assignments which were already ran for this Window + * (assignments run only once) */ + uint32_t nr_assignments; + Assignment **ran_assignments; }; struct Match { - enum { M_WINDOW, M_CON } what; - char *title; int title_len; char *application; @@ -296,10 +300,6 @@ struct Match { Con *con_id; enum { M_ANY = 0, M_TILING, M_FLOATING } floating; - enum { M_GLOBAL = 0, M_OUTPUT, M_WORKSPACE } levels; - - enum { M_USER = 0, M_RESTART } source; - char *target_ws; /* Where the window looking for a match should be inserted: @@ -317,6 +317,29 @@ struct Match { TAILQ_ENTRY(Match) assignments; }; +struct Assignment { + /** type of this assignment: + * + * A_COMMAND = run the specified command for the matching window + * A_TO_WORKSPACE = assign the matching window to the specified workspace + * A_TO_OUTPUT = assign the matching window to the specified output + * + */ + enum { A_COMMAND = 0, A_TO_WORKSPACE = 1, A_TO_OUTPUT = 2 } type; + + /** the criteria to check if a window matches */ + Match match; + + /** destination workspace/output/command, depending on the type */ + union { + char *command; + char *workspace; + char *output; + } dest; + + TAILQ_ENTRY(Assignment) real_assignments; +}; + struct Con { bool mapped; enum { diff --git a/include/i3.h b/include/i3.h index a18cd6bc..6aeea847 100644 --- a/include/i3.h +++ b/include/i3.h @@ -28,6 +28,7 @@ extern TAILQ_HEAD(bindings_head, Binding) *bindings; extern TAILQ_HEAD(autostarts_head, Autostart) autostarts; extern TAILQ_HEAD(assignments_head, Match) assignments; extern TAILQ_HEAD(ws_assignments_head, Workspace_Assignment) ws_assignments; +extern TAILQ_HEAD(real_assignments_head, Assignment) real_assignments; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; extern uint8_t root_depth; extern bool xcursor_supported, xkb_supported; diff --git a/include/window.h b/include/window.h index 1c48c012..fe282aa0 100644 --- a/include/window.h +++ b/include/window.h @@ -6,14 +6,14 @@ * given window. * */ -void window_update_class(i3Window *win, xcb_get_property_reply_t *prop); +void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt); /** * Updates the name by using _NET_WM_NAME (encoded in UTF-8) for the given * window. Further updates using window_update_name_legacy will be ignored. * */ -void window_update_name(i3Window *win, xcb_get_property_reply_t *prop); +void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt); /** * Updates the name by using WM_NAME (encoded in COMPOUND_TEXT). We do not @@ -22,7 +22,7 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop); * window_update_name()). * */ -void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop); +void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt); /** * Updates the CLIENT_LEADER (logical parent window). diff --git a/src/assignments.c b/src/assignments.c new file mode 100644 index 00000000..f41877f0 --- /dev/null +++ b/src/assignments.c @@ -0,0 +1,50 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) + * + */ +#include "all.h" + +/* + * Checks the list of assignments for the given window and runs all matching + * ones (unless they have already been run for this specific window). + * + */ +void run_assignments(i3Window *window) { + DLOG("Checking assignments...\n"); + + /* Check if any assignments match */ + Assignment *current; + TAILQ_FOREACH(current, &real_assignments, real_assignments) { + if (!match_matches_window(&(current->match), window)) + continue; + + bool skip = false; + for (int c = 0; c < window->nr_assignments; c++) { + if (window->ran_assignments[c] != current) + continue; + + DLOG("This assignment already ran for the given window, not executing it again.\n"); + skip = true; + break; + } + + if (skip) + continue; + + DLOG("matching assignment, would do:\n"); + if (current->type == A_COMMAND) { + DLOG("execute command %s\n", current->dest.command); + char *full_command; + asprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command); + parse_cmd(full_command); + } + + /* Store that we ran this assignment to not execute it again */ + window->nr_assignments++; + window->ran_assignments = srealloc(window->ran_assignments, sizeof(Assignment*) * window->nr_assignments); + window->ran_assignments[window->nr_assignments-1] = current; + } +} diff --git a/src/cfgparse.l b/src/cfgparse.l index 1615288e..09cf9c44 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -43,6 +43,9 @@ EOL (\r?\n) %s COLOR_COND %s OUTPUT_COND %s OUTPUT_AWS_COND +%s WANT_QSTRING +%s FOR_WINDOW_COND +%s REQUIRE_WS %x BUFFER_LINE %% @@ -68,6 +71,16 @@ EOL (\r?\n) } +"]" { yy_pop_state(); return ']'; } +[ \t]* { yy_pop_state(); return WHITESPACE; } +\"[^\"]+\" { + yy_pop_state(); + /* strip quotes */ + char *copy = sstrdup(yytext+1); + copy[strlen(copy)-1] = '\0'; + yylval.string = copy; + return STR; + } [^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; } [a-zA-Z0-9_-]+ { yylval.string = strdup(yytext); return OUTPUT; } ^[ \t]*#[^\n]* { return TOKCOMMENT; } @@ -108,6 +121,18 @@ workspace_bar { return TOKWORKSPACEBAR; } popup_during_fullscreen { return TOK_POPUP_DURING_FULLSCREEN; } ignore { return TOK_IGNORE; } leave_fullscreen { return TOK_LEAVE_FULLSCREEN; } +for_window { + /* Example: for_window [class="urxvt"] border none + * + * First, we wait for the ']' that finishes a match (FOR_WINDOW_COND) + * Then, we require a whitespace (REQUIRE_WS) + * And the rest of the line is parsed as a string + */ + yy_push_state(BIND_A2WS_COND); + yy_push_state(REQUIRE_WS); + yy_push_state(FOR_WINDOW_COND); + return TOK_FOR_WINDOW; + } default { /* yylval.number = MODE_DEFAULT; */return TOK_DEFAULT; } stacking { /* yylval.number = MODE_STACK; */return TOK_STACKING; } stacked { return TOK_STACKING; } @@ -134,6 +159,13 @@ control { return TOKCONTROL; } ctrl { return TOKCONTROL; } shift { return TOKSHIFT; } → { return TOKARROW; } + +class { yy_push_state(WANT_QSTRING); return TOK_CLASS; } +id { yy_push_state(WANT_QSTRING); return TOK_ID; } +con_id { yy_push_state(WANT_QSTRING); return TOK_CON_ID; } +con_mark { yy_push_state(WANT_QSTRING); return TOK_MARK; } +title { yy_push_state(WANT_QSTRING); return TOK_TITLE; } + {EOL} { FREE(context->line_copy); context->line_number++; diff --git a/src/cfgparse.y b/src/cfgparse.y index a5e0f0ba..00190dbc 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -7,9 +7,12 @@ #include #include #include +#include #include "all.h" +static Match current_match; + typedef struct yy_buffer_state *YY_BUFFER_STATE; extern int yylex(struct context *context); extern int yyparse(void); @@ -242,6 +245,13 @@ void parse_file(const char *f) { %token TOK_POPUP_DURING_FULLSCREEN "popup_during_fullscreen" %token TOK_IGNORE "ignore" %token TOK_LEAVE_FULLSCREEN "leave_fullscreen" +%token TOK_FOR_WINDOW "for_window" + +%token TOK_MARK "mark" +%token TOK_CLASS "class" +%token TOK_ID "id" +%token TOK_CON_ID "con_id" +%token TOK_TITLE "title" %type binding %type bindcode @@ -272,6 +282,7 @@ lines: /* empty */ line: bindline + | for_window | mode | floating_modifier | orientation @@ -292,6 +303,10 @@ line: | popup_during_fullscreen ; +optwhitespace: + | WHITESPACE + ; + comment: TOKCOMMENT ; @@ -340,6 +355,90 @@ bindsym: } ; +for_window: + TOK_FOR_WINDOW WHITESPACE match WHITESPACE command + { + printf("\t should execute command %s for the criteria mentioned above\n", $5); + Assignment *assignment = scalloc(sizeof(Assignment)); + assignment->type = A_COMMAND; + assignment->match = current_match; + assignment->dest.command = $5; + TAILQ_INSERT_TAIL(&real_assignments, assignment, real_assignments); + } + ; + +match: + | matchstart optwhitespace criteria optwhitespace matchend + { + printf("match parsed\n"); + } + ; + +matchstart: + '[' + { + printf("start\n"); + match_init(¤t_match); + } + ; + +matchend: + ']' + { + printf("match specification finished\n"); + } + ; + +criteria: + TOK_CLASS '=' STR + { + printf("criteria: class = %s\n", $3); + current_match.class = $3; + } + | TOK_CON_ID '=' STR + { + printf("criteria: id = %s\n", $3); + char *end; + long parsed = strtol($3, &end, 10); + if (parsed == LONG_MIN || + parsed == LONG_MAX || + parsed < 0 || + (end && *end != '\0')) { + ELOG("Could not parse con id \"%s\"\n", $3); + } else { + current_match.con_id = (Con*)parsed; + printf("id as int = %p\n", current_match.con_id); + } + } + | TOK_ID '=' STR + { + printf("criteria: window id = %s\n", $3); + char *end; + long parsed = strtol($3, &end, 10); + if (parsed == LONG_MIN || + parsed == LONG_MAX || + parsed < 0 || + (end && *end != '\0')) { + ELOG("Could not parse window id \"%s\"\n", $3); + } else { + current_match.id = parsed; + printf("window id as int = %d\n", current_match.id); + } + } + | TOK_MARK '=' STR + { + printf("criteria: mark = %s\n", $3); + current_match.mark = $3; + } + | TOK_TITLE '=' STR + { + printf("criteria: title = %s\n", $3); + current_match.title = $3; + } + ; + + + word_or_number: WORD | NUMBER diff --git a/src/cmdparse.y b/src/cmdparse.y index eb41c820..bc4858f2 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -3,7 +3,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) * * cmdparse.y: the parser for commands you send to i3 (or bind on keys) * @@ -80,6 +80,7 @@ int cmdyywrap() { } char *parse_cmd(const char *new) { + LOG("COMMAND: *%s*\n", new); cmdyy_scan_string(new); match_init(¤t_match); @@ -512,15 +513,21 @@ direction: mode: TOK_MODE WHITESPACE window_mode { - if ($3 == TOK_TOGGLE) { - printf("should toggle mode\n"); - toggle_floating_mode(focused, false); - } else { - printf("should switch mode to %s\n", ($3 == TOK_FLOATING ? "floating" : "tiling")); - if ($3 == TOK_FLOATING) { - floating_enable(focused, false); + HANDLE_EMPTY_MATCH; + + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + if ($3 == TOK_TOGGLE) { + printf("should toggle mode\n"); + toggle_floating_mode(current->con, false); } else { - floating_disable(focused, false); + printf("should switch mode to %s\n", ($3 == TOK_FLOATING ? "floating" : "tiling")); + if ($3 == TOK_FLOATING) { + floating_enable(current->con, false); + } else { + floating_disable(current->con, false); + } } } } @@ -608,11 +615,14 @@ layout: printf("changing layout to %d\n", $3); owindow *current; - HANDLE_EMPTY_MATCH; - - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - con_set_layout(current->con, $3); + /* check if the match is empty, not if the result is empty */ + if (match_is_empty(¤t_match)) + con_set_layout(focused->parent, $3); + else { + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + con_set_layout(current->con, $3); + } } } ; diff --git a/src/handlers.c b/src/handlers.c index f1e1c4b0..b87bb418 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -541,7 +541,7 @@ static int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t if ((con = con_by_window_id(window)) == NULL || con->window == NULL) return 1; - window_update_name(con->window, prop); + window_update_name(con->window, prop, false); x_push_changes(croot); @@ -559,7 +559,7 @@ static int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, u if ((con = con_by_window_id(window)) == NULL || con->window == NULL) return 1; - window_update_name_legacy(con->window, prop); + window_update_name_legacy(con->window, prop, false); x_push_changes(croot); @@ -576,7 +576,7 @@ static int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t if ((con = con_by_window_id(window)) == NULL || con->window == NULL) return 1; - window_update_class(con->window, prop); + window_update_class(con->window, prop, false); return 0; } diff --git a/src/main.c b/src/main.c index c079d8dc..39702223 100644 --- a/src/main.c +++ b/src/main.c @@ -37,6 +37,8 @@ struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments); * output) */ struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignments); +struct real_assignments_head real_assignments = TAILQ_HEAD_INITIALIZER(real_assignments); + /* We hope that those are supported and set them to true */ bool xcursor_supported = true; bool xkb_supported = true; diff --git a/src/manage.c b/src/manage.c index b511189c..7d240f42 100644 --- a/src/manage.c +++ b/src/manage.c @@ -158,9 +158,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* update as much information as possible so far (some replies may be NULL) */ - window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL)); - window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL)); - window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL)); + window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL), true); + window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL), true); + window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL), true); window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL)); window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL)); window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL)); @@ -330,6 +330,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki * cleanup) */ xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window); + /* Check if any assignments match */ + run_assignments(cwindow); + tree_render(); free(geom); diff --git a/src/match.c b/src/match.c index 9ed4d434..fd024d59 100644 --- a/src/match.c +++ b/src/match.c @@ -66,6 +66,12 @@ bool match_matches_window(Match *match, i3Window *window) { return true; } + /* TODO: pcre match */ + if (match->title != NULL && window->name_json != NULL && strcasecmp(match->title, window->name_json) == 0) { + LOG("match made by title (%s)\n", window->name_json); + return true; + } + LOG("match->dock = %d, window->dock = %d\n", match->dock, window->dock); if (match->dock != -1 && ((window->dock == W_DOCK_TOP && match->dock == M_DOCK_TOP) || diff --git a/src/window.c b/src/window.c index cd96890c..b6aaae9e 100644 --- a/src/window.c +++ b/src/window.c @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) * */ #include "all.h" @@ -12,7 +12,7 @@ * given window. * */ -void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) { +void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { DLOG("empty property, not updating\n"); return; @@ -32,6 +32,11 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) { else win->class_class = NULL; LOG("WM_CLASS changed to %s (instance), %s (class)\n", win->class_instance, win->class_class); + + if (before_mgmt) + return; + + run_assignments(win); } /* @@ -39,7 +44,7 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) { * window. Further updates using window_update_name_legacy will be ignored. * */ -void window_update_name(i3Window *win, xcb_get_property_reply_t *prop) { +void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { DLOG("_NET_WM_NAME not specified, not changing\n"); return; @@ -70,6 +75,11 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop) { LOG("_NET_WM_NAME changed to \"%s\"\n", win->name_json); win->uses_net_wm_name = true; + + if (before_mgmt) + return; + + run_assignments(win); } /* @@ -79,7 +89,7 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop) { * window_update_name()). * */ -void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop) { +void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { DLOG("prop == NULL\n"); return; @@ -106,6 +116,11 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop) { win->name_json = sstrdup(new_name); win->name_len = strlen(new_name); win->name_x_changed = true; + + if (before_mgmt) + return; + + run_assignments(win); } /* From 03cc490f0e72c0fddaaccf02641785558ebcabbc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 16 May 2011 20:51:29 +0200 Subject: [PATCH 633/867] Bugfix: fix crash when disabling floating mode --- src/floating.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/floating.c b/src/floating.c index 2f3f9bac..a60297fc 100644 --- a/src/floating.c +++ b/src/floating.c @@ -179,6 +179,8 @@ void floating_disable(Con *con, bool automatic) { return; } + Con *ws = con_get_workspace(con); + /* 1: detach from parent container */ TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); TAILQ_REMOVE(&(con->parent->focus_head), con, focused); @@ -190,7 +192,7 @@ void floating_disable(Con *con, bool automatic) { /* 3: re-attach to the parent of the currently focused con on the workspace * this floating con was on */ - Con *focused = con_descend_tiling_focused(con_get_workspace(con)); + Con *focused = con_descend_tiling_focused(ws); /* if there is no other container on this workspace, focused will be the * workspace itself */ From 1bd4c983dbbd9298d881ff06bdfd3675a5c03916 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 16 May 2011 21:45:30 +0200 Subject: [PATCH 634/867] tests: add test for the for_window config directive, use separate config for tests This test increases code coverage to 60.7% --- testcases/complete-run.pl | 2 +- testcases/i3-test.config | 105 ++++++++++++++++++++++++++++++ testcases/t/65-for_window.t | 123 ++++++++++++++++++++++++++++++++++++ 3 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 testcases/i3-test.config create mode 100644 testcases/t/65-for_window.t diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index c3a65480..e28bc19e 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -28,7 +28,7 @@ my $result = GetOptions( "coverage-testing" => \$coverage_testing ); -my $i3cmd = "export DISPLAY=:0; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c " . abs_path("../i3.config"); +my $i3cmd = "export DISPLAY=:0; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c i3-test.config"; # 1: get a list of all testcases my @testfiles = @ARGV; diff --git a/testcases/i3-test.config b/testcases/i3-test.config new file mode 100644 index 00000000..4065e5bb --- /dev/null +++ b/testcases/i3-test.config @@ -0,0 +1,105 @@ +for_window [class="borderless"] border none +for_window [title="special borderless title"] border none + +# ISO 10646 = Unicode +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +# Use Mouse+Mod1 to drag floating windows to their wanted position +floating_modifier Mod1 + +# temporary path during development +ipc-socket /tmp/nestedcons + +# Open empty container +bindsym Mod1+Shift+Return open + +# Start terminal (Mod1+Enter) +bindsym Mod1+Return exec /usr/bin/urxvt + +# Start dmenu (Mod1+p) +bindsym Mod1+p exec /usr/bin/dmenu_run + +# Horizontal orientation +bindsym Mod1+h split h + +# Vertical orientation +bindsym Mod1+v split v + +# Fullscreen (Mod1+f) +bindsym Mod1+f fullscreen + +# Stacking (Mod1+s) +bindsym Mod1+s layout stacking + +# Tabbed (Mod1+w) +bindsym Mod1+w layout tabbed + +# Default (Mod1+l) +bindsym Mod1+l layout default + +# toggle tiling / floating +bindsym Mod1+Shift+space mode toggle + +bindsym Mod1+u level up +#bindsym Mod1+d level down + +# Kill current client (Mod1+c) +bindsym Mod1+c kill + +# Restore saved JSON layout +bindsym Mod1+y restore /home/michael/i3/layout.json + +# Restart i3 +bindsym Mod1+Shift+c restart +# Reload i3 +bindsym Mod1+Shift+j reload +# Exit i3 +bindsym Mod1+Shift+l exit + +# Focus (Mod1+n/r/t/d) +bindsym Mod1+n prev h +bindsym Mod1+r next v +bindsym Mod1+t prev v +bindsym Mod1+d next h + +# alternatively, you can use the cursor keys: +bindsym Mod1+Left prev h +bindsym Mod1+Right next h +bindsym Mod1+Down next v +bindsym Mod1+Up prev v + +# Move +bindsym Mod1+Shift+n move left +bindsym Mod1+Shift+r move down +bindsym Mod1+Shift+t move up +bindsym Mod1+Shift+d move right + +# alternatively, you can use the cursor keys: +bindsym Mod1+Shift+Left move left +bindsym Mod1+Shift+Right move right +bindsym Mod1+Shift+Down move down +bindsym Mod1+Shift+Up move up + +# Workspaces (Mod1+1/2/…) +bindsym Mod1+1 workspace 1 +bindsym Mod1+2 workspace 2 +bindsym Mod1+3 workspace 3 +bindsym Mod1+4 workspace 4 +bindsym Mod1+5 workspace 5 +bindsym Mod1+6 workspace 6 +bindsym Mod1+7 workspace 7 +bindsym Mod1+8 workspace 8 +bindsym Mod1+9 workspace 9 +bindsym Mod1+0 workspace 10 + +# Move to Workspaces +bindsym Mod1+Shift+1 move workspace 1 +bindsym Mod1+Shift+2 move workspace 2 +bindsym Mod1+Shift+3 move workspace 3 +bindsym Mod1+Shift+4 move workspace 4 +bindsym Mod1+Shift+5 move workspace 5 +bindsym Mod1+Shift+6 move workspace 6 +bindsym Mod1+Shift+7 move workspace 7 +bindsym Mod1+Shift+8 move workspace 8 +bindsym Mod1+Shift+9 move workspace 9 +bindsym Mod1+Shift+0 move workspace 10 diff --git a/testcases/t/65-for_window.t b/testcases/t/65-for_window.t new file mode 100644 index 00000000..f780ce4b --- /dev/null +++ b/testcases/t/65-for_window.t @@ -0,0 +1,123 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# +use X11::XCB qw(:all); +use X11::XCB::Connection; +use i3test; + +my $x = X11::XCB::Connection->new; + +my $tmp = fresh_workspace; + +############################################################## +# 1: test the following directive: +# for_window [class="borderless"] border none +# by first creating a window with a different class (should get +# the normal border), then creating a window with the class +# "borderless" (should get no border) +############################################################## + +my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#00ff00', +); + +$window->name('Border window'); +$window->map; +sleep 0.25; + +my @content = @{get_ws_content($tmp)}; +cmp_ok(@content, '==', 1, 'one node on this workspace now'); +is($content[0]->{border}, 'normal', 'normal border'); + +$window->unmap; +sleep 0.25; + +my @content = @{get_ws_content($tmp)}; +cmp_ok(@content, '==', 0, 'no more nodes'); +diag('content = '. Dumper(\@content)); + +$window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#00ff00', +); + +$window->_create; + +# TODO: move this to X11::XCB::Window +sub set_wm_class { + my ($id, $class, $instance) = @_; + + # Add a _NET_WM_STRUT_PARTIAL hint + my $atomname = $x->atom(name => 'WM_CLASS'); + my $atomtype = $x->atom(name => 'STRING'); + + $x->change_property( + PROP_MODE_REPLACE, + $window->id, + $atomname->id, + $atomtype->id, + 8, + length($class) + length($instance) + 2, + "$instance\x00$class\x00" + ); +} + +set_wm_class($window->id, 'borderless', 'borderless'); +$window->name('Borderless window'); +$window->map; +sleep 0.25; + +@content = @{get_ws_content($tmp)}; +cmp_ok(@content, '==', 1, 'one node on this workspace now'); +is($content[0]->{border}, 'none', 'no border'); + +$window->unmap; +sleep 0.25; + +@content = @{get_ws_content($tmp)}; +cmp_ok(@content, '==', 0, 'no more nodes'); + +############################################################## +# 2: match on the title, check if for_window is really executed +# only once +############################################################## + +$window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#00ff00', +); + +$window->name('special title'); +$window->map; +sleep 0.25; + +@content = @{get_ws_content($tmp)}; +cmp_ok(@content, '==', 1, 'one node on this workspace now'); +is($content[0]->{border}, 'normal', 'normal border'); + +$window->name('special borderless title'); +sleep 0.25; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{border}, 'none', 'no border'); + +$window->name('special title'); +sleep 0.25; + +cmd 'border normal'; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{border}, 'normal', 'border reset to normal'); + +$window->name('special borderless title'); +sleep 0.25; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{border}, 'normal', 'still normal border'); + +done_testing; From 607fd7d0240c829a8fddb8337ec8ed32c1379f24 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 16 May 2011 22:03:07 +0200 Subject: [PATCH 635/867] tests: also test multiple commands in for_window Increases branch coverage to 49.1% --- testcases/i3-test.config | 1 + testcases/t/65-for_window.t | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/testcases/i3-test.config b/testcases/i3-test.config index 4065e5bb..ae9c0a98 100644 --- a/testcases/i3-test.config +++ b/testcases/i3-test.config @@ -1,5 +1,6 @@ for_window [class="borderless"] border none for_window [title="special borderless title"] border none +for_window [title="special mark title"] border none, mark bleh # ISO 10646 = Unicode font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 diff --git a/testcases/t/65-for_window.t b/testcases/t/65-for_window.t index f780ce4b..9c9c98d0 100644 --- a/testcases/t/65-for_window.t +++ b/testcases/t/65-for_window.t @@ -120,4 +120,41 @@ sleep 0.25; @content = @{get_ws_content($tmp)}; is($content[0]->{border}, 'normal', 'still normal border'); +$window->unmap; +sleep 0.25; + +@content = @{get_ws_content($tmp)}; +cmp_ok(@content, '==', 0, 'no more nodes'); + +############################################################## +# 3: match on the title, set border style *and* a mark +############################################################## + +$window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#00ff00', +); + +$window->name('special mark title'); +$window->map; +sleep 0.25; + +@content = @{get_ws_content($tmp)}; +cmp_ok(@content, '==', 1, 'one node on this workspace now'); +is($content[0]->{border}, 'none', 'no border'); + +my $other = open_standard_window($x); + +@content = @{get_ws_content($tmp)}; +cmp_ok(@content, '==', 2, 'two nodes'); +is($content[0]->{border}, 'none', 'no border'); +is($content[1]->{border}, 'normal', 'normal border'); +ok(!$content[0]->{focused}, 'first one not focused'); + +cmd qq|[con_mark="bleh"] focus|; + +@content = @{get_ws_content($tmp)}; +ok($content[0]->{focused}, 'first node focused'); + done_testing; From 08f64f011d00da966b474d4f93f7f55b78387fb7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 22 May 2011 21:26:50 +0200 Subject: [PATCH 636/867] cleanup cmdparse lexer/parser (ignore whitespace, solves conflicts) --- src/cmdparse.l | 34 +++++++----- src/cmdparse.y | 148 ++++++++++++++++++++++--------------------------- 2 files changed, 87 insertions(+), 95 deletions(-) diff --git a/src/cmdparse.l b/src/cmdparse.l index 6e33c950..1c71ed38 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -30,14 +30,20 @@ int cmdyycolumn = 1; cmdyycolumn += yyleng; \ } +/* macro to first eat whitespace, then expect a string */ +#define WS_STRING do { \ + yy_push_state(WANT_STRING); \ + yy_push_state(EAT_WHITESPACE); \ +} while (0) + %} EOL (\r?\n) /* handle everything up to \n as a string */ %s WANT_STRING -/* first expect a whitespace, then a string */ -%s WANT_WS_STRING +/* eat a whitespace, then go to the next state on the stack */ +%s EAT_WHITESPACE /* handle a quoted string or everything up to the next whitespace */ %s WANT_QSTRING @@ -65,7 +71,6 @@ EOL (\r?\n) cmdyycolumn = 1; } -[ \t]* { BEGIN(WANT_STRING); return WHITESPACE; } \"[^\"]+\" { BEGIN(INITIAL); /* strip quotes */ @@ -73,21 +78,24 @@ EOL (\r?\n) copy[strlen(copy)-1] = '\0'; cmdyylval.string = copy; return STR; - } -\"[^\"]+\" { + } +\"[^\"]+\" { BEGIN(INITIAL); /* strip quotes */ char *copy = sstrdup(yytext+1); copy[strlen(copy)-1] = '\0'; cmdyylval.string = copy; return STR; - } + } -[^;\n]+ { BEGIN(INITIAL); cmdyylval.string = sstrdup(yytext); return STR; } +[^;\n]+ { BEGIN(INITIAL); cmdyylval.string = sstrdup(yytext); return STR; } -[ \t]* { return WHITESPACE; } +[;\n] { BEGIN(INITIAL); return ';'; } +[ \t]* { yy_pop_state(); } + +[ \t]* { /* ignore whitespace */ ; } attach { return TOK_ATTACH; } -exec { BEGIN(WANT_WS_STRING); return TOK_EXEC; } +exec { WS_STRING; return TOK_EXEC; } exit { return TOK_EXIT; } reload { return TOK_RELOAD; } restart { return TOK_RESTART; } @@ -109,7 +117,7 @@ mode { return TOK_MODE; } tiling { return TOK_TILING; } floating { return TOK_FLOATING; } toggle { return TOK_TOGGLE; } -workspace { BEGIN(WANT_WS_STRING); return TOK_WORKSPACE; } +workspace { WS_STRING; return TOK_WORKSPACE; } focus { return TOK_FOCUS; } move { return TOK_MOVE; } open { return TOK_OPEN; } @@ -129,9 +137,9 @@ grow { return TOK_GROW; } px { return TOK_PX; } or { return TOK_OR; } ppt { return TOK_PPT; } -nop { BEGIN(WANT_WS_STRING); return TOK_NOP; } -restore { BEGIN(WANT_WS_STRING); return TOK_RESTORE; } -mark { BEGIN(WANT_WS_STRING); return TOK_MARK; } +nop { WS_STRING; return TOK_NOP; } +restore { WS_STRING; return TOK_RESTORE; } +mark { WS_STRING; return TOK_MARK; } class { BEGIN(WANT_QSTRING); return TOK_CLASS; } id { BEGIN(WANT_QSTRING); return TOK_ID; } diff --git a/src/cmdparse.y b/src/cmdparse.y index bc4858f2..b247d59b 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -106,7 +106,6 @@ char *parse_cmd(const char *new) { %} -%expect 5 %error-verbose %lex-param { struct context *context } @@ -116,7 +115,6 @@ char *parse_cmd(const char *new) { int number; } -%token TOK_ATTACH "attach" %token TOK_EXEC "exec" %token TOK_EXIT "exit" %token TOK_RELOAD "reload" @@ -166,7 +164,6 @@ char *parse_cmd(const char *new) { %token TOK_ID "id" %token TOK_CON_ID "con_id" -%token WHITESPACE "" %token STR "" %token NUMBER "" @@ -183,7 +180,7 @@ char *parse_cmd(const char *new) { %% commands: - commands optwhitespace ';' optwhitespace command + commands ';' command | command { owindow *current; @@ -198,16 +195,12 @@ commands: } ; -optwhitespace: - | WHITESPACE - ; - command: - match optwhitespace operations + match operations ; match: - | matchstart optwhitespace criteria optwhitespace matchend + | matchstart criteria matchend { printf("match parsed\n"); } @@ -322,8 +315,8 @@ criteria: ; operations: - operation optwhitespace - | operations ',' optwhitespace operation + operation + | operations ',' operation ; operation: @@ -336,7 +329,6 @@ operation: | restore | move | workspace - | attach | focus | kill | open @@ -352,11 +344,11 @@ operation: ; exec: - TOK_EXEC WHITESPACE STR + TOK_EXEC STR { - printf("should execute %s\n", $3); - start_application($3); - free($3); + printf("should execute %s\n", $2); + start_application($2); + free($2); } ; @@ -387,13 +379,6 @@ restart: } ; -attach: - TOK_ATTACH - { - printf("should attach\n"); - } - ; - focus: TOK_FOCUS { @@ -436,17 +421,16 @@ kill: optional_kill_mode: /* empty */ { $$ = KILL_WINDOW; } - | WHITESPACE { $$ = KILL_WINDOW; } - | WHITESPACE TOK_WINDOW { $$ = KILL_WINDOW; } - | WHITESPACE TOK_CLIENT { $$ = KILL_CLIENT; } + | TOK_WINDOW { $$ = KILL_WINDOW; } + | TOK_CLIENT { $$ = KILL_CLIENT; } ; workspace: - TOK_WORKSPACE WHITESPACE STR + TOK_WORKSPACE STR { - printf("should switch to workspace %s\n", $3); - workspace_show($3); - free($3); + printf("should switch to workspace %s\n", $2); + workspace_show($2); + free($2); } ; @@ -477,29 +461,29 @@ fullscreen: ; next: - TOK_NEXT WHITESPACE direction + TOK_NEXT direction { /* TODO: use matches */ - printf("should select next window in direction %c\n", $3); - tree_next('n', ($3 == 'v' ? VERT : HORIZ)); + printf("should select next window in direction %c\n", $2); + tree_next('n', ($2 == 'v' ? VERT : HORIZ)); } ; prev: - TOK_PREV WHITESPACE direction + TOK_PREV direction { /* TODO: use matches */ - printf("should select prev window in direction %c\n", $3); - tree_next('p', ($3 == 'v' ? VERT : HORIZ)); + printf("should select prev window in direction %c\n", $2); + tree_next('p', ($2 == 'v' ? VERT : HORIZ)); } ; split: - TOK_SPLIT WHITESPACE direction + TOK_SPLIT direction { /* TODO: use matches */ - printf("splitting in direction %c\n", $3); - tree_split(focused, ($3 == 'v' ? VERT : HORIZ)); + printf("splitting in direction %c\n", $2); + tree_split(focused, ($2 == 'v' ? VERT : HORIZ)); } ; @@ -511,19 +495,19 @@ direction: ; mode: - TOK_MODE WHITESPACE window_mode + TOK_MODE window_mode { HANDLE_EMPTY_MATCH; owindow *current; TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); - if ($3 == TOK_TOGGLE) { + if ($2 == TOK_TOGGLE) { printf("should toggle mode\n"); toggle_floating_mode(current->con, false); } else { - printf("should switch mode to %s\n", ($3 == TOK_FLOATING ? "floating" : "tiling")); - if ($3 == TOK_FLOATING) { + printf("should switch mode to %s\n", ($2 == TOK_FLOATING ? "floating" : "tiling")); + if ($2 == TOK_FLOATING) { floating_enable(current->con, false); } else { floating_disable(current->con, false); @@ -540,16 +524,16 @@ window_mode: ; border: - TOK_BORDER WHITESPACE border_style + TOK_BORDER border_style { - printf("border style should be changed to %d\n", $3); + printf("border style should be changed to %d\n", $2); owindow *current; HANDLE_EMPTY_MATCH; TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); - current->con->border_style = $3; + current->con->border_style = $2; } } ; @@ -562,10 +546,10 @@ border_style: level: - TOK_LEVEL WHITESPACE level_direction + TOK_LEVEL level_direction { - printf("level %c\n", $3); - if ($3 == 'u') + printf("level %c\n", $2); + if ($2 == 'u') level_up(); else level_down(); } @@ -577,19 +561,19 @@ level_direction: ; move: - TOK_MOVE WHITESPACE direction + TOK_MOVE direction { - printf("moving in direction %d\n", $3); - tree_move($3); + printf("moving in direction %d\n", $2); + tree_move($2); } - | TOK_MOVE WHITESPACE TOK_WORKSPACE WHITESPACE STR + | TOK_MOVE TOK_WORKSPACE STR { owindow *current; - printf("should move window to workspace %s\n", $5); + printf("should move window to workspace %s\n", $3); /* get the workspace */ - Con *ws = workspace_get($5, NULL); - free($5); + Con *ws = workspace_get($3, NULL); + free($3); HANDLE_EMPTY_MATCH; @@ -601,27 +585,27 @@ move: ; restore: - TOK_RESTORE WHITESPACE STR + TOK_RESTORE STR { - printf("restoring \"%s\"\n", $3); - tree_append_json($3); - free($3); + printf("restoring \"%s\"\n", $2); + tree_append_json($2); + free($2); } ; layout: - TOK_LAYOUT WHITESPACE layout_mode + TOK_LAYOUT layout_mode { - printf("changing layout to %d\n", $3); + printf("changing layout to %d\n", $2); owindow *current; /* check if the match is empty, not if the result is empty */ if (match_is_empty(¤t_match)) - con_set_layout(focused->parent, $3); + con_set_layout(focused->parent, $2); else { TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); - con_set_layout(current->con, $3); + con_set_layout(current->con, $2); } } } @@ -634,41 +618,41 @@ layout_mode: ; mark: - TOK_MARK WHITESPACE STR + TOK_MARK STR { - printf("marking window with str %s\n", $3); + printf("marking window with str %s\n", $2); owindow *current; HANDLE_EMPTY_MATCH; TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); - current->con->mark = sstrdup($3); + current->con->mark = sstrdup($2); } - free($3); + free($2); } ; nop: - TOK_NOP WHITESPACE STR + TOK_NOP STR { printf("-------------------------------------------------\n"); - printf(" NOP: %s\n", $3); + printf(" NOP: %s\n", $2); printf("-------------------------------------------------\n"); - free($3); + free($2); } ; resize: - TOK_RESIZE WHITESPACE resize_way WHITESPACE direction resize_px resize_tiling + TOK_RESIZE resize_way direction resize_px resize_tiling { /* resize [ px] [or ppt] */ - printf("resizing in way %d, direction %d, px %d or ppt %d\n", $3, $5, $6, $7); - int direction = $5; - int px = $6; - int ppt = $7; - if ($3 == TOK_SHRINK) { + printf("resizing in way %d, direction %d, px %d or ppt %d\n", $2, $3, $4, $5); + int direction = $3; + int px = $4; + int ppt = $5; + if ($2 == TOK_SHRINK) { px *= -1; ppt *= -1; } @@ -723,9 +707,9 @@ resize_px: { $$ = 10; } - | WHITESPACE NUMBER WHITESPACE TOK_PX + | NUMBER TOK_PX { - $$ = $2; + $$ = $1; } ; @@ -734,9 +718,9 @@ resize_tiling: { $$ = 10; } - | WHITESPACE TOK_OR WHITESPACE NUMBER WHITESPACE TOK_PPT + | TOK_OR NUMBER TOK_PPT { - $$ = $4; + $$ = $2; } ; From e27a8597d814c2073fc0fbbafc727fe37dc17457 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 22 May 2011 21:42:34 +0200 Subject: [PATCH 637/867] forgot to remove unused TOK_ATTACH token from lexer --- src/cmdparse.l | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cmdparse.l b/src/cmdparse.l index 1c71ed38..b366e48b 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -94,7 +94,6 @@ EOL (\r?\n) [ \t]* { yy_pop_state(); } [ \t]* { /* ignore whitespace */ ; } -attach { return TOK_ATTACH; } exec { WS_STRING; return TOK_EXEC; } exit { return TOK_EXIT; } reload { return TOK_RELOAD; } From 1d6447187c4caff63e89fa9674f7765793578ac7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 22 May 2011 21:48:25 +0200 Subject: [PATCH 638/867] cleanup cfgparse lexer/parser (ignore whitespace, solves conflicts) --- src/cfgparse.l | 16 ++--- src/cfgparse.y | 168 +++++++++++++++++++++++-------------------------- 2 files changed, 88 insertions(+), 96 deletions(-) diff --git a/src/cfgparse.l b/src/cfgparse.l index 09cf9c44..412923d6 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -72,7 +72,7 @@ EOL (\r?\n) "]" { yy_pop_state(); return ']'; } -[ \t]* { yy_pop_state(); return WHITESPACE; } +[ \t]* { yy_pop_state(); } \"[^\"]+\" { yy_pop_state(); /* strip quotes */ @@ -172,13 +172,13 @@ title { yy_push_state(WANT_QSTRING); return TOK_TITLE; BEGIN(INITIAL); yy_push_state(BUFFER_LINE); } -[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; } -[ \t]+ { BEGIN(BINDSYM_AWS_COND); return WHITESPACE; } -[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } -[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } -[ \t]+ { BEGIN(OUTPUT_AWS_COND); return WHITESPACE; } -[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } -[ \t]+ { return WHITESPACE; } +[ \t]+ { BEGIN(BIND_AWS_COND); } +[ \t]+ { BEGIN(BINDSYM_AWS_COND); } +[ \t]+ { BEGIN(BIND_A2WS_COND); } +[ \t]+ { BEGIN(BIND_A2WS_COND); } +[ \t]+ { BEGIN(OUTPUT_AWS_COND); } +[ \t]+ { BEGIN(BIND_A2WS_COND); } +[ \t]+ { /* ignore whitespace */ ; } \"[^\"]+\" { /* if ASSIGN_COND then */ BEGIN(INITIAL); diff --git a/src/cfgparse.y b/src/cfgparse.y index 00190dbc..346f9476 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -186,7 +186,6 @@ void parse_file(const char *f) { %} -%expect 1 %error-verbose %lex-param { struct context *context } @@ -213,7 +212,6 @@ void parse_file(const char *f) { %token MODIFIER "" %token TOKCONTROL "control" %token TOKSHIFT "shift" -%token WHITESPACE "" %token TOKFLOATING_MODIFIER "floating_modifier" %token QUOTEDSTRING "" %token TOKWORKSPACE "workspace" @@ -275,7 +273,6 @@ void parse_file(const char *f) { %% lines: /* empty */ - | lines WHITESPACE line | lines error | lines line ; @@ -303,10 +300,6 @@ line: | popup_during_fullscreen ; -optwhitespace: - | WHITESPACE - ; - comment: TOKCOMMENT ; @@ -323,52 +316,52 @@ bindline: ; binding: - TOKBINDCODE WHITESPACE bindcode { $$ = $3; } - | TOKBINDSYM WHITESPACE bindsym { $$ = $3; } + TOKBINDCODE bindcode { $$ = $2; } + | TOKBINDSYM bindsym { $$ = $2; } ; bindcode: - binding_modifiers NUMBER WHITESPACE command + binding_modifiers NUMBER command { - printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $4); + printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $3); Binding *new = scalloc(sizeof(Binding)); new->keycode = $2; new->mods = $1; - new->command = $4; + new->command = $3; $$ = new; } ; bindsym: - binding_modifiers word_or_number WHITESPACE command + binding_modifiers word_or_number command { - printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $4); + printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $3); Binding *new = scalloc(sizeof(Binding)); new->symbol = $2; new->mods = $1; - new->command = $4; + new->command = $3; $$ = new; } ; for_window: - TOK_FOR_WINDOW WHITESPACE match WHITESPACE command + TOK_FOR_WINDOW match command { - printf("\t should execute command %s for the criteria mentioned above\n", $5); + printf("\t should execute command %s for the criteria mentioned above\n", $3); Assignment *assignment = scalloc(sizeof(Assignment)); assignment->type = A_COMMAND; assignment->match = current_match; - assignment->dest.command = $5; + assignment->dest.command = $3; TAILQ_INSERT_TAIL(&real_assignments, assignment, real_assignments); } ; match: - | matchstart optwhitespace criteria optwhitespace matchend + | matchstart criteria matchend { printf("match parsed\n"); } @@ -448,13 +441,13 @@ word_or_number: ; mode: - TOKMODE WHITESPACE QUOTEDSTRING WHITESPACE '{' modelines '}' + TOKMODE QUOTEDSTRING '{' modelines '}' { - if (strcasecmp($3, "default") == 0) { + if (strcasecmp($2, "default") == 0) { printf("You cannot use the name \"default\" for your mode\n"); exit(1); } - printf("\t now in mode %s\n", $3); + printf("\t now in mode %s\n", $2); printf("\t current bindings = %p\n", current_bindings); Binding *binding; TAILQ_FOREACH(binding, current_bindings, bindings) { @@ -463,7 +456,7 @@ mode: } struct Mode *mode = scalloc(sizeof(struct Mode)); - mode->name = $3; + mode->name = $2; mode->bindings = current_bindings; current_bindings = NULL; SLIST_INSERT_HEAD(&modes, mode, modes); @@ -477,8 +470,7 @@ modelines: ; modeline: - WHITESPACE - | comment + comment | binding { if (current_bindings == NULL) { @@ -491,18 +483,18 @@ modeline: ; floating_modifier: - TOKFLOATING_MODIFIER WHITESPACE binding_modifiers + TOKFLOATING_MODIFIER binding_modifiers { - DLOG("floating modifier = %d\n", $3); - config.floating_modifier = $3; + DLOG("floating modifier = %d\n", $2); + config.floating_modifier = $2; } ; orientation: - TOK_ORIENTATION WHITESPACE direction + TOK_ORIENTATION direction { - DLOG("New containers should start with split direction %d\n", $3); - config.default_orientation = $3; + DLOG("New containers should start with split direction %d\n", $2); + config.default_orientation = $2; } ; @@ -513,10 +505,10 @@ direction: ; workspace_layout: - TOK_WORKSPACE_LAYOUT WHITESPACE layout_mode + TOK_WORKSPACE_LAYOUT layout_mode { - DLOG("new containers will be in mode %d\n", $3); - config.default_layout = $3; + DLOG("new containers will be in mode %d\n", $2); + config.default_layout = $2; #if 0 /* We also need to change the layout of the already existing @@ -537,11 +529,11 @@ workspace_layout: } #endif } - | TOK_WORKSPACE_LAYOUT WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER + | TOK_WORKSPACE_LAYOUT TOKSTACKLIMIT TOKSTACKLIMIT NUMBER { - DLOG("stack-limit %d with val %d\n", $5, $7); - config.container_stack_limit = $5; - config.container_stack_limit_value = $7; + DLOG("stack-limit %d with val %d\n", $3, $4); + config.container_stack_limit = $3; + config.container_stack_limit_value = $4; #if 0 /* See the comment above */ @@ -564,10 +556,10 @@ layout_mode: ; new_window: - TOKNEWWINDOW WHITESPACE border_style + TOKNEWWINDOW border_style { - DLOG("new windows should start with border style %d\n", $3); - config.default_border = $3; + DLOG("new windows should start with border style %d\n", $2); + config.default_border = $2; } ; @@ -594,53 +586,53 @@ bool: ; focus_follows_mouse: - TOKFOCUSFOLLOWSMOUSE WHITESPACE bool + TOKFOCUSFOLLOWSMOUSE bool { - DLOG("focus follows mouse = %d\n", $3); - config.disable_focus_follows_mouse = !($3); + DLOG("focus follows mouse = %d\n", $2); + config.disable_focus_follows_mouse = !($2); } ; workspace_bar: - TOKWORKSPACEBAR WHITESPACE bool + TOKWORKSPACEBAR bool { - DLOG("workspace bar = %d\n", $3); - config.disable_workspace_bar = !($3); + DLOG("workspace bar = %d\n", $2); + config.disable_workspace_bar = !($2); } ; workspace: - TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKOUTPUT WHITESPACE OUTPUT optional_workspace_name + TOKWORKSPACE NUMBER TOKOUTPUT OUTPUT optional_workspace_name { - int ws_num = $3; + int ws_num = $2; if (ws_num < 1) { DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { char *ws_name = NULL; - if ($8 == NULL) { + if ($5 == NULL) { asprintf(&ws_name, "%d", ws_num); } else { - ws_name = $8; + ws_name = $5; } - DLOG("Should assign workspace %s to output %s\n", ws_name, $7); + DLOG("Should assign workspace %s to output %s\n", ws_name, $4); struct Workspace_Assignment *assignment = scalloc(sizeof(struct Workspace_Assignment)); assignment->name = ws_name; - assignment->output = $7; + assignment->output = $4; TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments); } } - | TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name + | TOKWORKSPACE NUMBER workspace_name { - int ws_num = $3; + int ws_num = $2; if (ws_num < 1) { DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { - DLOG("workspace name to: %s\n", $5); + DLOG("workspace name to: %s\n", $3); #if 0 - if ($5 != NULL) { - workspace_set_name(workspace_get(ws_num - 1), $5); - free($5); + if ($3 != NULL) { + workspace_set_name(workspace_get(ws_num - 1), $3); + free($3); } #endif } @@ -648,8 +640,8 @@ workspace: ; optional_workspace_name: - /* empty */ { $$ = NULL; } - | WHITESPACE workspace_name { $$ = $2; } + /* empty */ { $$ = NULL; } + | workspace_name { $$ = $1; } ; workspace_name: @@ -659,20 +651,20 @@ workspace_name: ; assign: - TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target + TOKASSIGN window_class optional_arrow assign_target { - printf("assignment of %s\n", $3); + printf("assignment of %s\n", $2); - struct Match *match = $6; + struct Match *match = $4; char *separator = NULL; - if ((separator = strchr($3, '/')) != NULL) { + if ((separator = strchr($2, '/')) != NULL) { *(separator++) = '\0'; match->title = sstrdup(separator); } - if (*$3 != '\0') - match->class = sstrdup($3); - free($3); + if (*$2 != '\0') + match->class = sstrdup($2); + free($2); printf(" class = %s\n", match->class); printf(" title = %s\n", match->title); @@ -720,34 +712,34 @@ window_class: optional_arrow: /* NULL */ - | TOKARROW WHITESPACE + | TOKARROW ; ipcsocket: - TOKIPCSOCKET WHITESPACE STR + TOKIPCSOCKET STR { - config.ipc_socket_path = $3; + config.ipc_socket_path = $2; } ; restart_state: - TOKRESTARTSTATE WHITESPACE STR + TOKRESTARTSTATE STR { - config.restart_state_path = $3; + config.restart_state_path = $2; } ; exec: - TOKEXEC WHITESPACE STR + TOKEXEC STR { struct Autostart *new = smalloc(sizeof(struct Autostart)); - new->command = $3; + new->command = $2; TAILQ_INSERT_TAIL(&autostarts, new, autostarts); } ; terminal: - TOKTERMINAL WHITESPACE STR + TOKTERMINAL STR { ELOG("The terminal option is DEPRECATED and has no effect. " "Please remove it from your configuration file.\n"); @@ -755,29 +747,29 @@ terminal: ; font: - TOKFONT WHITESPACE STR + TOKFONT STR { - config.font = load_font($3, true); - printf("font %s\n", $3); + config.font = load_font($2, true); + printf("font %s\n", $2); } ; single_color: - TOKSINGLECOLOR WHITESPACE colorpixel + TOKSINGLECOLOR colorpixel { uint32_t *dest = $1; - *dest = $3; + *dest = $2; } ; color: - TOKCOLOR WHITESPACE colorpixel WHITESPACE colorpixel WHITESPACE colorpixel + TOKCOLOR colorpixel colorpixel colorpixel { struct Colortriple *dest = $1; - dest->border = $3; - dest->background = $5; - dest->text = $7; + dest->border = $2; + dest->background = $3; + dest->text = $4; } ; @@ -807,10 +799,10 @@ binding_modifier: ; popup_during_fullscreen: - TOK_POPUP_DURING_FULLSCREEN WHITESPACE popup_setting + TOK_POPUP_DURING_FULLSCREEN popup_setting { - DLOG("popup_during_fullscreen setting: %d\n", $3); - config.popup_during_fullscreen = $3; + DLOG("popup_during_fullscreen setting: %d\n", $2); + config.popup_during_fullscreen = $2; } ; From c23f3b45fca9d80f33a0e149fbfea3e7d20b5260 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 22 May 2011 22:08:40 +0200 Subject: [PATCH 639/867] cfgparse.l: kill a few states by using the stack --- src/cfgparse.l | 78 ++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/src/cfgparse.l b/src/cfgparse.l index 412923d6..5a4f47ee 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -5,7 +5,7 @@ %{ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * */ #include @@ -30,22 +30,24 @@ int yycolumn = 1; yycolumn += yyleng; \ } +/* macro to first eat whitespace, then expect a string */ +#define WS_STRING do { \ + yy_push_state(WANT_STRING); \ + yy_push_state(EAT_WHITESPACE); \ +} while (0) + %} EOL (\r?\n) -%s BINDCODE_COND +%s WANT_STRING +%s WANT_QSTRING %s BINDSYM_COND -%s BIND_AWS_COND -%s BINDSYM_AWS_COND -%s BIND_A2WS_COND %s ASSIGN_COND %s COLOR_COND %s OUTPUT_COND -%s OUTPUT_AWS_COND -%s WANT_QSTRING %s FOR_WINDOW_COND -%s REQUIRE_WS +%s EAT_WHITESPACE %x BUFFER_LINE %% @@ -62,7 +64,7 @@ EOL (\r?\n) ^[^\r\n]*/{EOL}? { /* save whole line */ - context->line_copy = strdup(yytext); + context->line_copy = sstrdup(yytext); yyless(0); yy_pop_state(); @@ -71,42 +73,42 @@ EOL (\r?\n) } -"]" { yy_pop_state(); return ']'; } -[ \t]* { yy_pop_state(); } -\"[^\"]+\" { +"]" { yy_pop_state(); return ']'; } +[ \t]* { yy_pop_state(); } +\"[^\"]+\" { yy_pop_state(); /* strip quotes */ char *copy = sstrdup(yytext+1); copy[strlen(copy)-1] = '\0'; yylval.string = copy; return STR; - } -[^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; } -[a-zA-Z0-9_-]+ { yylval.string = strdup(yytext); return OUTPUT; } + } +[^\n]+ { BEGIN(INITIAL); yylval.string = sstrdup(yytext); return STR; } +[a-zA-Z0-9_-]+ { yylval.string = sstrdup(yytext); return OUTPUT; } ^[ \t]*#[^\n]* { return TOKCOMMENT; } -[0-9a-fA-F]+ { yylval.string = strdup(yytext); return HEX; } +[0-9a-fA-F]+ { yylval.string = sstrdup(yytext); return HEX; } [0-9]+ { yylval.number = atoi(yytext); return NUMBER; } mode { return TOKMODE; } -bind { BEGIN(BINDCODE_COND); return TOKBINDCODE; } -bindcode { BEGIN(BINDCODE_COND); return TOKBINDCODE; } -bindsym { BEGIN(BINDSYM_COND); return TOKBINDSYM; } +bind { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; } +bindcode { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; } +bindsym { yy_push_state(BINDSYM_COND); yy_push_state(EAT_WHITESPACE); return TOKBINDSYM; } floating_modifier { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; } workspace { BEGIN(INITIAL); return TOKWORKSPACE; } -output { BEGIN(OUTPUT_COND); return TOKOUTPUT; } +output { yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE); return TOKOUTPUT; } screen { /* for compatibility until v3.φ */ ELOG("Assignments to screens are DEPRECATED and will not work. " \ "Please replace them with assignments to outputs.\n"); - BEGIN(OUTPUT_COND); + yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE); return TOKOUTPUT; } -terminal { BEGIN(BIND_AWS_COND); return TOKTERMINAL; } -font { BEGIN(BIND_AWS_COND); return TOKFONT; } +terminal { WS_STRING; return TOKTERMINAL; } +font { WS_STRING; return TOKFONT; } assign { BEGIN(ASSIGN_COND); return TOKASSIGN; } set[^\n]* { return TOKCOMMENT; } -ipc-socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; } -ipc_socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; } -restart_state { BEGIN(BIND_AWS_COND); return TOKRESTARTSTATE; } +ipc-socket { WS_STRING; return TOKIPCSOCKET; } +ipc_socket { WS_STRING; return TOKIPCSOCKET; } +restart_state { WS_STRING; return TOKRESTARTSTATE; } default_orientation { return TOK_ORIENTATION; } horizontal { return TOK_HORIZ; } vertical { return TOK_VERT; } @@ -125,11 +127,11 @@ for_window { /* Example: for_window [class="urxvt"] border none * * First, we wait for the ']' that finishes a match (FOR_WINDOW_COND) - * Then, we require a whitespace (REQUIRE_WS) + * Then, we require a whitespace (EAT_WHITESPACE) * And the rest of the line is parsed as a string */ - yy_push_state(BIND_A2WS_COND); - yy_push_state(REQUIRE_WS); + yy_push_state(WANT_STRING); + yy_push_state(EAT_WHITESPACE); yy_push_state(FOR_WINDOW_COND); return TOK_FOR_WINDOW; } @@ -140,7 +142,7 @@ tabbed { /* yylval.number = MODE_TABBED; */return TOK_T stack-limit { return TOKSTACKLIMIT; } cols { /* yylval.number = STACK_LIMIT_COLS; */return TOKSTACKLIMIT; } rows { /* yylval.number = STACK_LIMIT_ROWS; */return TOKSTACKLIMIT; } -exec { BEGIN(BIND_AWS_COND); return TOKEXEC; } +exec { WS_STRING; return TOKEXEC; } client.background { BEGIN(COLOR_COND); yylval.single_color = &config.client.background; return TOKSINGLECOLOR; } client.focused { BEGIN(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; } client.focused_inactive { BEGIN(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; } @@ -172,25 +174,21 @@ title { yy_push_state(WANT_QSTRING); return TOK_TITLE; BEGIN(INITIAL); yy_push_state(BUFFER_LINE); } -[ \t]+ { BEGIN(BIND_AWS_COND); } -[ \t]+ { BEGIN(BINDSYM_AWS_COND); } -[ \t]+ { BEGIN(BIND_A2WS_COND); } -[ \t]+ { BEGIN(BIND_A2WS_COND); } -[ \t]+ { BEGIN(OUTPUT_AWS_COND); } -[ \t]+ { BEGIN(BIND_A2WS_COND); } +[ \t]+ { BEGIN(WANT_STRING); } +[ \t]+ { BEGIN(WANT_STRING); } [ \t]+ { /* ignore whitespace */ ; } \"[^\"]+\" { /* if ASSIGN_COND then */ BEGIN(INITIAL); /* yylval will be the string, but without quotes */ - char *copy = strdup(yytext+1); + char *copy = sstrdup(yytext+1); copy[strlen(copy)-1] = '\0'; yylval.string = copy; return QUOTEDSTRING; } -[^ \t]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR_NG; } -[a-zA-Z0-9_]+ { yylval.string = strdup(yytext); return WORD; } -[a-zA-Z]+ { yylval.string = strdup(yytext); return WORD; } +[^ \t]+ { BEGIN(INITIAL); yylval.string = sstrdup(yytext); return STR_NG; } +[a-zA-Z0-9_]+ { yylval.string = sstrdup(yytext); return WORD; } +[a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; } . { return (int)yytext[0]; } <> { From 4c1392d8ab5e406af31a4616fde2a85fe24743fd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 22 May 2011 22:15:43 +0200 Subject: [PATCH 640/867] retab! cfgparse.l --- src/cfgparse.l | 60 +++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/cfgparse.l b/src/cfgparse.l index 5a4f47ee..41445944 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -1,13 +1,13 @@ +/* + * vim:ts=4:sw=4:expandtab + * + */ %option nounput %option noinput %option noyy_top_state %option stack %{ -/* - * vim:ts=4:sw=4:expandtab - * - */ #include #include #include @@ -25,9 +25,9 @@ int yycolumn = 1; #define YY_DECL int yylex (struct context *context) #define YY_USER_ACTION { \ - context->first_column = yycolumn; \ - context->last_column = yycolumn+yyleng-1; \ - yycolumn += yyleng; \ + context->first_column = yycolumn; \ + context->last_column = yycolumn+yyleng-1; \ + yycolumn += yyleng; \ } /* macro to first eat whitespace, then expect a string */ @@ -38,7 +38,7 @@ int yycolumn = 1; %} -EOL (\r?\n) +EOL (\r?\n) %s WANT_STRING %s WANT_QSTRING @@ -52,24 +52,24 @@ EOL (\r?\n) %% - { - /* This is called when a new line is lexed. We only want the - * first line to match to go into state BUFFER_LINE */ - if (context->line_number == 0) { - context->line_number = 1; - BEGIN(INITIAL); - yy_push_state(BUFFER_LINE); - } - } + { + /* This is called when a new line is lexed. We only want the + * first line to match to go into state BUFFER_LINE */ + if (context->line_number == 0) { + context->line_number = 1; + BEGIN(INITIAL); + yy_push_state(BUFFER_LINE); + } + } ^[^\r\n]*/{EOL}? { - /* save whole line */ - context->line_copy = sstrdup(yytext); + /* save whole line */ + context->line_copy = sstrdup(yytext); - yyless(0); - yy_pop_state(); - yy_set_bol(true); - yycolumn = 1; + yyless(0); + yy_pop_state(); + yy_set_bol(true); + yycolumn = 1; } @@ -84,7 +84,7 @@ EOL (\r?\n) return STR; } [^\n]+ { BEGIN(INITIAL); yylval.string = sstrdup(yytext); return STR; } -[a-zA-Z0-9_-]+ { yylval.string = sstrdup(yytext); return OUTPUT; } +[a-zA-Z0-9_-]+ { yylval.string = sstrdup(yytext); return OUTPUT; } ^[ \t]*#[^\n]* { return TOKCOMMENT; } [0-9a-fA-F]+ { yylval.string = sstrdup(yytext); return HEX; } [0-9]+ { yylval.number = atoi(yytext); return NUMBER; } @@ -174,8 +174,8 @@ title { yy_push_state(WANT_QSTRING); return TOK_TITLE; BEGIN(INITIAL); yy_push_state(BUFFER_LINE); } -[ \t]+ { BEGIN(WANT_STRING); } -[ \t]+ { BEGIN(WANT_STRING); } +[ \t]+ { BEGIN(WANT_STRING); } +[ \t]+ { BEGIN(WANT_STRING); } [ \t]+ { /* ignore whitespace */ ; } \"[^\"]+\" { /* if ASSIGN_COND then */ @@ -187,14 +187,14 @@ title { yy_push_state(WANT_QSTRING); return TOK_TITLE; return QUOTEDSTRING; } [^ \t]+ { BEGIN(INITIAL); yylval.string = sstrdup(yytext); return STR_NG; } -[a-zA-Z0-9_]+ { yylval.string = sstrdup(yytext); return WORD; } +[a-zA-Z0-9_]+ { yylval.string = sstrdup(yytext); return WORD; } [a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; } . { return (int)yytext[0]; } <> { - while (yy_start_stack_ptr > 0) - yy_pop_state(); - yyterminate(); + while (yy_start_stack_ptr > 0) + yy_pop_state(); + yyterminate(); } %% From 272a86745eafe9dc5f651507c9f929b72d78922c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 22 May 2011 23:32:59 +0200 Subject: [PATCH 641/867] Bugfix: Free pixmaps when killing windows (Thanks Mike) --- src/x.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/x.c b/src/x.c index cfbf5a3d..f37ef933 100644 --- a/src/x.c +++ b/src/x.c @@ -162,6 +162,8 @@ void x_con_kill(Con *con) { con_state *state; xcb_destroy_window(conn, con->frame); + xcb_free_pixmap(conn, con->pixmap); + xcb_free_gc(conn, con->pm_gc); state = state_for_frame(con->frame); CIRCLEQ_REMOVE(&state_head, state, state); CIRCLEQ_REMOVE(&old_state_head, state, old_state); From 2c68c234ea99f210ab2c8a47124f23849b5d6037 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 23 May 2011 18:41:17 +0200 Subject: [PATCH 642/867] Implement assignments for (named) workspaces, with '~' compatibility (floating) --- include/assignments.h | 6 +++ include/data.h | 26 ++++++++++--- include/i3.h | 3 +- include/match.h | 12 +++--- src/assignments.c | 20 +++++++++- src/cfgparse.l | 12 ++++-- src/cfgparse.y | 85 +++++++++++++++++++------------------------ src/main.c | 2 - src/manage.c | 10 +++-- src/match.c | 36 +++++++++--------- 10 files changed, 123 insertions(+), 89 deletions(-) diff --git a/include/assignments.h b/include/assignments.h index ecd8b808..f72dd2e5 100644 --- a/include/assignments.h +++ b/include/assignments.h @@ -12,4 +12,10 @@ */ void run_assignments(i3Window *window); +/** + * Returns the first matching assignment for the given window. + * + */ +Assignment *assignment_for(i3Window *window, int type); + #endif diff --git a/include/data.h b/include/data.h index 832200ee..fed4420c 100644 --- a/include/data.h +++ b/include/data.h @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) * * include/data.h: This file defines all data structures used by i3 * @@ -300,8 +300,6 @@ struct Match { Con *con_id; enum { M_ANY = 0, M_TILING, M_FLOATING } floating; - char *target_ws; - /* Where the window looking for a match should be inserted: * * M_HERE = the matched container will be replaced by the window @@ -314,9 +312,16 @@ struct Match { enum { M_HERE = 0, M_ASSIGN_WS, M_BELOW } insert_where; TAILQ_ENTRY(Match) matches; - TAILQ_ENTRY(Match) assignments; }; +/** + * An Assignment makes specific windows go to a specific workspace/output or + * run a command for that window. With this mechanism, the user can -- for + * example -- make specific windows floating or assign his browser to workspace + * "www". Checking if a window is assigned works by comparing the Match data + * structure with the window (see match_matches_window()). + * + */ struct Assignment { /** type of this assignment: * @@ -324,8 +329,17 @@ struct Assignment { * A_TO_WORKSPACE = assign the matching window to the specified workspace * A_TO_OUTPUT = assign the matching window to the specified output * + * 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 + * assignment_for() function. + * */ - enum { A_COMMAND = 0, A_TO_WORKSPACE = 1, A_TO_OUTPUT = 2 } type; + enum { + A_ANY = 0, + A_COMMAND = (1 << 0), + A_TO_WORKSPACE = (1 << 1), + A_TO_OUTPUT = (1 << 2) + } type; /** the criteria to check if a window matches */ Match match; @@ -337,7 +351,7 @@ struct Assignment { char *output; } dest; - TAILQ_ENTRY(Assignment) real_assignments; + TAILQ_ENTRY(Assignment) assignments; }; struct Con { diff --git a/include/i3.h b/include/i3.h index 6aeea847..7eb48ecc 100644 --- a/include/i3.h +++ b/include/i3.h @@ -26,9 +26,8 @@ extern Display *xlibdpy, *xkbdpy; extern int xkb_current_group; extern TAILQ_HEAD(bindings_head, Binding) *bindings; extern TAILQ_HEAD(autostarts_head, Autostart) autostarts; -extern TAILQ_HEAD(assignments_head, Match) assignments; extern TAILQ_HEAD(ws_assignments_head, Workspace_Assignment) ws_assignments; -extern TAILQ_HEAD(real_assignments_head, Assignment) real_assignments; +extern TAILQ_HEAD(assignments_head, Assignment) assignments; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; extern uint8_t root_depth; extern bool xcursor_supported, xkb_supported; diff --git a/include/match.h b/include/match.h index 4f0e9bdc..2786c66a 100644 --- a/include/match.h +++ b/include/match.h @@ -16,16 +16,16 @@ void match_init(Match *match); */ bool match_is_empty(Match *match); +/** + * Copies the data of a match from src to dest. + * + */ +void match_copy(Match *dest, Match *src); + /** * Check if a match data structure matches the given window. * */ bool match_matches_window(Match *match, i3Window *window); -/** - * Returns the first match in 'assignments' that matches the given window. - * - */ -Match *match_by_assignment(i3Window *window); - #endif diff --git a/src/assignments.c b/src/assignments.c index f41877f0..f171dc3b 100644 --- a/src/assignments.c +++ b/src/assignments.c @@ -17,7 +17,7 @@ void run_assignments(i3Window *window) { /* Check if any assignments match */ Assignment *current; - TAILQ_FOREACH(current, &real_assignments, real_assignments) { + TAILQ_FOREACH(current, &assignments, assignments) { if (!match_matches_window(&(current->match), window)) continue; @@ -48,3 +48,21 @@ void run_assignments(i3Window *window) { window->ran_assignments[window->nr_assignments-1] = current; } } + +/* + * Returns the first matching assignment for the given window. + * + */ +Assignment *assignment_for(i3Window *window, int type) { + Assignment *assignment; + + TAILQ_FOREACH(assignment, &assignments, assignments) { + if ((type != A_ANY && (assignment->type & type) == 0) || + !match_matches_window(&(assignment->match), window)) + continue; + DLOG("got a matching assignment (to %s)\n", assignment->dest.workspace); + return assignment; + } + + return NULL; +} diff --git a/src/cfgparse.l b/src/cfgparse.l index 41445944..fd9613f0 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -44,6 +44,7 @@ EOL (\r?\n) %s WANT_QSTRING %s BINDSYM_COND %s ASSIGN_COND +%s ASSIGN_TARGET_COND %s COLOR_COND %s OUTPUT_COND %s FOR_WINDOW_COND @@ -87,6 +88,8 @@ EOL (\r?\n) [a-zA-Z0-9_-]+ { yylval.string = sstrdup(yytext); return OUTPUT; } ^[ \t]*#[^\n]* { return TOKCOMMENT; } [0-9a-fA-F]+ { yylval.string = sstrdup(yytext); return HEX; } +[ \t]*→[ \t]* { BEGIN(WANT_STRING); } +[ \t]+ { BEGIN(WANT_STRING); } [0-9]+ { yylval.number = atoi(yytext); return NUMBER; } mode { return TOKMODE; } bind { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; } @@ -104,7 +107,7 @@ screen { } terminal { WS_STRING; return TOKTERMINAL; } font { WS_STRING; return TOKFONT; } -assign { BEGIN(ASSIGN_COND); return TOKASSIGN; } +assign { yy_push_state(ASSIGN_TARGET_COND); yy_push_state(ASSIGN_COND); return TOKASSIGN; } set[^\n]* { return TOKCOMMENT; } ipc-socket { WS_STRING; return TOKIPCSOCKET; } ipc_socket { WS_STRING; return TOKIPCSOCKET; } @@ -160,7 +163,6 @@ Mode_switch { yylval.number = BIND_MODE_SWITCH; return MODIF control { return TOKCONTROL; } ctrl { return TOKCONTROL; } shift { return TOKSHIFT; } -→ { return TOKARROW; } class { yy_push_state(WANT_QSTRING); return TOK_CLASS; } id { yy_push_state(WANT_QSTRING); return TOK_ID; } @@ -179,14 +181,16 @@ title { yy_push_state(WANT_QSTRING); return TOK_TITLE; [ \t]+ { /* ignore whitespace */ ; } \"[^\"]+\" { /* if ASSIGN_COND then */ - BEGIN(INITIAL); + if (yy_start_stack_ptr > 0) + yy_pop_state(); + else BEGIN(INITIAL); /* yylval will be the string, but without quotes */ char *copy = sstrdup(yytext+1); copy[strlen(copy)-1] = '\0'; yylval.string = copy; return QUOTEDSTRING; } -[^ \t]+ { BEGIN(INITIAL); yylval.string = sstrdup(yytext); return STR_NG; } +[^ \t\"]+ { BEGIN(ASSIGN_TARGET_COND); yylval.string = sstrdup(yytext); return STR_NG; } [a-zA-Z0-9_]+ { yylval.string = sstrdup(yytext); return WORD; } [a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; } . { return (int)yytext[0]; } diff --git a/src/cfgparse.y b/src/cfgparse.y index 346f9476..d6eb12cd 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -268,7 +268,6 @@ void parse_file(const char *f) { %type optional_workspace_name %type workspace_name %type window_class -%type assign_target %% @@ -356,7 +355,7 @@ for_window: assignment->type = A_COMMAND; assignment->match = current_match; assignment->dest.command = $3; - TAILQ_INSERT_TAIL(&real_assignments, assignment, real_assignments); + TAILQ_INSERT_TAIL(&assignments, assignment, assignments); } ; @@ -651,57 +650,54 @@ workspace_name: ; assign: - TOKASSIGN window_class optional_arrow assign_target + TOKASSIGN window_class STR { - printf("assignment of %s\n", $2); + printf("assignment of %s to *%s*\n", $2, $3); + char *workspace = $3; + char *criteria = $2; - struct Match *match = $4; + Assignment *assignment = scalloc(sizeof(Assignment)); + Match *match = &(assignment->match); + match_init(match); char *separator = NULL; - if ((separator = strchr($2, '/')) != NULL) { + if ((separator = strchr(criteria, '/')) != NULL) { *(separator++) = '\0'; match->title = sstrdup(separator); } - if (*$2 != '\0') - match->class = sstrdup($2); - free($2); + if (*criteria != '\0') + match->class = sstrdup(criteria); + free(criteria); printf(" class = %s\n", match->class); printf(" title = %s\n", match->title); - if (match->insert_where == M_ASSIGN_WS) - printf(" to ws %s\n", match->target_ws); - TAILQ_INSERT_TAIL(&assignments, match, assignments); - } - ; -assign_target: - NUMBER - { - /* TODO: named workspaces */ - Match *match = smalloc(sizeof(Match)); - match_init(match); - match->insert_where = M_ASSIGN_WS; - asprintf(&(match->target_ws), "%d", $1); - $$ = match; - } - | '~' - { - /* TODO: compatiblity */ -#if 0 - struct Assignment *new = scalloc(sizeof(struct Assignment)); - new->floating = ASSIGN_FLOATING_ONLY; - $$ = new; -#endif - } - | '~' NUMBER - { - /* TODO: compatiblity */ -#if 0 - struct Assignment *new = scalloc(sizeof(struct Assignment)); - new->workspace = $2; - new->floating = ASSIGN_FLOATING; - $$ = new; -#endif + /* Compatibility with older versions: If the assignment target starts + * with ~, we create the equivalent of: + * + * for_window [class="foo"] mode floating + */ + if (*workspace == '~') { + workspace++; + if (*workspace == '\0') { + /* This assignment was *only* for floating */ + assignment->type = A_COMMAND; + assignment->dest.command = sstrdup("mode floating"); + TAILQ_INSERT_TAIL(&assignments, assignment, assignments); + break; + } else { + /* Create a new assignment and continue afterwards */ + Assignment *floating = scalloc(sizeof(Assignment)); + match_copy(&(floating->match), match); + floating->type = A_COMMAND; + floating->dest.command = sstrdup("mode floating"); + TAILQ_INSERT_TAIL(&assignments, floating, assignments); + } + } + + assignment->type = A_TO_WORKSPACE; + assignment->dest.workspace = workspace; + TAILQ_INSERT_TAIL(&assignments, assignment, assignments); } ; @@ -710,11 +706,6 @@ window_class: | STR_NG ; -optional_arrow: - /* NULL */ - | TOKARROW - ; - ipcsocket: TOKIPCSOCKET STR { diff --git a/src/main.c b/src/main.c index 39702223..c079d8dc 100644 --- a/src/main.c +++ b/src/main.c @@ -37,8 +37,6 @@ struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments); * output) */ struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignments); -struct real_assignments_head real_assignments = TAILQ_HEAD_INITIALIZER(real_assignments); - /* We hope that those are supported and set them to true */ bool xcursor_supported = true; bool xkb_supported = true; diff --git a/src/manage.c b/src/manage.c index 7d240f42..9ccdc19a 100644 --- a/src/manage.c +++ b/src/manage.c @@ -205,17 +205,19 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki Con *nc = NULL; Match *match; + Assignment *assignment; /* check assignments first */ - if ((match = match_by_assignment(cwindow))) { + if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE | A_TO_OUTPUT))) { DLOG("Assignment matches (%p)\n", match); - if (match->insert_where == M_ASSIGN_WS) { - nc = con_descend_focused(workspace_get(match->target_ws, NULL)); - DLOG("focused on ws %s: %p / %s\n", match->target_ws, nc, nc->name); + if (assignment->type == A_TO_WORKSPACE) { + nc = con_descend_focused(workspace_get(assignment->dest.workspace, NULL)); + DLOG("focused on ws %s: %p / %s\n", assignment->dest.workspace, nc, nc->name); if (nc->type == CT_WORKSPACE) nc = tree_open_con(nc); else nc = tree_open_con(nc->parent); } + /* TODO: handle assignments with type == A_TO_OUTPUT */ } else { /* TODO: two matches for one container */ diff --git a/src/match.c b/src/match.c index fd024d59..2449bad7 100644 --- a/src/match.c +++ b/src/match.c @@ -45,6 +45,25 @@ bool match_is_empty(Match *match) { match->floating == M_ANY); } +/* + * Copies the data of a match from src to dest. + * + */ +void match_copy(Match *dest, Match *src) { + memcpy(dest, src, sizeof(Match)); + +#define STRDUP(field) do { \ + if (src->field != NULL) \ + dest->field = sstrdup(src->field); \ +} while (0) + + STRDUP(title); + STRDUP(mark); + STRDUP(application); + STRDUP(class); + STRDUP(instance); +} + /* * Check if a match data structure matches the given window. * @@ -87,20 +106,3 @@ bool match_matches_window(Match *match, i3Window *window) { return false; } - -/* - * Returns the first match in 'assignments' that matches the given window. - * - */ -Match *match_by_assignment(i3Window *window) { - Match *match; - - TAILQ_FOREACH(match, &assignments, assignments) { - if (!match_matches_window(match, window)) - continue; - DLOG("got a matching assignment (to %s)\n", match->target_ws); - return match; - } - - return NULL; -} From 07633a0dc25ae39da9c71f69f73623393cb07bf5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 24 May 2011 22:21:05 +0200 Subject: [PATCH 643/867] tests: make t/59-socketpaths exit gracefully Increases reported line coverage from 60.7% to 60.9% --- testcases/t/59-socketpaths.t | 10 +++++++--- testcases/t/lib/i3test.pm | 20 +++++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/testcases/t/59-socketpaths.t b/testcases/t/59-socketpaths.t index 3c9f90eb..9fc77910 100644 --- a/testcases/t/59-socketpaths.t +++ b/testcases/t/59-socketpaths.t @@ -34,7 +34,9 @@ ok(-d $folder, "folder $folder exists"); my $socketpath = "$folder/ipc-socket." . $process->pid; ok(-S $socketpath, "file $socketpath exists and is a socket"); -kill(9, $process->pid) or die "could not kill i3"; +exit_gracefully($process->pid, $socketpath); + +sleep 0.25; ##################################################################### # XDG_RUNTIME_DIR case: socket gets created in $XDG_RUNTIME_DIR/i3/ipc-socket. @@ -52,7 +54,9 @@ ok(-d "$rtdir/i3", "$rtdir/i3 exists and is a directory"); $socketpath = "$rtdir/i3/ipc-socket." . $process->pid; ok(-S $socketpath, "file $socketpath exists and is a socket"); -kill(9, $process->pid) or die "could not kill i3"; +exit_gracefully($process->pid, $socketpath); + +sleep 0.25; ##################################################################### # configuration file case: socket gets placed whereever we specify @@ -73,6 +77,6 @@ sleep 1; ok(-S $socketpath, "file $socketpath exists and is a socket"); -kill(9, $process->pid) or die "could not kill i3"; +exit_gracefully($process->pid, $socketpath); done_testing; diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 4e6989fc..2dbc83b8 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -10,10 +10,11 @@ use AnyEvent::I3; use List::Util qw(first); use List::MoreUtils qw(lastval); use Time::HiRes qw(sleep); +use Try::Tiny; use v5.10; use Exporter (); -our @EXPORT = qw(get_workspace_names get_unused_workspace fresh_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window get_dock_clients cmd does_i3_live); +our @EXPORT = qw(get_workspace_names get_unused_workspace fresh_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window get_dock_clients cmd does_i3_live exit_gracefully); my $tester = Test::Builder->new(); @@ -175,4 +176,21 @@ sub does_i3_live { return $ok; } +# Tries to exit i3 gracefully (with the 'exit' cmd) or kills the PID if that fails +sub exit_gracefully { + my ($pid, $socketpath) = @_; + $socketpath ||= '/tmp/nestedcons'; + + my $exited = 0; + try { + say "Exiting i3 cleanly..."; + i3($socketpath)->command('exit')->recv; + $exited = 1; + }; + + if (!$exited) { + kill(9, $pid) or die "could not kill i3"; + } +} + 1 From 57516db391d0f6e7eceaea05c6e70f7b1060b717 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 24 May 2011 22:56:06 +0200 Subject: [PATCH 644/867] fix typo in t/65-for_window.t --- testcases/t/65-for_window.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcases/t/65-for_window.t b/testcases/t/65-for_window.t index 9c9c98d0..d2952540 100644 --- a/testcases/t/65-for_window.t +++ b/testcases/t/65-for_window.t @@ -57,7 +57,7 @@ sub set_wm_class { $x->change_property( PROP_MODE_REPLACE, - $window->id, + $id, $atomname->id, $atomtype->id, 8, From b0bfcb42af6d35457f038f255091d7d2c43d1307 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 24 May 2011 22:56:28 +0200 Subject: [PATCH 645/867] tests: add test for the 'assign' feature Increases line coverage from 60.9% to 61.5% --- testcases/t/66-assign.t | 165 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 testcases/t/66-assign.t diff --git a/testcases/t/66-assign.t b/testcases/t/66-assign.t new file mode 100644 index 00000000..56b90548 --- /dev/null +++ b/testcases/t/66-assign.t @@ -0,0 +1,165 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3 +# +# Tests if assignments work +# +use i3test; +use Cwd qw(abs_path); +use Proc::Background; +use File::Temp qw(tempfile tempdir); +use X11::XCB qw(:all); +use X11::XCB::Connection; +use v5.10; + +my $x = X11::XCB::Connection->new; + +# assuming we are run by complete-run.pl +my $i3_path = abs_path("../i3"); + +# TODO: move to X11::XCB +sub set_wm_class { + my ($id, $class, $instance) = @_; + + # Add a _NET_WM_STRUT_PARTIAL hint + my $atomname = $x->atom(name => 'WM_CLASS'); + my $atomtype = $x->atom(name => 'STRING'); + + $x->change_property( + PROP_MODE_REPLACE, + $id, + $atomname->id, + $atomtype->id, + 8, + length($class) + length($instance) + 2, + "$instance\x00$class\x00" + ); +} + + +##################################################################### +# start a window and see that it does not get assigned with an empty config +##################################################################### + +my ($fh, $tmpfile) = tempfile(); +say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; +say $fh "ipc-socket /tmp/nestedcons"; +close($fh); + +diag("Starting i3"); +my $i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null"; +my $process = Proc::Background->new($i3cmd); +sleep 1; + +diag("pid = " . $process->pid); + +my $tmp = fresh_workspace; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + +my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#0000ff', +); + +$window->_create; +set_wm_class($window->id, 'special', 'special'); +$window->name('special window'); +$window->map; +sleep 0.25; + +ok(@{get_ws_content($tmp)} == 1, 'special window got managed to current (random) workspace'); + +exit_gracefully($process->pid); + +$window->destroy; + +sleep 0.25; + +##################################################################### +# start a window and see that it gets assigned to a formerly unused +# workspace +##################################################################### + +($fh, $tmpfile) = tempfile(); +say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; +say $fh "ipc-socket /tmp/nestedcons"; +say $fh q|assign "special" → targetws|; +close($fh); + +diag("Starting i3"); +$i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null"; +$process = Proc::Background->new($i3cmd); +sleep 1; + +diag("pid = " . $process->pid); + +$tmp = fresh_workspace; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); +my $workspaces = get_workspace_names; +ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet'); + +my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#0000ff', +); + +$window->_create; +set_wm_class($window->id, 'special', 'special'); +$window->name('special window'); +$window->map; +sleep 0.25; + +ok(@{get_ws_content($tmp)} == 0, 'still no containers'); +ok("targetws" ~~ @{get_workspace_names()}, 'targetws exists'); + +$window->destroy; + +exit_gracefully($process->pid); + +sleep 0.25; + +##################################################################### +# start a window and see that it gets assigned to a workspace which has content +# already, next to the existing node. +##################################################################### + +diag("Starting i3"); +$i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null"; +$process = Proc::Background->new($i3cmd); +sleep 1; + +diag("pid = " . $process->pid); + +# initialize the target workspace, then go to a fresh one +ok(!("targetws" ~~ @{get_workspace_names()}), 'targetws does not exist yet'); +cmd 'workspace targetws'; +cmp_ok(@{get_ws_content('targetws')}, '==', 0, 'no containers on targetws yet'); +cmd 'open'; +cmp_ok(@{get_ws_content('targetws')}, '==', 1, 'one container on targetws'); +$tmp = fresh_workspace; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); +ok("targetws" ~~ @{get_workspace_names()}, 'targetws does not exist yet'); + +my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#0000ff', +); + +$window->_create; +set_wm_class($window->id, 'special', 'special'); +$window->name('special window'); +$window->map; +sleep 0.25; + +ok(@{get_ws_content($tmp)} == 0, 'still no containers'); +ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws'); + +exit_gracefully($process->pid); + +done_testing; From 3a6b0f661875849b1a638c983e729f72b7f3ce20 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 25 May 2011 20:15:52 +0200 Subject: [PATCH 646/867] tests: use Test:Most instead of Test:More apt-get install libtest-most-perl --- testcases/t/lib/i3test.pm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 2dbc83b8..ed75faff 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -29,8 +29,7 @@ sub import { my $class = shift; my $pkg = caller; eval "package $pkg; -use Test::More" . (@_ > 0 ? " qw(@_)" : "") . "; -use Test::Exception; +use Test::Most" . (@_ > 0 ? " qw(@_)" : "") . "; use Data::Dumper; use AnyEvent::I3; use Time::HiRes qw(sleep); From df0ec0f771b56f51c1bf5767aeae932b2c712292 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 25 May 2011 20:16:14 +0200 Subject: [PATCH 647/867] tests: explicitly declare done_testing in subtest --- testcases/t/58-wm_take_focus.t | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/testcases/t/58-wm_take_focus.t b/testcases/t/58-wm_take_focus.t index a7552c24..eca3cef2 100644 --- a/testcases/t/58-wm_take_focus.t +++ b/testcases/t/58-wm_take_focus.t @@ -4,12 +4,12 @@ # Tests if the WM_TAKE_FOCUS protocol is correctly handled by i3 # use X11::XCB qw(:all); -use EV; -use AnyEvent; use i3test; use v5.10; BEGIN { + use_ok('EV'); + use_ok('AnyEvent'); use_ok('X11::XCB::Window'); use_ok('X11::XCB::Event::Generic'); use_ok('X11::XCB::Event::MapNotify'); @@ -59,6 +59,8 @@ subtest 'Window without WM_TAKE_FOCUS', sub { my $result = $cv->recv; ok($result, 'cv result'); + + done_testing; }; subtest 'Window with WM_TAKE_FOCUS', sub { @@ -105,6 +107,8 @@ subtest 'Window with WM_TAKE_FOCUS', sub { my ($data, $time) = unpack("L2", $result); is($data, $x->atom(name => 'WM_TAKE_FOCUS')->id, 'first uint32_t contains WM_TAKE_FOCUS atom'); } + + done_testing; }; From 7208d010489ba9ebd79b20aa830ae7fb176f05dc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 25 May 2011 20:47:47 +0200 Subject: [PATCH 648/867] remove unused code from manage.c --- src/manage.c | 221 +-------------------------------------------------- 1 file changed, 1 insertion(+), 220 deletions(-) diff --git a/src/manage.c b/src/manage.c index 9ccdc19a..80bfbc52 100644 --- a/src/manage.c +++ b/src/manage.c @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) * * manage.c: Contains all functions for initially managing new windows * (or existing ones on restart). @@ -342,222 +342,3 @@ out: free(attr); return; } - -#if 0 -void reparent_window(xcb_connection_t *conn, xcb_window_t child, - xcb_visualid_t visual, xcb_window_t root, uint8_t depth, - int16_t x, int16_t y, uint16_t width, uint16_t height, - uint32_t border_width) { - - /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */ - width = max(width, 75); - height = max(height, 50); - - if (config.default_border != NULL) - client_init_border(conn, new, config.default_border[1]); - - /* We need to grab the mouse buttons for click to focus */ - xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS, - XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, - 1 /* left mouse button */, - XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); - - xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS, - XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, - 3 /* right mouse button */, - XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); - - if (dock) { - DLOG("Window is a dock.\n"); - Output *t_out = get_output_containing(x, y); - if (t_out != c_ws->output) { - DLOG("Dock client requested to be on output %s by geometry (%d, %d)\n", - t_out->name, x, y); - new->workspace = t_out->current_workspace; - } - new->dock = true; - new->borderless = true; - new->titlebar_position = TITLEBAR_OFF; - new->force_reconfigure = true; - new->container = NULL; - SLIST_INSERT_HEAD(&(t_out->dock_clients), new, dock_clients); - /* If it’s a dock we can’t make it float, so we break */ - new->floating = FLOATING_AUTO_OFF; - } - - /* All clients which have a leader should be floating */ - if (!new->dock && !client_is_floating(new) && new->leader != 0) { - DLOG("Client has WM_CLIENT_LEADER hint set, setting floating\n"); - new->floating = FLOATING_AUTO_ON; - } - - if (new->workspace->auto_float) { - new->floating = FLOATING_AUTO_ON; - DLOG("workspace is in autofloat mode, setting floating\n"); - } - - if (new->dock) { - /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */ - uint32_t *strut; - preply = xcb_get_property_reply(conn, strut_cookie, NULL); - if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) { - /* We only use a subset of the provided values, namely the reserved space at the top/bottom - of the screen. This is because the only possibility for bars is at to be at the top/bottom - with maximum horizontal size. - TODO: bars at the top */ - new->desired_height = strut[3]; - if (new->desired_height == 0) { - DLOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height); - new->desired_height = original_height; - } - DLOG("the client wants to be %d pixels high\n", new->desired_height); - } else { - DLOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height); - new->desired_height = original_height; - } - } else { - /* If it’s not a dock, we can check on which workspace we should put it. */ - - /* Firstly, we need to get the window’s class / title. We asked for the properties at the - * top of this function, get them now and pass them to our callback function for window class / title - * changes. It is important that the client was already inserted into the by_child table, - * because the callbacks won’t work otherwise. */ - preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL); - handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply); - - preply = xcb_get_property_reply(conn, title_cookie, NULL); - handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply); - - preply = xcb_get_property_reply(conn, class_cookie, NULL); - handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply); - - /* if WM_CLIENT_LEADER is set, we put the new window on the - * same window as its leader. This might be overwritten by - * assignments afterwards. */ - if (new->leader != XCB_NONE) { - DLOG("client->leader is set (to 0x%08x)\n", new->leader); - Client *parent = table_get(&by_child, new->leader); - if (parent != NULL && parent->container != NULL) { - Workspace *t_ws = parent->workspace; - new->container = t_ws->table[parent->container->col][parent->container->row]; - new->workspace = t_ws; - old_focused = new->container->currently_focused; - map_frame = workspace_is_visible(t_ws); - new->urgent = true; - /* This is a little tricky: we cannot use - * workspace_update_urgent_flag() because the - * new window was not yet inserted into the - * focus stack on t_ws. */ - t_ws->urgent = true; - } else { - DLOG("parent is not usable\n"); - } - } - - struct Assignment *assign; - TAILQ_FOREACH(assign, &assignments, assignments) { - if (get_matching_client(conn, assign->windowclass_title, new) == NULL) - continue; - - if (assign->floating == ASSIGN_FLOATING_ONLY || - assign->floating == ASSIGN_FLOATING) { - new->floating = FLOATING_AUTO_ON; - LOG("Assignment matches, putting client into floating mode\n"); - if (assign->floating == ASSIGN_FLOATING_ONLY) - break; - } - - LOG("Assignment \"%s\" matches, so putting it on workspace %d\n", - assign->windowclass_title, assign->workspace); - - if (c_ws->output->current_workspace->num == (assign->workspace-1)) { - DLOG("We are already there, no need to do anything\n"); - break; - } - - DLOG("Changing container/workspace and unmapping the client\n"); - Workspace *t_ws = workspace_get(assign->workspace-1, NULL); - workspace_initialize(t_ws, c_ws->output, false); - - new->container = t_ws->table[t_ws->current_col][t_ws->current_row]; - new->workspace = t_ws; - old_focused = new->container->currently_focused; - - map_frame = workspace_is_visible(t_ws); - break; - } - } - - - if (client_is_floating(new)) { - SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients); - - /* Add the client to the list of floating clients for its workspace */ - TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients); - - new->container = NULL; - - new->rect.width = new->floating_rect.width + 2 + 2; - new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2; - - /* Some clients (like GIMP’s color picker window) get mapped - * to (0, 0), so we push them to a reasonable position - * (centered over their leader) */ - if (new->leader != 0 && x == 0 && y == 0) { - DLOG("Floating client wants to (0x0), moving it over its leader instead\n"); - Client *leader = table_get(&by_child, new->leader); - if (leader == NULL) { - DLOG("leader is NULL, centering it over current workspace\n"); - - x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2); - y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2); - } else { - x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2); - y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2); - } - } - new->floating_rect.x = new->rect.x = x; - new->floating_rect.y = new->rect.y = y; - DLOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n", - new->floating_rect.x, new->floating_rect.y, - new->floating_rect.width, new->floating_rect.height); - DLOG("outer rect (%d, %d) size (%d, %d)\n", - new->rect.x, new->rect.y, new->rect.width, new->rect.height); - - /* Make sure it is on top of the other windows */ - xcb_raise_window(conn, new->frame); - reposition_client(conn, new); - resize_client(conn, new); - /* redecorate_window flushes */ - redecorate_window(conn, new); - } - - new->initialized = true; - - - render_layout(conn); - -map: - /* Map the window first to avoid flickering */ - xcb_map_window(conn, child); - if (map_frame) - client_map(conn, new); - - if ((CUR_CELL->workspace->fullscreen_client == NULL || new->fullscreen) && !new->dock) { - /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */ - if ((new->workspace->fullscreen_client == NULL) || new->fullscreen) { - if (!client_is_floating(new)) { - new->container->currently_focused = new; - if (map_frame) - render_container(conn, new->container); - } - if (new->container == CUR_CELL || client_is_floating(new)) { - xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); - ewmh_update_active_window(new->child); - } - } - } - - xcb_flush(conn); -} -#endif From 7fca97b151ad142eae8400c7248487fd75b499aa Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 25 May 2011 21:00:46 +0200 Subject: [PATCH 649/867] gitignore: ignore tarballs --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d1555b0d..f62aac51 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ testcases/latest *.tab.c *.tab.h *.yy.c +*.tar.bz2* From 7ae0c9c97371c3d413ff023024c40a42264f0556 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 25 May 2011 21:00:53 +0200 Subject: [PATCH 650/867] Bugfix: Check swallows before assignments when managing windows (Thanks julien) Fixes #395 (empty containers on restart when assigned windows are visible) --- src/manage.c | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/manage.c b/src/manage.c index 80bfbc52..15c1cdbc 100644 --- a/src/manage.c +++ b/src/manage.c @@ -207,34 +207,35 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki Match *match; Assignment *assignment; - /* check assignments first */ - if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE | A_TO_OUTPUT))) { - DLOG("Assignment matches (%p)\n", match); - if (assignment->type == A_TO_WORKSPACE) { - nc = con_descend_focused(workspace_get(assignment->dest.workspace, NULL)); - DLOG("focused on ws %s: %p / %s\n", assignment->dest.workspace, nc, nc->name); - if (nc->type == CT_WORKSPACE) - nc = tree_open_con(nc); - else nc = tree_open_con(nc->parent); - } - /* TODO: handle assignments with type == A_TO_OUTPUT */ - } else { - /* TODO: two matches for one container */ + /* TODO: two matches for one container */ - /* See if any container swallows this new window */ - nc = con_for_window(search_at, cwindow, &match); - if (nc == NULL) { + /* See if any container swallows this new window */ + nc = con_for_window(search_at, cwindow, &match); + if (nc == NULL) { + /* If not, check if it is assigned to a specific workspace / output */ + if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE | A_TO_OUTPUT))) { + DLOG("Assignment matches (%p)\n", match); + if (assignment->type == A_TO_WORKSPACE) { + nc = con_descend_focused(workspace_get(assignment->dest.workspace, NULL)); + DLOG("focused on ws %s: %p / %s\n", assignment->dest.workspace, nc, nc->name); + if (nc->type == CT_WORKSPACE) + nc = tree_open_con(nc); + else nc = tree_open_con(nc->parent); + } + /* TODO: handle assignments with type == A_TO_OUTPUT */ + } else { + /* If not, insert it at the currently focused position */ if (focused->type == CT_CON && con_accepts_window(focused)) { LOG("using current container, focused = %p, focused->name = %s\n", focused, focused->name); nc = focused; } else nc = tree_open_con(NULL); - } else { - /* M_BELOW inserts the new window as a child of the one which was - * matched (e.g. dock areas) */ - if (match != NULL && match->insert_where == M_BELOW) { - nc = tree_open_con(nc); - } + } + } else { + /* M_BELOW inserts the new window as a child of the one which was + * matched (e.g. dock areas) */ + if (match != NULL && match->insert_where == M_BELOW) { + nc = tree_open_con(nc); } } From a26a11c6099f82fc9fd8b87f8bda128408b4f7c9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 28 May 2011 21:58:58 +0200 Subject: [PATCH 651/867] update the userguide for tree (not complete yet) --- docs/userguide | 389 +++++++++++++++++++++++++++---------------------- 1 file changed, 212 insertions(+), 177 deletions(-) diff --git a/docs/userguide b/docs/userguide index 5610eaa5..aea0e760 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1,12 +1,18 @@ i3 User’s Guide =============== Michael Stapelberg -March 2010 +May 2011 + +********************************************************************************* +This document is not yet finished. The tree branch is still in development. The +information provided here should be correct, just not complete yet. +********************************************************************************* This document contains all the information you need to configure and use the i3 -window manager. If it does not, please contact me on IRC, Jabber or E-Mail and -I’ll help you out. +window manager. If it does not, please contact us on IRC (preferred) or post your +question(s) on the mailing list. +////////////////////////////////////////////////////////////////////////////// == Default keybindings For the "too long; didn’t read" people, here is an overview of the default @@ -26,42 +32,48 @@ you can also use keysymbols, see <>). The red keys are the modifiers you need to press (by default), the blue keys are your homerow. +////////////////////////////////////////////////////////////////////////////// == Using i3 +Throughout this guide, the keyword +mod+ will be used to refer to the +configured modifier. This is the windows key (mod4) by default, with alt (mod1) +being a popular alternative. + === Opening terminals and moving around One very basic operation is opening a new terminal. By default, the keybinding -for this is Mod1+Enter, that is Alt+Enter in the default configuration. By -pressing Mod1+Enter, a new terminal will be opened. It will fill the whole +for this is mod+Enter, that is Win+Enter in the default configuration. By +pressing mod+Enter, a new terminal will be opened. It will fill the whole space available on your screen. image:single_terminal.png[Single terminal] -It is important to keep in mind that i3 uses a table to manage your windows. At -the moment, you have exactly one column and one row which leaves you with one -cell. In this cell there is a container, which is where your new terminal is -opened. - -If you now open another terminal, you still have only one cell. However, the -container in that cell holds both of your terminals. So, a container is just a -group of clients with a specific layout. Containers can be resized by adjusting -the size of the cell that holds them. +If you now open another terminal, i3 will place it next to the current one, +splitting the screen size in half. Depending on your monitor, i3 will put the +new window right to the old window (for widescreen) or below the old window +(rotated displays). image:two_terminals.png[Two terminals] To move the focus between the two terminals, you use the direction keys which you may know from the editor +vi+. However, in i3, your homerow is used for these keys (in +vi+, the keys are shifted to the left by one for compatibility -with most keyboard layouts). Therefore, +Mod1+J+ is left, +Mod1+K+ is down, -+Mod1+L+ is up and `Mod1+;` is right. So, to switch between the terminals, -use +Mod1+K+ or +Mod1+L+. +with most keyboard layouts). Therefore, +mod+J+ is left, +mod+K+ is down, ++mod+L+ is up and `mod+;` is right. So, to switch between the terminals, +use +mod+K+ or +mod+L+. -To create a new row/column (and a new cell), you can simply move a terminal (or -any other window) in the direction you want to expand your table. So, let’s -expand the table to the right by pressing `Mod1+Shift+;`. +At the moment, your workspace is split (it contains two terminals) in a +specific direction (horizontal by default). Every window can be split +horizontally or vertically again, just like the workspace. The terminology is +"window" for a container that actually contains an X11 window (like a terminal +or browser) and "split container" for containers that consist of one or more +windows. -image:two_columns.png[Two columns] +TODO: picture of the tree + +To split a window vertically, press +mod+v+. To split it horizontally, press ++mod+h+. === Changing container modes @@ -85,15 +97,19 @@ image:modes.png[Container modes] === Toggling fullscreen mode for a window To display a window fullscreen or to go out of fullscreen mode again, press -+Mod1+f+. ++mod+f+. + +///////////////////////////////////////////////////////////////////////////// +TODO: not yet implemented There is also a global fullscreen mode in i3 in which the client will use all -available outputs. To use it, or to get out of it again, press +Mod1+Shift+f+. +available outputs. To use it, or to get out of it again, press +mod+Shift+f+. +///////////////////////////////////////////////////////////////////////////// === Opening other applications Aside from opening applications from a terminal, you can also use the handy -+dmenu+ which is opened by pressing +Mod1+v+ by default. Just type the name ++dmenu+ which is opened by pressing +mod+p+ by default. Just type the name (or a part of it) of the application which you want to open. The application typed has to be in your +$PATH+ for this to work. @@ -115,7 +131,7 @@ depends on the application. Workspaces are an easy way to group a set of windows. By default, you are on the first workspace, as the bar on the bottom left indicates. To switch to -another workspace, press +Mod1+num+ where +num+ is the number of the workspace +another workspace, press +mod+num+ where +num+ is the number of the workspace you want to use. If the workspace does not exist yet, it will be created. A common paradigm is to put the web browser on one workspace, communication @@ -129,18 +145,15 @@ focus to that screen. === Moving windows to workspaces -To move a window to another workspace, simply press +Mod1+Shift+num+ where +To move a window to another workspace, simply press +mod+Shift+num+ where +num+ is (like when switching workspaces) the number of the target workspace. Similarly to switching workspaces, the target workspace will be created if it does not yet exist. -=== Resizing columns/rows +=== Resizing -To resize columns or rows, just grab the border between the two columns/rows -and move it to the wanted size. Please keep in mind that each cell of the table -holds a +container+ and thus you cannot horizontally resize single windows. If -you need applications with different horizontal sizes, place them in seperate -cells one above the other. +The easiest way to resize a container is by using the mouse: Grab the border +and move it to the wanted size. See <> for how to configure i3 to be able to resize columns/rows with your keyboard. @@ -148,35 +161,21 @@ columns/rows with your keyboard. === Restarting i3 inplace To restart i3 inplace (and thus get into a clean state if there is a bug, or -to upgrade to a newer version of i3) you can use +Mod1+Shift+r+. Be aware, -though, that this kills your current layout and all the windows you have opened -will be put in a default container in only one cell. Saving layouts will be -implemented in a later version. +to upgrade to a newer version of i3) you can use +mod+Shift+r+. === Exiting i3 -To cleanly exit i3 without killing your X server, you can use +Mod1+Shift+e+. - -=== Snapping - -Snapping is a mechanism to increase/decrease the colspan/rowspan of a container. -Colspan/rowspan is the number of columns/rows a specific cell of the table -consumes. This is easier explained by giving an example, so take the following -layout: - -image:snapping.png[Snapping example] - -To use the full size of your screen, you can now snap container 3 downwards -by pressing +Mod1+Control+k+ (or snap container 2 rightwards). +To cleanly exit i3 without killing your X server, you can use +mod+Shift+e+. === Floating Floating mode is the opposite of tiling mode. The position and size of a window are not managed by i3, but by you. Using this mode violates the tiling paradigm but can be useful for some corner cases like "Save as" dialog -windows, or toolbar windows (GIMP or similar). +windows, or toolbar windows (GIMP or similar). Those windows usually set the +appropriate hint and are opened in floating mode by default. -You can enable floating mode for a window by pressing +Mod1+Shift+Space+. By +You can enable floating mode for a window by pressing +mod+Shift+Space+. By dragging the window’s titlebar with your mouse you can move the window around. By grabbing the borders and moving them you can resize the window. You can also do that by using the <>. @@ -185,6 +184,74 @@ For resizing floating windows with your keyboard, see <>. Floating windows are always on top of tiling windows. +== Tree + +The most important change and reason for the name is that i3 stores all +information about the X11 outputs, workspaces and layout of the windows on them +in a tree. The root node is the X11 root window, followed by the X11 outputs, +then workspaces and finally the windows themselve. In previous versions of i3 +we had multiple lists (of outputs, workspaces) and a table for each workspace. +That approach turned out to be complicated to use (snapping), understand and +implement. + +=== The tree consists of Containers + +The building blocks of our tree are so called +Containers+. A +Container+ can +host a window (meaning an X11 window, one that you can actually see and use, +like a browser). Alternatively, it could contain one or more +Containers+. A +simple example is the workspace: When you start i3 with a single monitor, a +single workspace and you open two terminal windows, you will end up with a tree +like this: + +image::tree-layout2.png["layout2",float="right"] +image::tree-shot4.png["shot4",title="Two terminals on standard workspace"] + +=== Orientation and Split Containers + +[[OrientationSplit]] + +It is only natural to use so-called +Split Containers+ in order to build a +layout when using a tree as data structure. In i3, every +Container+ has an +orientation (horizontal, vertical or unspecified). So, in our example with the +workspace, the default orientation of the workspace +Container+ is horizontal +(most monitors are widescreen nowadays). If you change the orientation to +vertical (+Alt+v+ in the default config) and *then* open two terminals, i3 will +configure your windows like this: + +image::tree-shot2.png["shot2",title="Vertical Workspace Orientation"] + +An interesting new feature of the tree branch is the ability to split anything: +Let’s assume you have two terminals on a workspace (with horizontal +orientation), focus is on the right terminal. Now you want to open another +terminal window below the current one. If you would just open a new terminal +window, it would show up to the right due to the horizontal workspace +orientation. Instead, press +Alt+v+ to create a +Vertical Split Container+ (to +open a +Horizontal Split Container+, use +Alt+h+). Now you can open a new +terminal and it will open below the current one: + +image::tree-layout1.png["Layout",float="right"] +image::tree-shot1.png["shot",title="Vertical Split Container"] + +unfloat::[] + +You probably guessed it already: There is no limit on how deep your hierarchy +of splits can be. + +=== Level up + +Let’s stay with our example from above. We have a terminal on the left and two +vertically split terminals on the right, focus is on the bottom right one. When +you open a new terminal, it will open below the current one. + +So, how can you open a new terminal window to the *right* of the current one? +The solution is to use +level up+, which will focus the +Parent Container+ of +the current +Container+. In this case, you would focus the +Vertical Split +Container+ which is *inside* the horizontally oriented workspace. Thus, now new +windows will be opened to the right of the +Vertical Split Container+: + +image::tree-shot3.png["shot3",title="Level Up, then open new terminal"] + + == Configuring i3 This is where the real fun begins ;-). Most things are very dependant on your @@ -221,6 +288,9 @@ workspace bar. You can use +xfontsel(1)+ to generate such a font description. To see special characters (Unicode), you need to use a font which supports the ISO-10646 encoding. +If i3 cannot open the configured font, it will output an error in the logfile +and fall back to a working font. + *Syntax*: ------------------------------ font @@ -308,8 +378,12 @@ floating_modifier floating_modifier Mod1 -------------------------------- +//////////////////////////////////////////////////////////////////////// === Layout mode for new containers +TODO: this is workspace_layout. but workspace_layout only works for the first +con, right? + This option determines in which mode new containers will start. See also <>. @@ -323,6 +397,7 @@ new_container stack-limit --------------------- new_container tabbed --------------------- +//////////////////////////////////////////////////////////////////////// === Border style for new windows @@ -330,12 +405,12 @@ This option determines which border style new windows will have. *Syntax*: --------------------------------------------- -new_window +new_window --------------------------------------------- *Examples*: --------------------- -new_window bp +new_window 1pixel --------------------- === Variables @@ -380,21 +455,21 @@ not be put onto any workspace, but will be set floating on the current one. *Syntax*: ------------------------------------------------------------ -assign ["]window class[/window title]["] [→] [~ | workspace] +assign ["]window class[/window title]["] [→] [workspace] ------------------------------------------------------------ *Examples*: ---------------------- assign urxvt 2 assign urxvt → 2 +assign urxvt → work assign "urxvt" → 2 assign "urxvt/VIM" → 3 -assign "gecko" → ~4 -assign "xv/MPlayer" → ~ +assign "gecko" → 4 ---------------------- Note that the arrow is not required, it just looks good :-). If you decide to -use it, it has to be a UTF-8 encoded arrow, not "->" or something like that. +use it, it has to be a UTF-8 encoded arrow, not `->` or something like that. === Automatically starting applications on i3 startup @@ -409,7 +484,7 @@ exec command *Examples*: -------------------------------- -exec sudo i3status | dzen2 -dock +exec i3status | dzen2 -dock -------------------------------- [[workspace_screen]] @@ -437,26 +512,6 @@ workspace 1 output LVDS1 workspace 5 output VGA1 --------------------------- -=== Named workspaces - -If you always have a certain arrangement of workspaces, you might want to give -them names (of course UTF-8 is supported): - -*Syntax*: ---------------------------------------- -workspace -workspace output name ---------------------------------------- - -For more details about the 'output' part of this command, see above. - -*Examples*: --------------------------- -workspace 1 www -workspace 2 work -workspace 3 i ♥ workspaces --------------------------- - === Changing colors You can change all colors which i3 uses to draw the window decorations and the @@ -549,107 +604,73 @@ focus_follows_mouse focus_follows_mouse no ---------------------- -=== Internal workspace bar - -The internal workspace bar (the thing at the bottom of your screen) is very -simple -- it does not provide a way to display custom text and it does not -offer advanced customization features. This is intended because we do not -want to duplicate functionality of tools like +dzen2+, +xmobar+ and so on -(they render bars, we manage windows). Instead, there is an option which will -turn off the internal bar completely, so that you can use a separate program to -display it (see +i3-wsbar+, a sample implementation of such a program): - -*Syntax*: ----------------------- -workspace_bar ----------------------- - -*Examples*: ----------------- -workspace_bar no ----------------- - == List of commands === Manipulating layout -To change the layout of the current container to stacking, use +s+, for default -use +d+ and for tabbed, use +T+. To make the current client (!) fullscreen, -use +f+, to make it span all outputs, use +fg+, to make it floating (or -tiling again) use +t+: +To change the layout of the current container to stacking, use +layout +stacking+, for default use +layout default+ and for tabbed, use +layout +tabbed+. To make the current client (!) fullscreen, use +fullscreen+, to make +it floating (or tiling again) use +mode floating+ respectively +mode tiling+ +(or +mode toggle+): *Examples*: -------------- -bindsym Mod1+s s -bindsym Mod1+l d -bindsym Mod1+w T +bindsym Mod1+s layout stacking +bindsym Mod1+l layout default +bindsym Mod1+w layout tabbed # Toggle fullscreen -bindsym Mod1+f f - -# Toggle global fullscreen -bindsym Mod1+Shift+f fg +bindsym Mod1+f fullscreen # Toggle floating/tiling -bindsym Mod1+t t +bindsym Mod1+t mode toggle -------------- -=== Focusing/Moving/Snapping clients/containers/screens +=== Focusing/Moving containers -To change the focus, use one of the +h+, +j+, +k+ and +l+ commands, meaning -left, down, up, right (respectively). To focus a container, prefix it with -+wc+. To focus a screen, prefix it with +ws+. +To change the focus, use one of the +prev h+, +next v+, +prev v+ and +next h+ +commands, meaning left, down, up, right (respectively). -The same principle applies for moving and snapping: just prefix the command -with +m+ when moving and with +s+ when snapping: +For moving, use +move left+, +move right+, +move down+ and +move up+. *Examples*: ---------------------- # Focus clients on the left, bottom, top, right: -bindsym Mod1+j h -bindsym Mod1+k j -bindsym Mod1+j k -bindsym Mod1+semicolon l +bindsym Mod1+j prev h +bindsym Mod1+k next v +bindsym Mod1+j prev v +bindsym Mod1+semicolon next h # Move client to the left, bottom, top, right: -bindsym Mod1+j mh -bindsym Mod1+k mj -bindsym Mod1+j mk -bindsym Mod1+semicolon ml - -# Snap client to the left, bottom, top, right: -bindsym Mod1+j sh -bindsym Mod1+k sj -bindsym Mod1+j sk -bindsym Mod1+semicolon sl - -# Focus container on the left, bottom, top, right: -bindsym Mod3+j wch -… +bindsym Mod1+j move left +bindsym Mod1+k move down +bindsym Mod1+j move up +bindsym Mod1+semicolon move right ---------------------- -=== Changing workspaces/moving clients to workspaces +=== Changing workspaces/moving containers to workspaces -To change to a specific workspace, the command is just the number of the -workspace, e.g. +1+ or +3+. To move the current client to a specific workspace, -prefix the number with an +m+. +To change to a specific workspace, use the +workspace+ command, followed by the +number or name of the workspace. To move containers, use +move workspace+. + +////////////////////////////////////////////////////////////////////////////// +TODO: not yet implemented You can also switch to the next and previous workspace with the commands +nw+ and +pw+, which is handy, for example, if you have workspace 1, 3, 4 and 9 and you want to cycle through them with a single key combination. +////////////////////////////////////////////////////////////////////////////// *Examples*: ------------------------- -bindsym Mod1+1 1 -bindsym Mod1+2 2 +bindsym Mod1+1 workspace 1 +bindsym Mod1+2 workspace 2 ... -bindsym Mod1+Shift+1 m1 -bindsym Mod1+Shift+2 m2 +bindsym Mod1+Shift+1 move workspace 1 +bindsym Mod1+Shift+2 move workspace 2 ... - -bindsym Mod1+o nw -bindsym Mod1+p pw ------------------------- [[resizingconfig]] @@ -659,6 +680,8 @@ bindsym Mod1+p pw If you want to resize columns/rows using your keyboard, you can use the +resize+ command, I recommend using it inside a so called +mode+: +/////////////////////////////////////////////////////////////////////// +TODO: mode is not yet implemented .Example: Configuration file, defining a mode for resizing ---------------------------------------------------------------------- mode "resize" { @@ -668,17 +691,17 @@ mode "resize" { # when pressing left, the window is resized so that it has # more space on its left - bindsym n resize left -10 - bindsym Shift+n resize left +10 + bindsym j resize shrink left + bindsym Shift+j resize grow left - bindsym r resize bottom +10 - bindsym Shift+r resize bottom -10 + bindsym k resize grow bottom + bindsym Shift+k resize shrink bottom - bindsym t resize top -10 - bindsym Shift+t resize top +10 + bindsym l resize shrink top + bindsym Shift+l resize grow top - bindsym d resize right +10 - bindsym Shift+d resize right -10 + bindsym semicolon resize grow right + bindsym Shift+semicolon resize shrink right bindcode 36 mode default } @@ -687,29 +710,28 @@ mode "resize" { bindsym Mod1+r mode resize ---------------------------------------------------------------------- +/////////////////////////////////////////////////////////////////////// + === Jumping to specific windows Often when in a multi-monitor environment, you want to quickly jump to a specific window. For example, while working on workspace 3 you may want to jump to your mail client to email your boss that you’ve achieved some important goal. Instead of figuring out how to navigate to your mailclient, -it would be more convenient to have a shortcut. +it would be more convenient to have a shortcut. You can use the +focus+ command +for that. *Syntax*: ---------------------------------------------------- -jump ["]window class[/window title]["] -jump workspace [ column row ] +[class="class"] focus +[title="title"] focus ---------------------------------------------------- -You can either use the same matching algorithm as in the +assign+ command -(see above) or you can specify the position of the client if you always use -the same layout. - *Examples*: --------------------------------------- +------------------------------------------------ # Get me to the next open VIM instance -bindsym Mod1+a jump "urxvt/VIM" --------------------------------------- +bindsym Mod1+a [class="urxvt" title="VIM"] focus +------------------------------------------------ === VIM-like marks (mark/goto) @@ -728,11 +750,13 @@ 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. *Syntax*: ------------------ +------------------------------ mark -goto ------------------ +[con_mark="identifier"] focus +------------------------------ +/////////////////////////////////////////////////////////////////// +TODO: make i3-input replace %s *Examples*: --------------------------------------- # Read 1 character and mark the current window with this character @@ -744,7 +768,10 @@ bindsym Mod1+g exec i3-input -p 'goto ' -l 1 -P 'Goto: ' Alternatively, if you do not want to mess with +i3-input+, you could create seperate bindings for a specific set of labels and then only use those labels. +/////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +TODO: not yet implemented === Traveling the focus stack This mechanism can be thought of as the opposite of the +jump+ command. @@ -767,29 +794,36 @@ tiling:: ft:: If the current window is floating, the next tiling window will be selected; and vice-versa. +/////////////////////////////////////////////////////////////////////////////// === Changing border style -To change the border of the current client, you can use +bn+ to use the normal -border (including window title), +bp+ to use a 1-pixel border (no window title) -and +bb+ to make the client borderless. There is also +bt+ which will toggle -the different border styles. +To change the border of the current client, you can use +border normal+ to use the normal +border (including window title), +border 1pixel+ to use a 1-pixel border (no window title) +and +border none+ to make the client borderless. + +//////////////////////////////////////////////////////////////////////////// +TODO: not yet implemented +There is also +border toggle+ which will toggle the different border styles. +//////////////////////////////////////////////////////////////////////////// *Examples*: ------------------- -bindsym Mod1+t bn -bindsym Mod1+y bp -bindsym Mod1+u bb ------------------- +---------------------------- +bindsym Mod1+t border normal +bindsym Mod1+y border 1pixel +bindsym Mod1+u border none +---------------------------- [[stack-limit]] +/////////////////////////////////////////////////////////////////////////////// +TODO: not yet implemented === Changing the stack-limit of a container If you have a single container with a lot of windows inside it (say, more than 10), the default layout of a stacking container can get a little unhandy. -Depending on your screen’s size, you might end up seeing only half of the -titlebars for each window in the container. +Depending on your screen’s size, you might end up with only half of the title +lines being actually used, wasting a lot of screen space. Using the +stack-limit+ command, you can limit the number of rows or columns in a stacking container. i3 will create columns or rows (depending on what @@ -810,6 +844,7 @@ stack-limit rows 5 ------------------- image:stacklimit.png[Container limited to two columns] +/////////////////////////////////////////////////////////////////////////////// === Reloading/Restarting/Exiting From e29891bdd48662fb1fdda787e3c3aa4e1d581105 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 28 May 2011 22:12:06 +0200 Subject: [PATCH 652/867] makefile: include yajl-fallback in 'make dist' --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6e032f41..4e2761a9 100644 --- a/Makefile +++ b/Makefile @@ -80,7 +80,7 @@ dist: distclean [ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2 mkdir i3-${VERSION} cp DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen Makefile i3-${VERSION} - cp -r src i3-msg include man i3-${VERSION} + cp -r src i3-msg yajl-fallback include man i3-${VERSION} # Only copy toplevel documentation (important stuff) mkdir i3-${VERSION}/docs find docs -maxdepth 1 -type f ! -name "*.xcf" -exec cp '{}' i3-${VERSION}/docs \; From bbddacd3361d057394655d2f44baa474e1d6de10 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 28 May 2011 22:12:47 +0200 Subject: [PATCH 653/867] add release notes for tree-pr3 --- RELEASE-NOTES-tree-pr1 | 45 ++++++++++++++++++++++++++++++++++++++++++ RELEASE-NOTES-tree-pr2 | 43 ++++++++++++++++++++++++++++++++++++++++ RELEASE-NOTES-tree-pr3 | 43 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+) create mode 100644 RELEASE-NOTES-tree-pr1 create mode 100644 RELEASE-NOTES-tree-pr2 create mode 100644 RELEASE-NOTES-tree-pr3 diff --git a/RELEASE-NOTES-tree-pr1 b/RELEASE-NOTES-tree-pr1 new file mode 100644 index 00000000..dec817fa --- /dev/null +++ b/RELEASE-NOTES-tree-pr1 @@ -0,0 +1,45 @@ +Release notes for i3 tree-pr1 +----------------------------- + +This is a PREVIEW RELEASE for the tree branch. It is *NOT* part of i3’s regular +releases and should *NOT* be packaged in the usual way for distributions. +Instead, provide a separate, unofficial package if possible. + +The so called tree branch is the place where the next version of i3 is +developed. This time, we did a major code refactoring bringing many changes. +The idea is to use a tree as datastructure instead of separate lists (like one +for outputs, workspaces and a table for storing your window layout). + +Quite a few advantages arise from this new data structure. The most prominent +ones will be a slightly different look and feel, the possibility to store your +layout and restore it later, correct resizing, a much cleaner command parser +and more little improvements. + +As this is a preview release, some things are not working yet. Generally, +though, the core developers are using it already and think it’s good enough to +try it out. With this release, we want to gather feedback from you, so please +report any bugs you encounter in our bugtracker at http://i3.zekjur.net/bugs + +What should be working in this release? +--------------------------------------- + + • Basic window management, navigation, moving + • Fullscreen mode, correct aspect ratio + • Stacked/Tabbed layout, floating mode + • Splitting (for fancy layouts), resizing + • Restarts, preserving the layout + • i3bar, get it from http://git.merovius.de/ + +If any of these features do not work (correctly), please file a bugreport. + +What is not working in this release? +------------------------------------ + + • RandR changes (i3 needs to be restarted) + • Assignments + • Configfile compatibility + • Workspace switching is sometimes not working. If you find a pattern, please + report it. + • There are still some bugs in resizing. Please report! + + -- Michael Stapelberg, 2010-12-06 diff --git a/RELEASE-NOTES-tree-pr2 b/RELEASE-NOTES-tree-pr2 new file mode 100644 index 00000000..533c3c76 --- /dev/null +++ b/RELEASE-NOTES-tree-pr2 @@ -0,0 +1,43 @@ +Release notes for i3 tree-pr2 +----------------------------- + +This is the second PREVIEW RELEASE for the tree branch. It is *NOT* part of +i3’s regular releases and should *NOT* be packaged in the usual way for +distributions. Instead, provide a separate, unofficial package if possible. + +The so called tree branch is the place where the next version of i3 is +developed. This time, we did a major code refactoring bringing many changes. +The idea is to use a tree as datastructure instead of separate lists (like one +for outputs, workspaces and a table for storing your window layout). + +Quite a few advantages arise from this new data structure. The most prominent +ones will be a slightly different look and feel, the possibility to store your +layout and restore it later, correct resizing, a much cleaner command parser +and more little improvements. + +As this is a preview release, some things are not working yet. Generally, +though, the core developers are using it already and think it’s good enough to +try it out. With this release, we want to gather feedback from you, so please +report any bugs you encounter in our bugtracker at http://i3.zekjur.net/bugs + +What should be working in this release? +--------------------------------------- + + • Basic window management, navigation, moving + • Fullscreen mode, correct aspect ratio + • Stacked/Tabbed layout, floating mode + • Splitting (for fancy layouts), resizing + • Restarts (preserving the layout), crash handler + • RandR changes, keyboard layout changes + • Dock clients + • i3bar, get it from http://git.merovius.de/ + +If any of these features do not work (correctly), please file a bugreport. + +What is not working in this release? +------------------------------------ + + • Assignments + • Configfile compatibility + + -- Michael Stapelberg, 2011-03-07 diff --git a/RELEASE-NOTES-tree-pr3 b/RELEASE-NOTES-tree-pr3 new file mode 100644 index 00000000..fd8f4c3f --- /dev/null +++ b/RELEASE-NOTES-tree-pr3 @@ -0,0 +1,43 @@ +Release notes for i3 tree-pr3 +----------------------------- + +This is the third PREVIEW RELEASE for the tree branch. It is *NOT* part of +i3’s regular releases and should *NOT* be packaged in the usual way for +distributions. Instead, provide a separate, unofficial package if possible. + +The so called tree branch is the place where the next version of i3 is +developed. This time, we did a major code refactoring bringing many changes. +The idea is to use a tree as datastructure instead of separate lists (like one +for outputs, workspaces and a table for storing your window layout). + +Quite a few advantages arise from this new data structure. The most prominent +ones will be a slightly different look and feel, the possibility to store your +layout and restore it later, correct resizing, a much cleaner command parser +and more little improvements. + +As this is a preview release, some things are not working yet. Generally, +though, the core developers are using it already and think it’s good enough to +try it out. With this release, we want to gather feedback from you, so please +report any bugs you encounter in our bugtracker at http://bugs.i3wm.org/ + +What should be working in this release? +--------------------------------------- + + • Basic window management, navigation, moving + • Fullscreen mode, correct aspect ratio + • Stacked/Tabbed layout, floating mode + • Splitting (for fancy layouts), resizing + • Restarts (preserving the layout), crash handler + • RandR changes, keyboard layout changes + • Dock clients + • Assignments + • i3bar, get it from http://git.merovius.de/ + +If any of these features do not work (correctly), please file a bugreport. + +What is not working in this release? +------------------------------------ + + • Configfile compatibility + + -- Michael Stapelberg, 2011-05-28 From b49874dcb8d2b0aaa603c9b4fb2f170b956ce613 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 29 May 2011 11:31:22 +0200 Subject: [PATCH 654/867] x: first create/render pixmap, then change window sizes (reduces flickering for new windows) Especially in stacked cons. --- src/x.c | 55 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/src/x.c b/src/x.c index f37ef933..b166d682 100644 --- a/src/x.c +++ b/src/x.c @@ -452,6 +452,26 @@ update_pixmaps: xcb_clear_area(conn, false, parent->frame, 0, 0, parent->rect.width, parent->rect.height); } +/* + * Recursively calls x_draw_decoration. This cannot be done in x_push_node + * because x_push_node uses focus order to recurse (see the comment above) + * while drawing the decoration needs to happen in the actual order. + * + */ +static void x_deco_recurse(Con *con) { + Con *current; + + TAILQ_FOREACH(current, &(con->nodes_head), nodes) + x_deco_recurse(current); + + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) + x_deco_recurse(current); + + if ((con->type != CT_ROOT && con->type != CT_OUTPUT) && + con->mapped) + x_draw_decoration(con); +} + /* * This function pushes the properties of each node of the layout tree to * X11 if they have changed (like the map state, position of the window, …). @@ -522,8 +542,9 @@ void x_push_node(Con *con) { bool fake_notify = false; /* set new position if rect changed */ if (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0) { - DLOG("setting rect (%d, %d, %d, %d)\n", rect.x, rect.y, rect.width, rect.height); - xcb_set_window_rect(conn, con->frame, rect); + /* We first create the new pixmap, then render to it, set it as the + * background and only afterwards change the window size. This reduces + * flickering. */ /* As the pixmap only depends on the size and not on the position, it * is enough to check if width/height have changed. Also, we don’t @@ -542,10 +563,20 @@ void x_push_node(Con *con) { } xcb_create_pixmap(conn, root_depth, con->pixmap, con->frame, rect.width, rect.height); xcb_create_gc(conn, con->pm_gc, con->pixmap, 0, 0); + + /* Render the decoration now to make the correct decoration visible + * from the very first moment. Later calls will be cached, so this + * doesn’t hurt performance. */ + x_deco_recurse(con); + uint32_t values[] = { con->pixmap }; xcb_change_window_attributes(conn, con->frame, XCB_CW_BACK_PIXMAP, values); con->pixmap_recreated = true; } + + DLOG("setting rect (%d, %d, %d, %d)\n", rect.x, rect.y, rect.width, rect.height); + xcb_set_window_rect(conn, con->frame, rect); + memcpy(&(state->rect), &rect, sizeof(Rect)); fake_notify = true; } @@ -603,26 +634,6 @@ void x_push_node(Con *con) { x_push_node(current); } -/* - * Recursively calls x_draw_decoration. This cannot be done in x_push_node - * because x_push_node uses focus order to recurse (see the comment above) - * while drawing the decoration needs to happen in the actual order. - * - */ -static void x_deco_recurse(Con *con) { - Con *current; - - TAILQ_FOREACH(current, &(con->nodes_head), nodes) - x_deco_recurse(current); - - TAILQ_FOREACH(current, &(con->floating_head), floating_windows) - x_deco_recurse(current); - - if ((con->type != CT_ROOT && con->type != CT_OUTPUT) && - con->mapped) - x_draw_decoration(con); -} - /* * Same idea as in x_push_node(), but this function only unmaps windows. It is * necessary to split this up to handle new fullscreen clients properly: The From f680c8841f88da435ca0a48329b4a22aba0853d2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 29 May 2011 11:46:01 +0200 Subject: [PATCH 655/867] x: only re-render the tree in handle_normal_hints when they actually changed --- src/handlers.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index b87bb418..aceb5b70 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -739,6 +739,7 @@ static int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state con->base_height = base_height; DLOG("client's base_height changed to %d\n", base_height); DLOG("client's base_width changed to %d\n", base_width); + changed = true; } /* If no aspect ratio was set or if it was invalid, we ignore the hints */ @@ -764,15 +765,24 @@ static int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state /* Check if we need to set proportional_* variables using the correct ratio */ if ((width / height) < min_aspect) { - con->proportional_width = width; - con->proportional_height = width / min_aspect; + if (con->proportional_width != width || + con->proportional_height != (width / min_aspect)) { + con->proportional_width = width; + con->proportional_height = width / min_aspect; + changed = true; + } } else if ((width / height) > max_aspect) { - con->proportional_width = width; - con->proportional_height = width / max_aspect; + if (con->proportional_width != width || + con->proportional_height != (width / max_aspect)) { + con->proportional_width = width; + con->proportional_height = width / max_aspect; + changed = true; + } } else goto render_and_return; render_and_return: - tree_render(); + if (changed) + tree_render(); return 1; } From 446c9b7313f07bd76c1a31b5c0ecf2fd998a047e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 29 May 2011 12:20:09 +0200 Subject: [PATCH 656/867] Bugfix: Set pixmap_recreated before rendering the decoration --- src/x.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/x.c b/src/x.c index b166d682..8aa05f91 100644 --- a/src/x.c +++ b/src/x.c @@ -507,7 +507,7 @@ void x_push_node(Con *con) { } rect.height = max_y + max_height; if (rect.height == 0) { - DLOG("Unmapping container because it does not contain anything atm.\n"); + DLOG("Unmapping container %p because it does not contain anything.\n", con); con->mapped = false; } } @@ -553,7 +553,9 @@ void x_push_node(Con *con) { if (rect.height > 0 && (state->rect.width != rect.width || state->rect.height != rect.height)) { - DLOG("CACHE: creating new pixmap\n"); + DLOG("CACHE: creating new pixmap for con %p (old: %d x %d, new: %d x %d)\n", + con, state->rect.width, state->rect.height, + rect.width, rect.height); if (con->pixmap == 0) { con->pixmap = xcb_generate_id(conn); con->pm_gc = xcb_generate_id(conn); @@ -564,6 +566,8 @@ void x_push_node(Con *con) { xcb_create_pixmap(conn, root_depth, con->pixmap, con->frame, rect.width, rect.height); xcb_create_gc(conn, con->pm_gc, con->pixmap, 0, 0); + con->pixmap_recreated = true; + /* Render the decoration now to make the correct decoration visible * from the very first moment. Later calls will be cached, so this * doesn’t hurt performance. */ @@ -571,7 +575,6 @@ void x_push_node(Con *con) { uint32_t values[] = { con->pixmap }; xcb_change_window_attributes(conn, con->frame, XCB_CW_BACK_PIXMAP, values); - con->pixmap_recreated = true; } DLOG("setting rect (%d, %d, %d, %d)\n", rect.x, rect.y, rect.width, rect.height); From 8acea3d34c2a663f9a617b6e119b73519fae3195 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 29 May 2011 12:29:49 +0200 Subject: [PATCH 657/867] call tree_render() only after commands which require it Saves one call of tree_render for exec, for example --- src/cmdparse.y | 32 ++++++++++++++++++++++++++++++++ src/handlers.c | 1 - src/ipc.c | 1 - 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index b247d59b..3770028b 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -397,6 +397,8 @@ focus: LOG("focusing %p / %s\n", current->con, current->con->name); con_focus(current->con); } + + tree_render(); } ; @@ -416,6 +418,7 @@ kill: } } + tree_render(); } ; @@ -431,6 +434,8 @@ workspace: printf("should switch to workspace %s\n", $2); workspace_show($2); free($2); + + tree_render(); } ; @@ -441,6 +446,8 @@ open: Con *con = tree_open_con(NULL); con_focus(con); asprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con); + + tree_render(); } ; @@ -457,6 +464,8 @@ fullscreen: printf("matching: %p / %s\n", current->con, current->con->name); con_toggle_fullscreen(current->con); } + + tree_render(); } ; @@ -466,6 +475,8 @@ next: /* TODO: use matches */ printf("should select next window in direction %c\n", $2); tree_next('n', ($2 == 'v' ? VERT : HORIZ)); + + tree_render(); } ; @@ -475,6 +486,8 @@ prev: /* TODO: use matches */ printf("should select prev window in direction %c\n", $2); tree_next('p', ($2 == 'v' ? VERT : HORIZ)); + + tree_render(); } ; @@ -484,6 +497,8 @@ split: /* TODO: use matches */ printf("splitting in direction %c\n", $2); tree_split(focused, ($2 == 'v' ? VERT : HORIZ)); + + tree_render(); } ; @@ -514,6 +529,8 @@ mode: } } } + + tree_render(); } ; @@ -535,6 +552,8 @@ border: printf("matching: %p / %s\n", current->con, current->con->name); current->con->border_style = $2; } + + tree_render(); } ; @@ -552,6 +571,8 @@ level: if ($2 == 'u') level_up(); else level_down(); + + tree_render(); } ; @@ -565,6 +586,8 @@ move: { printf("moving in direction %d\n", $2); tree_move($2); + + tree_render(); } | TOK_MOVE TOK_WORKSPACE STR { @@ -581,6 +604,8 @@ move: printf("matching: %p / %s\n", current->con, current->con->name); con_move_to_workspace(current->con, ws); } + + tree_render(); } ; @@ -590,6 +615,7 @@ restore: printf("restoring \"%s\"\n", $2); tree_append_json($2); free($2); + tree_render(); } ; @@ -608,6 +634,8 @@ layout: con_set_layout(current->con, $2); } } + + tree_render(); } ; @@ -631,6 +659,8 @@ mark: } free($2); + + tree_render(); } ; @@ -699,6 +729,8 @@ resize: LOG("focused->percent after = %f\n", focused->percent); LOG("other->percent after = %f\n", other->percent); } + + tree_render(); } ; diff --git a/src/handlers.c b/src/handlers.c index aceb5b70..eaf71ddb 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -106,7 +106,6 @@ static int handle_key_press(xcb_key_press_event_t *event) { } parse_cmd(bind->command); - tree_render(); return 1; } diff --git a/src/ipc.c b/src/ipc.c index 0535d122..bdf1dfea 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -148,7 +148,6 @@ IPC_HANDLER(command) { strncpy(command, (const char*)message, message_size); LOG("IPC: received: *%s*\n", command); const char *reply = parse_cmd((const char*)command); - tree_render(); free(command); /* If no reply was provided, we just use the default success message */ From 18ec15b0f9caab845ea40de5e9e23293499e1533 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 29 May 2011 12:32:01 +0200 Subject: [PATCH 658/867] x: fix race condition where the new event mask was not set directly after reparenting --- src/manage.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/manage.c b/src/manage.c index 15c1cdbc..fa948bec 100644 --- a/src/manage.c +++ b/src/manage.c @@ -322,6 +322,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki mask = XCB_CW_EVENT_MASK; values[0] = CHILD_EVENT_MASK; xcb_change_window_attributes(conn, window, mask, values); + xcb_flush(conn); reply = xcb_get_property_reply(conn, state_cookie, NULL); if (xcb_reply_contains_atom(reply, A__NET_WM_STATE_FULLSCREEN)) From 1fc15d270e1a9805fd38d5515c5ae89e542d7c90 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 29 May 2011 13:03:36 +0200 Subject: [PATCH 659/867] Bugfix: rendering cache also needs to consider con->pixmap_recreated for borders --- src/x.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/x.c b/src/x.c index 8aa05f91..0228cd23 100644 --- a/src/x.c +++ b/src/x.c @@ -235,7 +235,7 @@ void x_window_kill(xcb_window_t window, kill_window_t kill_window) { * */ void x_draw_decoration(Con *con) { - const Con *parent = con->parent; + Con *parent = con->parent; /* This code needs to run for: * • leaf containers * • non-leaf containers which are in a stacked/tabbed container @@ -282,7 +282,8 @@ void x_draw_decoration(Con *con) { if (con->deco_render_params != NULL && (con->window == NULL || !con->window->name_x_changed) && - !con->parent->pixmap_recreated && + !parent->pixmap_recreated && + !con->pixmap_recreated && memcmp(p, con->deco_render_params, sizeof(struct deco_render_params)) == 0) { DLOG("CACHE HIT, not re-rendering\n"); free(p); @@ -302,11 +303,12 @@ void x_draw_decoration(Con *con) { if (con->window != NULL && con->window->name_x_changed) con->window->name_x_changed = false; - con->parent->pixmap_recreated = false; + parent->pixmap_recreated = false; + con->pixmap_recreated = false; /* If the con is in fullscreen mode, the decoration height we work with is set to 0 */ Rect deco_rect = con->deco_rect; - if (con_get_fullscreen_con(con->parent) == con) + if (con_get_fullscreen_con(parent) == con) deco_rect.height = 0; /* 2: draw the client.background, but only for the parts around the client_rect */ @@ -411,7 +413,7 @@ void x_draw_decoration(Con *con) { int indent_level = 0, indent_mult = 0; - Con *il_parent = con->parent; + Con *il_parent = parent; if (il_parent->layout != L_STACKED) { while (1) { DLOG("il_parent = %p, layout = %d\n", il_parent, il_parent->layout); From f007e3621d5126bc0687fd5fd8929dc9d46225af Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 29 May 2011 13:20:12 +0200 Subject: [PATCH 660/867] testcase: correct window names (trivial) --- testcases/t/35-floating-focus.t | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testcases/t/35-floating-focus.t b/testcases/t/35-floating-focus.t index 83c1c90c..d037b9db 100644 --- a/testcases/t/35-floating-focus.t +++ b/testcases/t/35-floating-focus.t @@ -59,9 +59,9 @@ is($x->input_focus, $second->id, 'second con still focused after killing third') $tmp = fresh_workspace; -$first = open_standard_window($x); # window 2 -$second = open_standard_window($x); # window 3 -my $third = open_standard_window($x); # window 4 +$first = open_standard_window($x); # window 5 +$second = open_standard_window($x); # window 6 +my $third = open_standard_window($x); # window 7 is($x->input_focus, $third->id, 'last container focused'); From 97e45b9cfcaf34412b3e582ab05cf9f1d7b8bd77 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 29 May 2011 14:39:41 +0200 Subject: [PATCH 661/867] Bugfix: RandR: Correctly assign focused workspaces to outputs Fixes: #399 --- src/randr.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/randr.c b/src/randr.c index a6b84ddc..bf02518f 100644 --- a/src/randr.c +++ b/src/randr.c @@ -306,11 +306,31 @@ void init_ws_for_output(Output *output, Con *content) { /* if so, move it over */ LOG("Moving workspace \"%s\" from output \"%s\" to \"%s\" due to assignment\n", workspace->name, workspace_out->name, output->name); - DLOG("Detaching workspace = %p / %s\n", workspace, workspace->name); + + /* if the workspace is currently visible on that output, we need to + * switch to a different workspace - otherwise the output would end up + * with no active workspace */ + bool visible = workspace_is_visible(workspace); + Con *previous = NULL; + if (visible && (previous = TAILQ_NEXT(workspace, focused))) { + LOG("Switching to previously used workspace \"%s\" on output \"%s\"\n", + previous->name, workspace_out->name); + workspace_show(previous->name); + } + con_detach(workspace); - DLOG("Re-attaching current = %p / %s\n", workspace, workspace->name); con_attach(workspace, content, false); - DLOG("Done, next\n"); + + /* In case the workspace we just moved was visible but there was no + * other workspace to switch to, we need to initialize the source + * output aswell */ + if (visible && previous == NULL) { + LOG("There is no workspace left on \"%s\", re-initializing\n", + workspace_out->name); + init_ws_for_output(get_output_by_name(workspace_out->name), + output_get_content(workspace_out)); + DLOG("Done re-initializing, continuing with \"%s\"\n", output->name); + } } /* if a workspace exists, we are done now */ From 51bfdbf0a840833830030c1752a4ea6d414b9347 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 2 Jun 2011 17:12:18 +0200 Subject: [PATCH 662/867] ipc: make 'layout' a string --- src/ipc.c | 18 +++++++++++++++++- src/load_layout.c | 16 ++++++++++++++++ testcases/t/16-nestedcons.t | 2 +- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index bdf1dfea..fa3513bc 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -202,7 +202,23 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { y(integer, (con == focused)); ystr("layout"); - y(integer, con->layout); + switch (con->layout) { + case L_DEFAULT: + ystr("default"); + break; + case L_STACKED: + ystr("stacked"); + break; + case L_TABBED: + ystr("tabbed"); + break; + case L_DOCKAREA: + ystr("dockarea"); + break; + case L_OUTPUT: + ystr("output"); + break; + } ystr("border"); switch (con->border_style) { diff --git a/src/load_layout.c b/src/load_layout.c index 7e57e94d..6fe0a086 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -131,6 +131,21 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) { json_node->border_style = BS_NORMAL; else LOG("Unhandled \"border\": %s\n", buf); free(buf); + } else if (strcasecmp(last_key, "layout") == 0) { + char *buf = NULL; + asprintf(&buf, "%.*s", (int)len, val); + if (strcasecmp(buf, "default") == 0) + json_node->layout = L_DEFAULT; + else if (strcasecmp(buf, "stacked") == 0) + json_node->layout = L_STACKED; + else if (strcasecmp(buf, "tabbed") == 0) + json_node->layout = L_TABBED; + else if (strcasecmp(buf, "dockarea") == 0) + json_node->layout = L_DOCKAREA; + else if (strcasecmp(buf, "output") == 0) + json_node->layout = L_OUTPUT; + else LOG("Unhandled \"layout\": %s\n", buf); + free(buf); } } return 1; @@ -142,6 +157,7 @@ static int json_int(void *ctx, long long val) { static int json_int(void *ctx, long val) { #endif LOG("int %d for key %s\n", val, last_key); + // TODO: remove this after the next preview release if (strcasecmp(last_key, "layout") == 0) { json_node->layout = val; } diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t index 954732dd..8d25482b 100644 --- a/testcases/t/16-nestedcons.t +++ b/testcases/t/16-nestedcons.t @@ -26,7 +26,7 @@ my $expected = { geometry => ignore(), swallows => ignore(), percent => 0, - layout => 0, + layout => 'default', focus => ignore(), focused => 0, urgent => 0, From 1585d942eac06ddf141c479c90262d0bae4f8cc4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 2 Jun 2011 17:21:38 +0200 Subject: [PATCH 663/867] Make workspace_layout handle all cons at workspace level, not only the first one (+test) This makes opening new windows on workspace level and moving windows to the right/left more like in the old i3. --- include/con.h | 2 +- include/tree.h | 2 +- include/workspace.h | 12 +++ src/cmdparse.y | 2 +- src/con.c | 43 +++++---- src/floating.c | 4 +- src/load_layout.c | 4 +- src/manage.c | 8 +- src/move.c | 20 ++++- src/randr.c | 10 +-- src/tree.c | 10 +-- src/workspace.c | 46 +++++++++- testcases/t/67-workspace_layout.t | 139 ++++++++++++++++++++++++++++++ 13 files changed, 262 insertions(+), 40 deletions(-) create mode 100644 testcases/t/67-workspace_layout.t diff --git a/include/con.h b/include/con.h index 1dd65a6e..37bca3bb 100644 --- a/include/con.h +++ b/include/con.h @@ -7,7 +7,7 @@ * X11 IDs using x_con_init(). * */ -Con *con_new(Con *parent); +Con *con_new(Con *parent, i3Window *window); /** * Sets input focus to the given container. Will be updated in X11 in the next diff --git a/include/tree.h b/include/tree.h index 98358edd..b66aa3f7 100644 --- a/include/tree.h +++ b/include/tree.h @@ -24,7 +24,7 @@ void tree_init(); * Opens an empty container in the current container * */ -Con *tree_open_con(Con *con); +Con *tree_open_con(Con *con, i3Window *window); /** * Splits (horizontally or vertically) the given container by creating a new diff --git a/include/workspace.h b/include/workspace.h index 2132a792..367c150e 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -109,4 +109,16 @@ void workspace_update_urgent_flag(Con *ws); */ void ws_force_orientation(Con *ws, orientation_t orientation); +/** + * Called when a new con (with a window, not an empty or split con) should be + * attached to the workspace (for example when managing a new window or when + * moving an existing window to the workspace level). + * + * Depending on the workspace_layout setting, this function either returns the + * workspace itself (default layout) or creates a new stacked/tabbed con and + * returns that. + * + */ +Con *workspace_attach_to(Con *ws); + #endif diff --git a/src/cmdparse.y b/src/cmdparse.y index 3770028b..f3475d9d 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -443,7 +443,7 @@ open: TOK_OPEN { printf("opening new container\n"); - Con *con = tree_open_con(NULL); + Con *con = tree_open_con(NULL, NULL); con_focus(con); asprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con); diff --git a/src/con.c b/src/con.c index 45021f5f..1a45076a 100644 --- a/src/con.c +++ b/src/con.c @@ -32,11 +32,12 @@ static void con_on_remove_child(Con *con); * X11 IDs using x_con_init(). * */ -Con *con_new(Con *parent) { +Con *con_new(Con *parent, i3Window *window) { Con *new = scalloc(sizeof(Con)); new->on_remove_child = con_on_remove_child; TAILQ_INSERT_TAIL(&all_cons, new, all_cons); new->type = CT_CON; + new->window = window; new->border_style = config.default_border; static int cnt = 0; DLOG("opening window %d\n", cnt); @@ -59,19 +60,8 @@ Con *con_new(Con *parent) { TAILQ_INIT(&(new->focus_head)); TAILQ_INIT(&(new->swallow_head)); - if (parent != NULL) { - /* Set layout of ws if this is the first child of the ws and the user - * wanted something different than the default layout. */ - if (parent->type == CT_WORKSPACE && - con_is_leaf(parent) && - config.default_layout != L_DEFAULT) { - con_set_layout(new, config.default_layout); - con_attach(new, parent, false); - con_set_layout(parent, config.default_layout); - } else { - con_attach(new, parent, false); - } - } + if (parent != NULL) + con_attach(new, parent, false); return new; } @@ -91,6 +81,7 @@ void con_attach(Con *con, Con *parent, bool ignore_focus) { Con *loop; Con *current = NULL; struct nodes_head *nodes_head = &(parent->nodes_head); + struct focus_head *focus_head = &(parent->focus_head); /* Workspaces are handled differently: they need to be inserted at the * right position. */ @@ -134,6 +125,26 @@ void con_attach(Con *con, Con *parent, bool ignore_focus) { } } + /* When the container is not a split container (but contains a window) + * and is attached to a workspace, we check if the user configured a + * workspace_layout. This is done in workspace_attach_to, which will + * provide us with the container to which we should attach (either the + * workspace or a new split container with the configured + * workspace_layout). + */ + if (con->window != NULL && parent->type == CT_WORKSPACE) { + DLOG("Parent is a workspace. Applying default layout...\n"); + Con *target = workspace_attach_to(parent); + + /* Attach the original con to this new split con instead */ + nodes_head = &(target->nodes_head); + focus_head = &(target->focus_head); + con->parent = target; + current = NULL; + + DLOG("done\n"); + } + /* Insert the container after the tiling container, if found. * When adding to a CT_OUTPUT, just append one after another. */ if (current && parent->type != CT_OUTPUT) { @@ -147,7 +158,7 @@ add_to_focus_head: /* We insert to the TAIL because con_focus() will correct this. * This way, we have the option to insert Cons without having * to focus them. */ - TAILQ_INSERT_TAIL(&(parent->focus_head), con, focused); + TAILQ_INSERT_TAIL(focus_head, con, focused); } /* @@ -818,7 +829,7 @@ void con_set_layout(Con *con, int layout) { if (con->type == CT_WORKSPACE) { DLOG("Creating new split container\n"); /* 1: create a new split container */ - Con *new = con_new(NULL); + Con *new = con_new(NULL, NULL); new->parent = con; /* 2: set the requested layout on the split con */ diff --git a/src/floating.c b/src/floating.c index a60297fc..0cbe4871 100644 --- a/src/floating.c +++ b/src/floating.c @@ -35,7 +35,7 @@ void floating_enable(Con *con, bool automatic) { return; } /* TODO: refactor this with src/con.c:con_set_layout */ - Con *new = con_new(NULL); + Con *new = con_new(NULL, NULL); new->parent = con; new->orientation = con->orientation; @@ -76,7 +76,7 @@ void floating_enable(Con *con, bool automatic) { /* 2: create a new container to render the decoration on, add * it as a floating window to the workspace */ - Con *nc = con_new(NULL); + Con *nc = con_new(NULL, NULL); /* we need to set the parent afterwards instead of passing it as an * argument to con_new() because nc would be inserted into the tiling layer * otherwise. */ diff --git a/src/load_layout.c b/src/load_layout.c index 6fe0a086..6e311f1f 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -32,12 +32,12 @@ static int json_start_map(void *ctx) { if (last_key && strcasecmp(last_key, "floating_nodes") == 0) { DLOG("New floating_node\n"); Con *ws = con_get_workspace(json_node); - json_node = con_new(NULL); + json_node = con_new(NULL, NULL); json_node->parent = ws; DLOG("Parent is workspace = %p\n", ws); } else { Con *parent = json_node; - json_node = con_new(NULL); + json_node = con_new(NULL, NULL); json_node->parent = parent; } } diff --git a/src/manage.c b/src/manage.c index fa948bec..3c6f48a6 100644 --- a/src/manage.c +++ b/src/manage.c @@ -219,8 +219,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki nc = con_descend_focused(workspace_get(assignment->dest.workspace, NULL)); DLOG("focused on ws %s: %p / %s\n", assignment->dest.workspace, nc, nc->name); if (nc->type == CT_WORKSPACE) - nc = tree_open_con(nc); - else nc = tree_open_con(nc->parent); + nc = tree_open_con(nc, cwindow); + else nc = tree_open_con(nc->parent, cwindow); } /* TODO: handle assignments with type == A_TO_OUTPUT */ } else { @@ -229,13 +229,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki LOG("using current container, focused = %p, focused->name = %s\n", focused, focused->name); nc = focused; - } else nc = tree_open_con(NULL); + } else nc = tree_open_con(NULL, cwindow); } } else { /* M_BELOW inserts the new window as a child of the one which was * matched (e.g. dock areas) */ if (match != NULL && match->insert_where == M_BELOW) { - nc = tree_open_con(nc); + nc = tree_open_con(nc, cwindow); } } diff --git a/src/move.c b/src/move.c index caa23eaf..37fc0d34 100644 --- a/src/move.c +++ b/src/move.c @@ -22,6 +22,23 @@ static void insert_con_into(Con *con, Con *target, position_t position) { con_detach(con); con_fix_percent(con->parent); + /* When moving to a workspace, we respect the user’s configured + * workspace_layout */ + if (parent->type == CT_WORKSPACE) { + Con *split = workspace_attach_to(parent); + if (split != parent) { + DLOG("Got a new split con, using that one instead\n"); + con->parent = split; + con_attach(con, split, false); + DLOG("attached\n"); + con->percent = 0.0; + con_fix_percent(split); + con = split; + DLOG("ok, continuing with con %p instead\n", con); + con_detach(con); + } + } + con->parent = parent; if (position == BEFORE) { @@ -142,7 +159,8 @@ void tree_move(int direction) { } while (same_orientation == NULL); /* this time, we have to move to another container */ - /* This is the container *above* 'con' which is inside 'same_orientation' */ + /* This is the container *above* 'con' (an ancestor of con) which is inside + * 'same_orientation' */ Con *above = con; while (above->parent != same_orientation) above = above->parent; diff --git a/src/randr.c b/src/randr.c index bf02518f..4a33458c 100644 --- a/src/randr.c +++ b/src/randr.c @@ -193,7 +193,7 @@ void output_init_con(Output *output) { } if (con == NULL) { - con = con_new(croot); + con = con_new(croot, NULL); FREE(con->name); con->name = sstrdup(output->name); con->type = CT_OUTPUT; @@ -213,7 +213,7 @@ void output_init_con(Output *output) { } DLOG("Changing layout, adding top/bottom dockarea\n"); - Con *topdock = con_new(NULL); + Con *topdock = con_new(NULL, NULL); topdock->type = CT_DOCKAREA; topdock->layout = L_DOCKAREA; topdock->orientation = VERT; @@ -235,7 +235,7 @@ void output_init_con(Output *output) { /* content container */ DLOG("adding main content container\n"); - Con *content = con_new(NULL); + Con *content = con_new(NULL, NULL); content->type = CT_CON; content->name = sstrdup("content"); @@ -245,7 +245,7 @@ void output_init_con(Output *output) { con_attach(content, con, false); /* bottom dock container */ - Con *bottomdock = con_new(NULL); + Con *bottomdock = con_new(NULL, NULL); bottomdock->type = CT_DOCKAREA; bottomdock->layout = L_DOCKAREA; bottomdock->orientation = VERT; @@ -363,7 +363,7 @@ void init_ws_for_output(Output *output, Con *content) { DLOG("Now adding a workspace\n"); /* add a workspace to this output */ - Con *ws = con_new(NULL); + Con *ws = con_new(NULL, NULL); ws->type = CT_WORKSPACE; /* get the next unused workspace number */ diff --git a/src/tree.c b/src/tree.c index 336db4fa..a32723df 100644 --- a/src/tree.c +++ b/src/tree.c @@ -23,7 +23,7 @@ bool tree_restore(const char *path) { } /* TODO: refactor the following */ - croot = con_new(NULL); + croot = con_new(NULL, NULL); focused = croot; tree_append_json(globbed); @@ -45,7 +45,7 @@ bool tree_restore(const char *path) { * */ void tree_init() { - croot = con_new(NULL); + croot = con_new(NULL, NULL); FREE(croot->name); croot->name = "root"; croot->type = CT_ROOT; @@ -55,7 +55,7 @@ void tree_init() { * Opens an empty container in the current container * */ -Con *tree_open_con(Con *con) { +Con *tree_open_con(Con *con, i3Window *window) { if (con == NULL) { /* every focusable Con has a parent (outputs have parent root) */ con = focused->parent; @@ -79,7 +79,7 @@ Con *tree_open_con(Con *con) { assert(con != NULL); /* 3. create the container and attach it to its parent */ - Con *new = con_new(con); + Con *new = con_new(con, window); /* 4: re-calculate child->percent for each child */ con_fix_percent(con); @@ -265,7 +265,7 @@ void tree_split(Con *con, orientation_t orientation) { DLOG("Splitting in orientation %d\n", orientation); /* 2: replace it with a new Con */ - Con *new = con_new(NULL); + Con *new = con_new(NULL, NULL); TAILQ_REPLACE(&(parent->nodes_head), con, new, nodes); TAILQ_REPLACE(&(parent->focus_head), con, new, focused); new->parent = parent; diff --git a/src/workspace.c b/src/workspace.c index 385921a4..3a637b2f 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -41,7 +41,7 @@ Con *workspace_get(const char *num, bool *created) { LOG("got output %p with content %p\n", output, content); /* We need to attach this container after setting its type. con_attach * will handle CT_WORKSPACEs differently */ - workspace = con_new(NULL); + workspace = con_new(NULL, NULL); char *name; asprintf(&name, "[i3 con] workspace %s", num); x_set_name(workspace, name); @@ -266,7 +266,7 @@ void workspace_update_urgent_flag(Con *ws) { */ void ws_force_orientation(Con *ws, orientation_t orientation) { /* 1: create a new split container */ - Con *split = con_new(NULL); + Con *split = con_new(NULL, NULL); split->parent = ws; /* 2: copy layout and orientation from workspace */ @@ -296,3 +296,45 @@ void ws_force_orientation(Con *ws, orientation_t orientation) { if (old_focused) con_focus(old_focused); } + +/* + * Called when a new con (with a window, not an empty or split con) should be + * attached to the workspace (for example when managing a new window or when + * moving an existing window to the workspace level). + * + * Depending on the workspace_layout setting, this function either returns the + * workspace itself (default layout) or creates a new stacked/tabbed con and + * returns that. + * + */ +Con *workspace_attach_to(Con *ws) { + DLOG("Attaching a window to workspace %p / %s\n", ws, ws->name); + + if (config.default_layout == L_DEFAULT) { + DLOG("Default layout, just attaching it to the workspace itself.\n"); + return ws; + } + + DLOG("Non-default layout, creating a new split container\n"); + /* 1: create a new split container */ + Con *new = con_new(NULL, NULL); + new->parent = ws; + + /* 2: set the requested layout on the split con */ + new->layout = config.default_layout; + + /* 3: While the layout is irrelevant in stacked/tabbed mode, it needs + * to be set. Otherwise, this con will not be interpreted as a split + * container. */ + if (config.default_orientation == NO_ORIENTATION) { + new->orientation = (ws->rect.height > ws->rect.width) ? VERT : HORIZ; + } else { + new->orientation = config.default_orientation; + } + + /* 4: attach the new split container to the workspace */ + DLOG("Attaching new split %p to workspace %p\n", new, ws); + con_attach(new, ws, false); + + return new; +} diff --git a/testcases/t/67-workspace_layout.t b/testcases/t/67-workspace_layout.t new file mode 100644 index 00000000..456e7316 --- /dev/null +++ b/testcases/t/67-workspace_layout.t @@ -0,0 +1,139 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3 +# +# Tests the workspace_layout config option. +# + +use i3test; +use Cwd qw(abs_path); +use Proc::Background; +use File::Temp qw(tempfile tempdir); +use X11::XCB qw(:all); +use X11::XCB::Connection; + +my $x = X11::XCB::Connection->new; + +# assuming we are run by complete-run.pl +my $i3_path = abs_path("../i3"); + +##################################################################### +# 1: check that with an empty config, cons are place next to each +# other and no split containers are created +##################################################################### + +my ($fh, $tmpfile) = tempfile(); +say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; +say $fh "ipc-socket /tmp/nestedcons"; +close($fh); + +diag("Starting i3"); +my $i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null"; +my $process = Proc::Background->new($i3cmd); +sleep 1; + +diag("pid = " . $process->pid); + +my $tmp = fresh_workspace; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + +my $first = open_standard_window($x); +my $second = open_standard_window($x); + +is($x->input_focus, $second->id, 'second window focused'); +ok(@{get_ws_content($tmp)} == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'stacked', 'layout not stacked'); +isnt($content[1]->{layout}, 'stacked', 'layout not stacked'); + +exit_gracefully($process->pid); + +##################################################################### +# 2: set workspace_layout stacked, check that when opening two cons, +# they end up in a stacked con +##################################################################### + +($fh, $tmpfile) = tempfile(); +say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; +say $fh "ipc-socket /tmp/nestedcons"; +say $fh "workspace_layout stacked"; +close($fh); + +diag("Starting i3"); +$i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null"; +$process = Proc::Background->new($i3cmd); +sleep 1; + +diag("pid = " . $process->pid); + +$tmp = fresh_workspace; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + +$first = open_standard_window($x); +$second = open_standard_window($x); + +is($x->input_focus, $second->id, 'second window focused'); +my @content = @{get_ws_content($tmp)}; +ok(@content == 1, 'one con at workspace level'); +is($content[0]->{layout}, 'stacked', 'layout stacked'); + +##################################################################### +# 3: level up, open two new cons, check that they end up in a stacked +# con +##################################################################### + +cmd 'level up'; +my $right_top = open_standard_window($x); +my $right_bot = open_standard_window($x); + +@content = @{get_ws_content($tmp)}; +is(@content, 2, 'two cons at workspace level after level up'); +is($content[0]->{layout}, 'stacked', 'layout stacked'); +is($content[1]->{layout}, 'stacked', 'layout stacked'); + +##################################################################### +# 4: move one of the cons to the right, check that it will end up in +# a stacked con +##################################################################### + +cmd 'move right'; + +@content = @{get_ws_content($tmp)}; +is(@content, 3, 'three cons at workspace level after move'); +is($content[0]->{layout}, 'stacked', 'layout stacked'); +is($content[1]->{layout}, 'stacked', 'layout stacked'); +is($content[2]->{layout}, 'stacked', 'layout stacked'); + +##################################################################### +# 5: move it to the left again, check that the stacked con is deleted +##################################################################### + +cmd 'move left'; + +@content = @{get_ws_content($tmp)}; +is(@content, 2, 'two cons at workspace level after moving back'); +is($content[0]->{layout}, 'stacked', 'layout stacked'); +is($content[1]->{layout}, 'stacked', 'layout stacked'); + +##################################################################### +# 6: move it to a different workspace, check that it ends up in a +# stacked con +##################################################################### + +my $otmp = get_unused_workspace; + +cmd "move workspace $otmp"; + +@content = @{get_ws_content($tmp)}; +is(@content, 2, 'still two cons on this workspace'); +is($content[0]->{layout}, 'stacked', 'layout stacked'); +is($content[1]->{layout}, 'stacked', 'layout stacked'); + +@content = @{get_ws_content($otmp)}; +is(@content, 1, 'one con on target workspace'); +is($content[0]->{layout}, 'stacked', 'layout stacked'); + +exit_gracefully($process->pid); + +done_testing; From 58e02e84e24a7a04b97c2f76633c2a41cd85e3d3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 3 Jun 2011 01:48:55 +0200 Subject: [PATCH 664/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20break=20focus?= =?UTF-8?q?=20stack=20when=20inplace=20restarting=20with=20fullscreen=20wi?= =?UTF-8?q?ndows=20(Thanks=20mike)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/manage.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/manage.c b/src/manage.c index 3c6f48a6..85beb4cb 100644 --- a/src/manage.c +++ b/src/manage.c @@ -264,8 +264,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki * focused (fullscreen) con. This way, the new container will be * focused after we return from fullscreen mode */ Con *first = TAILQ_FIRST(&(nc->parent->focus_head)); - TAILQ_REMOVE(&(nc->parent->focus_head), nc, focused); - TAILQ_INSERT_AFTER(&(nc->parent->focus_head), first, nc, focused); + if (first != nc) { + /* We only modify the focus stack if the container is not already + * the first one. This can happen when existing containers swallow + * new windows, for example when restarting. */ + TAILQ_REMOVE(&(nc->parent->focus_head), nc, focused); + TAILQ_INSERT_AFTER(&(nc->parent->focus_head), first, nc, focused); + } } /* set floating if necessary */ From 30501e7c8aa42ac4ed9c06f9a6ab7524d376c62b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 3 Jun 2011 02:05:34 +0200 Subject: [PATCH 665/867] add testcase for the fullscreen restart problem of the previous commit --- testcases/t/68-regress-fullscreen-restart.t | 28 +++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 testcases/t/68-regress-fullscreen-restart.t diff --git a/testcases/t/68-regress-fullscreen-restart.t b/testcases/t/68-regress-fullscreen-restart.t new file mode 100644 index 00000000..fcc9ac7e --- /dev/null +++ b/testcases/t/68-regress-fullscreen-restart.t @@ -0,0 +1,28 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Verifies that i3 survives inplace restarts with fullscreen containers +# +use i3test; +use X11::XCB qw(:all); +use X11::XCB::Connection; + +my $x = X11::XCB::Connection->new; + +fresh_workspace; + +open_standard_window($x); +open_standard_window($x); + +cmd 'layout stacking'; +sleep 1; + +cmd 'fullscreen'; +sleep 1; + +cmd 'restart'; +sleep 1; + +does_i3_live; + +done_testing; From d68e4710fed1ae28cb5889579d63f1cdc40157c9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 8 Jun 2011 20:49:49 +0200 Subject: [PATCH 666/867] CONFIG BREAK: kick the 'next'/'prev' commands, introduce 'focus left' etc. Instead of the cryptic next/prev commands, we now provide an argument to the focus command. Userguide and default config is updated. --- docs/userguide | 13 ++++---- i3.config | 16 ++++----- src/cmdparse.l | 1 - src/cmdparse.y | 52 ++++++++++++++++-------------- testcases/t/06-focus.t | 8 ++--- testcases/t/11-goto.t | 4 +-- testcases/t/21-next-prev.t | 32 ++++++------------ testcases/t/24-move.t | 4 +-- testcases/t/29-focus-after-close.t | 4 +-- testcases/t/31-stacking-order.t | 8 ++--- testcases/t/40-focus-lost.t | 2 +- 11 files changed, 66 insertions(+), 78 deletions(-) diff --git a/docs/userguide b/docs/userguide index aea0e760..b7a8ff09 100644 --- a/docs/userguide +++ b/docs/userguide @@ -629,23 +629,22 @@ bindsym Mod1+t mode toggle === Focusing/Moving containers -To change the focus, use one of the +prev h+, +next v+, +prev v+ and +next h+ -commands, meaning left, down, up, right (respectively). +To change the focus, use the focus command: +focus left+, +focus right+, +focus down+ and +focus up+. For moving, use +move left+, +move right+, +move down+ and +move up+. *Examples*: ---------------------- # Focus clients on the left, bottom, top, right: -bindsym Mod1+j prev h -bindsym Mod1+k next v -bindsym Mod1+j prev v -bindsym Mod1+semicolon next h +bindsym Mod1+j focus left +bindsym Mod1+k focus down +bindsym Mod1+l focus up +bindsym Mod1+semicolon focus right # Move client to the left, bottom, top, right: bindsym Mod1+j move left bindsym Mod1+k move down -bindsym Mod1+j move up +bindsym Mod1+l move up bindsym Mod1+semicolon move right ---------------------- diff --git a/i3.config b/i3.config index b0cbc926..847c4b14 100644 --- a/i3.config +++ b/i3.config @@ -57,16 +57,16 @@ bindsym Mod1+Shift+j reload bindsym Mod1+Shift+l exit # Focus (Mod1+n/r/t/d) -bindsym Mod1+n prev h -bindsym Mod1+r next v -bindsym Mod1+t prev v -bindsym Mod1+d next h +bindsym Mod1+n focus left +bindsym Mod1+r focus down +bindsym Mod1+t focus up +bindsym Mod1+d focus right # alternatively, you can use the cursor keys: -bindsym Mod1+Left prev h -bindsym Mod1+Right next h -bindsym Mod1+Down next v -bindsym Mod1+Up prev v +bindsym Mod1+Left focus left +bindsym Mod1+Down focus down +bindsym Mod1+Up focus up +bindsym Mod1+Right focus right # Move bindsym Mod1+Shift+n move left diff --git a/src/cmdparse.l b/src/cmdparse.l index b366e48b..91aed5e3 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -120,7 +120,6 @@ workspace { WS_STRING; return TOK_WORKSPACE; } focus { return TOK_FOCUS; } move { return TOK_MOVE; } open { return TOK_OPEN; } -next { return TOK_NEXT; } prev { return TOK_PREV; } split { return TOK_SPLIT; } horizontal { return TOK_HORIZONTAL; } diff --git a/src/cmdparse.y b/src/cmdparse.y index f3475d9d..08f02c61 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -140,7 +140,6 @@ char *parse_cmd(const char *new) { %token TOK_FOCUS "focus" %token TOK_MOVE "move" %token TOK_OPEN "open" -%token TOK_NEXT "next" %token TOK_PREV "prev" %token TOK_SPLIT "split" %token TOK_HORIZONTAL "horizontal" @@ -333,8 +332,6 @@ operation: | kill | open | fullscreen - | next - | prev | split | mode | level @@ -400,6 +397,33 @@ focus: tree_render(); } + | TOK_FOCUS direction + { + int direction = $2; + switch (direction) { + case TOK_LEFT: + LOG("Focusing left\n"); + tree_next('p', HORIZ); + break; + case TOK_RIGHT: + LOG("Focusing right\n"); + tree_next('n', HORIZ); + break; + case TOK_UP: + LOG("Focusing up\n"); + tree_next('p', VERT); + break; + case TOK_DOWN: + LOG("Focusing down\n"); + tree_next('n', VERT); + break; + default: + ELOG("Invalid focus direction (%d)\n", direction); + break; + } + + tree_render(); + } ; kill: @@ -469,28 +493,6 @@ fullscreen: } ; -next: - TOK_NEXT direction - { - /* TODO: use matches */ - printf("should select next window in direction %c\n", $2); - tree_next('n', ($2 == 'v' ? VERT : HORIZ)); - - tree_render(); - } - ; - -prev: - TOK_PREV direction - { - /* TODO: use matches */ - printf("should select prev window in direction %c\n", $2); - tree_next('p', ($2 == 'v' ? VERT : HORIZ)); - - tree_render(); - } - ; - split: TOK_SPLIT direction { diff --git a/testcases/t/06-focus.t b/testcases/t/06-focus.t index 798a5e69..826588d5 100644 --- a/testcases/t/06-focus.t +++ b/testcases/t/06-focus.t @@ -44,20 +44,20 @@ sub focus_after { $focus = $x->input_focus; is($focus, $bottom->id, "Latest window focused"); -$focus = focus_after("prev v"); +$focus = focus_after('focus up'); is($focus, $mid->id, "Middle window focused"); -$focus = focus_after("prev v"); +$focus = focus_after('focus up'); is($focus, $top->id, "Top window focused"); ##################################################################### # Test focus wrapping ##################################################################### -$focus = focus_after("prev v"); +$focus = focus_after('focus up'); is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)"); -$focus = focus_after("next v"); +$focus = focus_after('focus down'); is($focus, $top->id, "Top window focused (wrapping to the bottom works)"); ############################################### diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t index 929af5c2..7aebc880 100644 --- a/testcases/t/11-goto.t +++ b/testcases/t/11-goto.t @@ -45,7 +45,7 @@ sub focus_after { $focus = $x->input_focus; is($focus, $bottom->id, "Latest window focused"); -$focus = focus_after("prev h"); +$focus = focus_after('focus left'); is($focus, $mid->id, "Middle window focused"); ##################################################################### @@ -59,7 +59,7 @@ is($focus, $mid->id, "focus unchanged"); $i3->command("mark $random_mark")->recv; -$focus = focus_after("prev h"); +$focus = focus_after('focus left'); is($focus, $top->id, "Top window focused"); $focus = focus_after(qq|[con_mark="$random_mark"] focus|); diff --git a/testcases/t/21-next-prev.t b/testcases/t/21-next-prev.t index 042573a4..447be315 100644 --- a/testcases/t/21-next-prev.t +++ b/testcases/t/21-next-prev.t @@ -8,23 +8,23 @@ use i3test; my $tmp = fresh_workspace; ###################################################################### -# Open one container, verify that 'next v' and 'next h' do nothing +# Open one container, verify that 'focus down' and 'focus right' do nothing ###################################################################### cmd 'open'; my ($nodes, $focus) = get_ws_content($tmp); my $old_focused = $focus->[0]; -cmd 'next v'; +cmd 'focus down'; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $old_focused, 'focus did not change with only one con'); -cmd 'next h'; +cmd 'focus right'; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $old_focused, 'focus did not change with only one con'); ###################################################################### -# Open another container, verify that 'next h' switches +# Open another container, verify that 'focus right' switches ###################################################################### my $left = $old_focused; @@ -38,44 +38,32 @@ cmd 'open'; isnt($old_focused, $focus->[0], 'new container is focused'); my $right = $focus->[0]; -cmd 'next h'; +cmd 'focus right'; ($nodes, $focus) = get_ws_content($tmp); isnt($focus->[0], $right, 'focus did change'); is($focus->[0], $left, 'left container focused (wrapping)'); -cmd 'next h'; +cmd 'focus right'; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $mid, 'middle container focused'); -cmd 'next h'; +cmd 'focus right'; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $right, 'right container focused'); -cmd 'prev h'; +cmd 'focus left'; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $mid, 'middle container focused'); -cmd 'prev h'; +cmd 'focus left'; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $left, 'left container focused'); -cmd 'prev h'; +cmd 'focus left'; ($nodes, $focus) = get_ws_content($tmp); is($focus->[0], $right, 'right container focused'); -###################################################################### -# Test synonyms (horizontal/vertical instead of h/v) -###################################################################### - -cmd 'prev horizontal'; -($nodes, $focus) = get_ws_content($tmp); -is($focus->[0], $mid, 'middle container focused'); - -cmd 'next horizontal'; -($nodes, $focus) = get_ws_content($tmp); -is($focus->[0], $right, 'right container focused'); - ###################################################################### # Test focus command ###################################################################### diff --git a/testcases/t/24-move.t b/testcases/t/24-move.t index ebb7dc66..9d200aa9 100644 --- a/testcases/t/24-move.t +++ b/testcases/t/24-move.t @@ -121,11 +121,11 @@ cmd "open"; cmd "open"; cmd "split v"; cmd "open"; -cmd "prev h"; +cmd 'focus left'; cmd "split v"; cmd "open"; cmd "move right"; -cmd "prev h"; +cmd 'focus left'; cmd "move right"; $content = get_ws_content($otmp); diff --git a/testcases/t/29-focus-after-close.t b/testcases/t/29-focus-after-close.t index b34e6686..dec9bb92 100644 --- a/testcases/t/29-focus-after-close.t +++ b/testcases/t/29-focus-after-close.t @@ -64,11 +64,11 @@ cmd 'split v'; $first = open_empty_con($i3); my $bottom = open_empty_con($i3); -cmd 'prev v'; +cmd 'focus up'; cmd 'split h'; my $middle = open_empty_con($i3); my $right = open_empty_con($i3); -cmd 'next v'; +cmd 'focus down'; # We have the following layout now (second is focused): # .----------------------------. diff --git a/testcases/t/31-stacking-order.t b/testcases/t/31-stacking-order.t index 78a35cce..4ffafa17 100644 --- a/testcases/t/31-stacking-order.t +++ b/testcases/t/31-stacking-order.t @@ -28,10 +28,10 @@ isnt($first, $second, 'two different containers opened'); cmd 'layout stacking'; is(get_focused($tmp), $second, 'second container still focused'); -cmd 'next v'; +cmd 'focus down'; is(get_focused($tmp), $first, 'first container focused'); -cmd 'prev v'; +cmd 'focus up'; is(get_focused($tmp), $second, 'second container focused again'); ############################################################## @@ -42,10 +42,10 @@ cmd 'level up'; cmd 'split h'; cmd 'level down'; -cmd 'next v'; +cmd 'focus down'; is(get_focused($tmp), $first, 'first container focused'); -cmd 'prev v'; +cmd 'focus up'; is(get_focused($tmp), $second, 'second container focused again'); diff --git a/testcases/t/40-focus-lost.t b/testcases/t/40-focus-lost.t index 4158d1f9..a39d74ca 100644 --- a/testcases/t/40-focus-lost.t +++ b/testcases/t/40-focus-lost.t @@ -36,7 +36,7 @@ diag("left = " . $left->id . ", mid = " . $mid->id . ", right = " . $right->id); is($x->input_focus, $right->id, 'Right window focused'); -cmd 'prev h'; +cmd 'focus left'; is($x->input_focus, $mid->id, 'Mid window focused'); From 9aa7e5fbd6f0ed02bc0edd34cf7cd28bf1eacd85 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 8 Jun 2011 23:34:08 +0200 Subject: [PATCH 667/867] Parse multiple criteria in commands (+test), better error message for 'focus' --- src/cmdparse.l | 1 + src/cmdparse.y | 27 ++++++++++++++++++++++----- testcases/t/11-goto.t | 11 ++++++++++- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/cmdparse.l b/src/cmdparse.l index 91aed5e3..ba2789f2 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -143,6 +143,7 @@ class { BEGIN(WANT_QSTRING); return TOK_CLASS; } id { BEGIN(WANT_QSTRING); return TOK_ID; } con_id { BEGIN(WANT_QSTRING); return TOK_CON_ID; } con_mark { BEGIN(WANT_QSTRING); return TOK_MARK; } +title { BEGIN(WANT_QSTRING); return TOK_TITLE; } [0-9]+ { cmdyylval.number = atoi(yytext); return NUMBER; } diff --git a/src/cmdparse.y b/src/cmdparse.y index 08f02c61..c33a145b 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -162,6 +162,7 @@ char *parse_cmd(const char *new) { %token TOK_CLASS "class" %token TOK_ID "id" %token TOK_CON_ID "con_id" +%token TOK_TITLE "title" %token STR "" %token NUMBER "" @@ -271,6 +272,11 @@ matchend: ; criteria: + criteria criterion + | criterion + ; + +criterion: TOK_CLASS '=' STR { printf("criteria: class = %s\n", $3); @@ -311,6 +317,11 @@ criteria: printf("criteria: mark = %s\n", $3); current_match.mark = $3; } + | TOK_TITLE '=' STR + { + printf("criteria: title = %s\n", $3); + current_match.title = $3; + } ; operations: @@ -381,20 +392,26 @@ focus: { owindow *current; - printf("should focus\n"); if (match_is_empty(¤t_match)) { - /* TODO: better error message */ - LOG("Error: The focus command requires you to use some criteria.\n"); + ELOG("You have to specify which window/container should be focused.\n"); + ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n"); + + asprintf(&json_output, "{\"success\":false, \"error\":\"You have to " + "specify which window/container should be focused\"}"); break; } - /* TODO: warning if the match contains more than one entry. does not - * make so much sense when focusing */ + int count = 0; TAILQ_FOREACH(current, &owindows, owindows) { LOG("focusing %p / %s\n", current->con, current->con->name); con_focus(current->con); + count++; } + if (count > 1) + LOG("WARNING: Your criteria for the focus command matches %d containers, " + "while only exactly one container can be focused at a time.\n", count); + tree_render(); } | TOK_FOCUS direction diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t index 7aebc880..4eedb56c 100644 --- a/testcases/t/11-goto.t +++ b/testcases/t/11-goto.t @@ -1,7 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 6; +use i3test; use X11::XCB qw(:all); use Digest::SHA1 qw(sha1_base64); @@ -65,4 +65,13 @@ is($focus, $top->id, "Top window focused"); $focus = focus_after(qq|[con_mark="$random_mark"] focus|); is($focus, $mid->id, "goto worked"); +# check that we can specify multiple criteria + +$focus = focus_after('focus left'); +is($focus, $top->id, "Top window focused"); + +$focus = focus_after(qq|[con_mark="$random_mark" con_mark="$random_mark"] focus|); +is($focus, $mid->id, "goto worked"); + + done_testing; From adb6d9630cabf171e560feeccb946d7c2c51c8bb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 10 Jun 2011 01:16:29 +0200 Subject: [PATCH 668/867] config: more descriptive comments --- i3.config | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/i3.config b/i3.config index 847c4b14..f726de8c 100644 --- a/i3.config +++ b/i3.config @@ -13,10 +13,10 @@ ipc-socket /tmp/nestedcons # Open empty container bindsym Mod1+Shift+Return open -# Start terminal (Mod1+Enter) +# Start terminal bindsym Mod1+Return exec /usr/bin/urxvt -# Start dmenu (Mod1+p) +# Start dmenu bindsym Mod1+p exec /usr/bin/dmenu_run # Horizontal orientation @@ -25,7 +25,7 @@ bindsym Mod1+h split h # Vertical orientation bindsym Mod1+v split v -# Fullscreen (Mod1+f) +# Enter fullscreen mode for the focused container bindsym Mod1+f fullscreen # Stacking (Mod1+s) @@ -43,20 +43,20 @@ bindsym Mod1+Shift+space mode toggle bindsym Mod1+u level up #bindsym Mod1+d level down -# Kill current client (Mod1+c) +# Kill current window bindsym Mod1+c kill # Restore saved JSON layout bindsym Mod1+y restore /home/michael/i3/layout.json -# Restart i3 -bindsym Mod1+Shift+c restart -# Reload i3 +# Reload the configuration file bindsym Mod1+Shift+j reload -# Exit i3 +# Restart i3 inplace +bindsym Mod1+Shift+c restart +# Exit i3 (logs you out of your X session) bindsym Mod1+Shift+l exit -# Focus (Mod1+n/r/t/d) +# Focus bindsym Mod1+n focus left bindsym Mod1+r focus down bindsym Mod1+t focus up @@ -80,7 +80,7 @@ bindsym Mod1+Shift+Right move right bindsym Mod1+Shift+Down move down bindsym Mod1+Shift+Up move up -# Workspaces (Mod1+1/2/…) +# Switch to workspace bindsym Mod1+1 workspace 1 bindsym Mod1+2 workspace 2 bindsym Mod1+3 workspace 3 @@ -92,7 +92,7 @@ bindsym Mod1+8 workspace 8 bindsym Mod1+9 workspace 9 bindsym Mod1+0 workspace 10 -# Move to Workspaces +# Move focused container to workspace bindsym Mod1+Shift+1 move workspace 1 bindsym Mod1+Shift+2 move workspace 2 bindsym Mod1+Shift+3 move workspace 3 From 9bbb37bb55bab10d498765cd1d701cffff0ee158 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 10 Jun 2011 01:36:33 +0200 Subject: [PATCH 669/867] BREAKS CONFIG: rename 'level up' to 'focus parent' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …and 'level down' to 'focus child'. More intuitive than the old command names. --- docs/userguide | 6 ++--- i3.config | 7 ++++-- src/cmdparse.l | 3 ++- src/cmdparse.y | 37 ++++++++++++---------------- testcases/t/24-move.t | 2 +- testcases/t/29-focus-after-close.t | 4 +-- testcases/t/31-stacking-order.t | 4 +-- testcases/t/55-floating-split-size.t | 2 +- testcases/t/67-workspace_layout.t | 6 ++--- 9 files changed, 35 insertions(+), 36 deletions(-) diff --git a/docs/userguide b/docs/userguide index b7a8ff09..86319974 100644 --- a/docs/userguide +++ b/docs/userguide @@ -237,19 +237,19 @@ unfloat::[] You probably guessed it already: There is no limit on how deep your hierarchy of splits can be. -=== Level up +=== Focus parent Let’s stay with our example from above. We have a terminal on the left and two vertically split terminals on the right, focus is on the bottom right one. When you open a new terminal, it will open below the current one. So, how can you open a new terminal window to the *right* of the current one? -The solution is to use +level up+, which will focus the +Parent Container+ of +The solution is to use +focus parent+, which will focus the +Parent Container+ of the current +Container+. In this case, you would focus the +Vertical Split Container+ which is *inside* the horizontally oriented workspace. Thus, now new windows will be opened to the right of the +Vertical Split Container+: -image::tree-shot3.png["shot3",title="Level Up, then open new terminal"] +image::tree-shot3.png["shot3",title="Focus parent, then open new terminal"] == Configuring i3 diff --git a/i3.config b/i3.config index f726de8c..72399dfa 100644 --- a/i3.config +++ b/i3.config @@ -40,8 +40,11 @@ bindsym Mod1+l layout default # toggle tiling / floating bindsym Mod1+Shift+space mode toggle -bindsym Mod1+u level up -#bindsym Mod1+d level down +# focus the parent container +bindsym Mod1+u focus parent + +# focus the child container +#bindsym Mod1+d focus child # Kill current window bindsym Mod1+c kill diff --git a/src/cmdparse.l b/src/cmdparse.l index ba2789f2..545def7d 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -124,11 +124,12 @@ prev { return TOK_PREV; } split { return TOK_SPLIT; } horizontal { return TOK_HORIZONTAL; } vertical { return TOK_VERTICAL; } -level { return TOK_LEVEL; } up { return TOK_UP; } down { return TOK_DOWN; } left { return TOK_LEFT; } right { return TOK_RIGHT; } +parent { return TOK_PARENT; } +child { return TOK_CHILD; } resize { return TOK_RESIZE; } shrink { return TOK_SHRINK; } grow { return TOK_GROW; } diff --git a/src/cmdparse.y b/src/cmdparse.y index c33a145b..8cf7858b 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -144,11 +144,12 @@ char *parse_cmd(const char *new) { %token TOK_SPLIT "split" %token TOK_HORIZONTAL "horizontal" %token TOK_VERTICAL "vertical" -%token TOK_LEVEL "level" %token TOK_UP "up" %token TOK_DOWN "down" %token TOK_LEFT "left" %token TOK_RIGHT "right" +%token TOK_PARENT "parent" +%token TOK_CHILD "child" %token TOK_RESTORE "restore" %token TOK_MARK "mark" %token TOK_RESIZE "resize" @@ -168,7 +169,7 @@ char *parse_cmd(const char *new) { %token NUMBER "" %type direction -%type level_direction +%type level %type window_mode %type border_style %type layout_mode @@ -345,7 +346,6 @@ operation: | fullscreen | split | mode - | level | mark | resize | nop @@ -441,6 +441,19 @@ focus: tree_render(); } + | TOK_FOCUS level + { + if ($2 == TOK_PARENT) + level_up(); + else level_down(); + + tree_render(); + } + ; + +level: + TOK_PARENT { $$ = TOK_PARENT; } + | TOK_CHILD { $$ = TOK_CHILD; } ; kill: @@ -582,24 +595,6 @@ border_style: | TOK_1PIXEL { $$ = BS_1PIXEL; } ; - -level: - TOK_LEVEL level_direction - { - printf("level %c\n", $2); - if ($2 == 'u') - level_up(); - else level_down(); - - tree_render(); - } - ; - -level_direction: - TOK_UP { $$ = 'u'; } - | TOK_DOWN { $$ = 'd'; } - ; - move: TOK_MOVE direction { diff --git a/testcases/t/24-move.t b/testcases/t/24-move.t index 9d200aa9..b007d5b1 100644 --- a/testcases/t/24-move.t +++ b/testcases/t/24-move.t @@ -75,7 +75,7 @@ is($content->[0]->{id}, $first, 'first container unmodified'); # | | | | # -------------------------- cmd 'split v'; -cmd 'level up'; +cmd 'focus parent'; cmd 'open'; $content = get_ws_content($tmp); diff --git a/testcases/t/29-focus-after-close.t b/testcases/t/29-focus-after-close.t index dec9bb92..5f5ef4af 100644 --- a/testcases/t/29-focus-after-close.t +++ b/testcases/t/29-focus-after-close.t @@ -23,9 +23,9 @@ cmd 'split v'; my ($nodes, $focus) = get_ws_content($tmp); is($nodes->[1]->{focused}, 0, 'split container not focused'); -cmd 'level up'; +cmd 'focus parent'; ($nodes, $focus) = get_ws_content($tmp); -is($nodes->[1]->{focused}, 1, 'split container focused after level up'); +is($nodes->[1]->{focused}, 1, 'split container focused after focus parent'); my $third = open_empty_con($i3); diff --git a/testcases/t/31-stacking-order.t b/testcases/t/31-stacking-order.t index 4ffafa17..ea42596c 100644 --- a/testcases/t/31-stacking-order.t +++ b/testcases/t/31-stacking-order.t @@ -38,9 +38,9 @@ is(get_focused($tmp), $second, 'second container focused again'); # now change the orientation to horizontal and cycle ############################################################## -cmd 'level up'; +cmd 'focus parent'; cmd 'split h'; -cmd 'level down'; +cmd 'focus child'; cmd 'focus down'; is(get_focused($tmp), $first, 'first container focused'); diff --git a/testcases/t/55-floating-split-size.t b/testcases/t/55-floating-split-size.t index 61ceae3c..3ff1d8f3 100644 --- a/testcases/t/55-floating-split-size.t +++ b/testcases/t/55-floating-split-size.t @@ -48,7 +48,7 @@ sleep 0.25; # Set the parent to floating ##################################################################### cmd 'nop setting floating'; -cmd 'level up'; +cmd 'focus parent'; cmd 'mode floating'; ##################################################################### diff --git a/testcases/t/67-workspace_layout.t b/testcases/t/67-workspace_layout.t index 456e7316..46d0c274 100644 --- a/testcases/t/67-workspace_layout.t +++ b/testcases/t/67-workspace_layout.t @@ -79,16 +79,16 @@ ok(@content == 1, 'one con at workspace level'); is($content[0]->{layout}, 'stacked', 'layout stacked'); ##################################################################### -# 3: level up, open two new cons, check that they end up in a stacked +# 3: focus parent, open two new cons, check that they end up in a stacked # con ##################################################################### -cmd 'level up'; +cmd 'focus parent'; my $right_top = open_standard_window($x); my $right_bot = open_standard_window($x); @content = @{get_ws_content($tmp)}; -is(@content, 2, 'two cons at workspace level after level up'); +is(@content, 2, 'two cons at workspace level after focus parent'); is($content[0]->{layout}, 'stacked', 'layout stacked'); is($content[1]->{layout}, 'stacked', 'layout stacked'); From bd49c80d5f70f2b379aff74ff5af618c94da7871 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 10 Jun 2011 02:06:47 +0200 Subject: [PATCH 670/867] CONFIG BREAK: rename 'mode floating/tiling' to 'floating enable/disable' The three different variants now: floating enable floating disable floating toggle --- docs/userguide | 6 +++--- i3.config | 2 +- src/cmdparse.l | 7 +++++++ src/cmdparse.y | 20 +++++++++++--------- testcases/t/32-move-workspace.t | 2 +- testcases/t/46-floating-reinsert.t | 4 ++-- testcases/t/53-floating-originalsize.t | 2 +- testcases/t/55-floating-split-size.t | 2 +- 8 files changed, 27 insertions(+), 18 deletions(-) diff --git a/docs/userguide b/docs/userguide index 86319974..bf0e84a2 100644 --- a/docs/userguide +++ b/docs/userguide @@ -611,8 +611,8 @@ focus_follows_mouse no To change the layout of the current container to stacking, use +layout stacking+, for default use +layout default+ and for tabbed, use +layout tabbed+. To make the current client (!) fullscreen, use +fullscreen+, to make -it floating (or tiling again) use +mode floating+ respectively +mode tiling+ -(or +mode toggle+): +it floating (or tiling again) use +floating enable+ respectively +floating disable+ +(or +floating toggle+): *Examples*: -------------- @@ -624,7 +624,7 @@ bindsym Mod1+w layout tabbed bindsym Mod1+f fullscreen # Toggle floating/tiling -bindsym Mod1+t mode toggle +bindsym Mod1+t floating toggle -------------- === Focusing/Moving containers diff --git a/i3.config b/i3.config index 72399dfa..1ce396de 100644 --- a/i3.config +++ b/i3.config @@ -38,7 +38,7 @@ bindsym Mod1+w layout tabbed bindsym Mod1+l layout default # toggle tiling / floating -bindsym Mod1+Shift+space mode toggle +bindsym Mod1+Shift+space floating toggle # focus the parent container bindsym Mod1+u focus parent diff --git a/src/cmdparse.l b/src/cmdparse.l index 545def7d..6c57984e 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -140,6 +140,13 @@ nop { WS_STRING; return TOK_NOP; } restore { WS_STRING; return TOK_RESTORE; } mark { WS_STRING; return TOK_MARK; } +enable { return TOK_ENABLE; } +true { return TOK_ENABLE; } +yes { return TOK_ENABLE; } +disable { return TOK_DISABLE; } +false { return TOK_DISABLE; } +no { return TOK_DISABLE; } + class { BEGIN(WANT_QSTRING); return TOK_CLASS; } id { BEGIN(WANT_QSTRING); return TOK_ID; } con_id { BEGIN(WANT_QSTRING); return TOK_CON_ID; } diff --git a/src/cmdparse.y b/src/cmdparse.y index 8cf7858b..0738d0c9 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -135,6 +135,8 @@ char *parse_cmd(const char *new) { %token TOK_MODE "mode" %token TOK_TILING "tiling" %token TOK_FLOATING "floating" +%token TOK_ENABLE "enable" +%token TOK_DISABLE "disable" %token TOK_WORKSPACE "workspace" %token TOK_TOGGLE "toggle" %token TOK_FOCUS "focus" @@ -170,7 +172,7 @@ char *parse_cmd(const char *new) { %type direction %type level -%type window_mode +%type boolean %type border_style %type layout_mode %type resize_px @@ -345,7 +347,7 @@ operation: | open | fullscreen | split - | mode + | floating | mark | resize | nop @@ -541,8 +543,8 @@ direction: | 'v' { $$ = 'v'; } ; -mode: - TOK_MODE window_mode +floating: + TOK_FLOATING boolean { HANDLE_EMPTY_MATCH; @@ -554,7 +556,7 @@ mode: toggle_floating_mode(current->con, false); } else { printf("should switch mode to %s\n", ($2 == TOK_FLOATING ? "floating" : "tiling")); - if ($2 == TOK_FLOATING) { + if ($2 == TOK_ENABLE) { floating_enable(current->con, false); } else { floating_disable(current->con, false); @@ -566,10 +568,10 @@ mode: } ; -window_mode: - TOK_FLOATING { $$ = TOK_FLOATING; } - | TOK_TILING { $$ = TOK_TILING; } - | TOK_TOGGLE { $$ = TOK_TOGGLE; } +boolean: + TOK_ENABLE { $$ = TOK_ENABLE; } + | TOK_DISABLE { $$ = TOK_DISABLE; } + | TOK_TOGGLE { $$ = TOK_TOGGLE; } ; border: diff --git a/testcases/t/32-move-workspace.t b/testcases/t/32-move-workspace.t index affd18f2..93bd4f8f 100644 --- a/testcases/t/32-move-workspace.t +++ b/testcases/t/32-move-workspace.t @@ -47,7 +47,7 @@ $tmp2 = get_unused_workspace(); cmd "workspace $tmp"; cmd "open"; -cmd "mode toggle"; +cmd "floating toggle"; my $ws = get_ws($tmp); is(@{$ws->{nodes}}, 0, 'no nodes on workspace'); diff --git a/testcases/t/46-floating-reinsert.t b/testcases/t/46-floating-reinsert.t index b75c08e1..a4e90d4d 100644 --- a/testcases/t/46-floating-reinsert.t +++ b/testcases/t/46-floating-reinsert.t @@ -54,10 +54,10 @@ is(@{$nodes->[1]->{nodes}}, 2, 'two windows in split con'); # 2: make it tiling, see where it ends up ############################################################################# -cmd 'mode toggle'; +cmd 'floating toggle'; my ($nodes, $focus) = get_ws_content($tmp); -is(@{$nodes->[1]->{nodes}}, 3, 'three windows in split con after mode toggle'); +is(@{$nodes->[1]->{nodes}}, 3, 'three windows in split con after floating toggle'); done_testing; diff --git a/testcases/t/53-floating-originalsize.t b/testcases/t/53-floating-originalsize.t index 16694593..16e62c20 100644 --- a/testcases/t/53-floating-originalsize.t +++ b/testcases/t/53-floating-originalsize.t @@ -29,7 +29,7 @@ ok($window->mapped, 'Window is mapped'); cmp_ok($absolute->{width}, '>', 400, 'i3 raised the width'); cmp_ok($absolute->{height}, '>', 150, 'i3 raised the height'); -cmd 'mode toggle'; +cmd 'floating toggle'; sleep 0.25; ($absolute, $top) = $window->rect; diff --git a/testcases/t/55-floating-split-size.t b/testcases/t/55-floating-split-size.t index 3ff1d8f3..c6f42b97 100644 --- a/testcases/t/55-floating-split-size.t +++ b/testcases/t/55-floating-split-size.t @@ -49,7 +49,7 @@ sleep 0.25; ##################################################################### cmd 'nop setting floating'; cmd 'focus parent'; -cmd 'mode floating'; +cmd 'floating enable'; ##################################################################### # Get geometry of the first floating node (the split container) From f1385ba3d522d069d616aa5c824c5a5ed84505e0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 10 Jun 2011 02:15:31 +0200 Subject: [PATCH 671/867] CONFIG BREAK: Rename 'restore' to 'append_layout' --- i3.config | 2 +- src/cmdparse.l | 2 +- src/cmdparse.y | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/i3.config b/i3.config index 1ce396de..a594ea24 100644 --- a/i3.config +++ b/i3.config @@ -50,7 +50,7 @@ bindsym Mod1+u focus parent bindsym Mod1+c kill # Restore saved JSON layout -bindsym Mod1+y restore /home/michael/i3/layout.json +bindsym Mod1+y append_layout /home/michael/i3/layout.json # Reload the configuration file bindsym Mod1+Shift+j reload diff --git a/src/cmdparse.l b/src/cmdparse.l index 6c57984e..bc83b026 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -137,7 +137,7 @@ px { return TOK_PX; } or { return TOK_OR; } ppt { return TOK_PPT; } nop { WS_STRING; return TOK_NOP; } -restore { WS_STRING; return TOK_RESTORE; } +append_layout { WS_STRING; return TOK_APPEND_LAYOUT; } mark { WS_STRING; return TOK_MARK; } enable { return TOK_ENABLE; } diff --git a/src/cmdparse.y b/src/cmdparse.y index 0738d0c9..61903398 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -152,7 +152,7 @@ char *parse_cmd(const char *new) { %token TOK_RIGHT "right" %token TOK_PARENT "parent" %token TOK_CHILD "child" -%token TOK_RESTORE "restore" +%token TOK_APPEND_LAYOUT "append_layout" %token TOK_MARK "mark" %token TOK_RESIZE "resize" %token TOK_GROW "grow" @@ -339,7 +339,7 @@ operation: | reload | border | layout - | restore + | append_layout | move | workspace | focus @@ -625,8 +625,8 @@ move: } ; -restore: - TOK_RESTORE STR +append_layout: + TOK_APPEND_LAYOUT STR { printf("restoring \"%s\"\n", $2); tree_append_json($2); From df0b7bed48cc77d44ad039291bc5638bbaac52d1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 10 Jun 2011 02:25:14 +0200 Subject: [PATCH 672/867] cmdparse: s/direction/split_direction to be more clear --- src/cmdparse.y | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index 61903398..a540efed 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -171,6 +171,7 @@ char *parse_cmd(const char *new) { %token NUMBER "" %type direction +%type split_direction %type level %type boolean %type border_style @@ -526,7 +527,7 @@ fullscreen: ; split: - TOK_SPLIT direction + TOK_SPLIT split_direction { /* TODO: use matches */ printf("splitting in direction %c\n", $2); @@ -536,7 +537,7 @@ split: } ; -direction: +split_direction: TOK_HORIZONTAL { $$ = 'h'; } | 'h' { $$ = 'h'; } | TOK_VERTICAL { $$ = 'v'; } From c6352ded4e4b8722bd395ef4402858cdc9c5ba67 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 10 Jun 2011 02:27:14 +0200 Subject: [PATCH 673/867] =?UTF-8?q?default=20config:=20don=E2=80=99t=20use?= =?UTF-8?q?=20/tmp/nestedcons,=20better=20description=20of=20ipc-socket?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- i3.config | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/i3.config b/i3.config index a594ea24..55bd5cb8 100644 --- a/i3.config +++ b/i3.config @@ -7,8 +7,9 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 # Use Mouse+Mod1 to drag floating windows to their wanted position floating_modifier Mod1 -# temporary path during development -ipc-socket /tmp/nestedcons +# Enable Inter Process Communication (IPC) for i3bar, i3msg, etc. +# socket will be stored in /tmp/i3-/ipc-socket. +ipc-socket # Open empty container bindsym Mod1+Shift+Return open From 39b1c1bf754ccc81a362dbe2be9b02ba9799baa5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 10 Jun 2011 02:38:07 +0200 Subject: [PATCH 674/867] Re-implement the 'mode' command --- include/config.h | 2 +- src/cmdparse.l | 2 +- src/cmdparse.y | 8 ++++++++ src/config.c | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/include/config.h b/include/config.h index c4fe6b08..f05de324 100644 --- a/include/config.h +++ b/include/config.h @@ -171,7 +171,7 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch); * Switches the key bindings to the given mode, if the mode exists * */ -void switch_mode(xcb_connection_t *conn, const char *new_mode); +void switch_mode(const char *new_mode); /** * Returns a pointer to the Binding with the specified modifiers and keycode diff --git a/src/cmdparse.l b/src/cmdparse.l index bc83b026..641265e6 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -112,7 +112,7 @@ border { return TOK_BORDER; } normal { return TOK_NORMAL; } none { return TOK_NONE; } 1pixel { return TOK_1PIXEL; } -mode { return TOK_MODE; } +mode { BEGIN(WANT_QSTRING); return TOK_MODE; } tiling { return TOK_TILING; } floating { return TOK_FLOATING; } toggle { return TOK_TOGGLE; } diff --git a/src/cmdparse.y b/src/cmdparse.y index a540efed..f86bead9 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -352,6 +352,7 @@ operation: | mark | resize | nop + | mode ; exec: @@ -784,3 +785,10 @@ direction: | TOK_LEFT { $$ = TOK_LEFT; } | TOK_RIGHT { $$ = TOK_RIGHT; } ; + +mode: + TOK_MODE STR + { + switch_mode($2); + } + ; diff --git a/src/config.c b/src/config.c index 572b4ab9..98b40dc4 100644 --- a/src/config.c +++ b/src/config.c @@ -150,7 +150,7 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) { * Switches the key bindings to the given mode, if the mode exists * */ -void switch_mode(xcb_connection_t *conn, const char *new_mode) { +void switch_mode(const char *new_mode) { struct Mode *mode; LOG("Switching to mode %s\n", new_mode); From 015a94bd8589d772859147a2eb4d09478e96c209 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 10 Jun 2011 13:00:24 +0200 Subject: [PATCH 675/867] debian: fix build-deps for recent debian systems --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 39446178..1eb2c92e 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: utils Priority: extra Maintainer: Michael Stapelberg DM-Upload-Allowed: yes -Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev +Build-Depends: debhelper (>= 5), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev Standards-Version: 3.8.3 Homepage: http://i3.zekjur.net/ From 238057667a99304c7e8cf0e25ff7ac2324b2361f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 10 Jun 2011 15:14:42 +0200 Subject: [PATCH 676/867] userguide: document workspace_layout --- docs/userguide | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/userguide b/docs/userguide index bf0e84a2..17373410 100644 --- a/docs/userguide +++ b/docs/userguide @@ -378,26 +378,26 @@ floating_modifier floating_modifier Mod1 -------------------------------- -//////////////////////////////////////////////////////////////////////// === Layout mode for new containers -TODO: this is workspace_layout. but workspace_layout only works for the first -con, right? - -This option determines in which mode new containers will start. See also -<>. +This option determines in which mode new containers on workspace level will +start. +/////////////////////////////// +See also <>. +////////////////////////////// *Syntax*: --------------------------------------------- -new_container -new_container stack-limit +workspace_layout --------------------------------------------- +///////////////////////////////////////////// +new_container stack-limit +///////////////////////////////////////////// *Examples*: --------------------- -new_container tabbed +workspace_layout tabbed --------------------- -//////////////////////////////////////////////////////////////////////// === Border style for new windows From c5a44f12d41f39728686ef8b4f875d59a0b51e04 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 10 Jun 2011 15:16:05 +0200 Subject: [PATCH 677/867] userguide: document resizing with a mode --- docs/userguide | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/docs/userguide b/docs/userguide index 17373410..205b5a99 100644 --- a/docs/userguide +++ b/docs/userguide @@ -674,13 +674,11 @@ bindsym Mod1+Shift+2 move workspace 2 [[resizingconfig]] -=== Resizing columns/rows +=== Resizing containers/windows -If you want to resize columns/rows using your keyboard, you can use the +If you want to resize containers/windows using your keyboard, you can use the +resize+ command, I recommend using it inside a so called +mode+: -/////////////////////////////////////////////////////////////////////// -TODO: mode is not yet implemented .Example: Configuration file, defining a mode for resizing ---------------------------------------------------------------------- mode "resize" { @@ -706,11 +704,9 @@ mode "resize" { } # Enter resize mode -bindsym Mod1+r mode resize +bindsym Mod1+r mode "resize" ---------------------------------------------------------------------- -/////////////////////////////////////////////////////////////////////// - === Jumping to specific windows Often when in a multi-monitor environment, you want to quickly jump to a From 60ae26c19d31f3e20a939bcd01b750d1112dab6c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 10 Jun 2011 16:03:59 +0200 Subject: [PATCH 678/867] Implement 'workspace next/prev' (+test) --- docs/userguide | 11 ++--- include/workspace.h | 12 ++++++ src/cmdparse.l | 6 ++- src/cmdparse.y | 13 +++++- src/workspace.c | 26 ++++++++++++ testcases/t/17-workspace.t | 86 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 145 insertions(+), 9 deletions(-) diff --git a/docs/userguide b/docs/userguide index 205b5a99..0046028a 100644 --- a/docs/userguide +++ b/docs/userguide @@ -653,13 +653,10 @@ bindsym Mod1+semicolon move right To change to a specific workspace, use the +workspace+ command, followed by the number or name of the workspace. To move containers, use +move workspace+. -////////////////////////////////////////////////////////////////////////////// -TODO: not yet implemented - -You can also switch to the next and previous workspace with the commands +nw+ -and +pw+, which is handy, for example, if you have workspace 1, 3, 4 and 9 and -you want to cycle through them with a single key combination. -////////////////////////////////////////////////////////////////////////////// +You can also switch to the next and previous workspace with the commands ++workspace next+ and +workspace prev+, which is handy, for example, if you have +workspace 1, 3, 4 and 9 and you want to cycle through them with a single key +combination. *Examples*: ------------------------- diff --git a/include/workspace.h b/include/workspace.h index 367c150e..aebf1365 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -49,6 +49,18 @@ bool workspace_is_visible(Con *ws); /** Switches to the given workspace */ void workspace_show(const char *num); +/** + * Focuses the next workspace. + * + */ +void workspace_next(); + +/** + * Focuses the previous workspace. + * + */ +void workspace_prev(); + #if 0 /** * Assigns the given workspace to the given screen by correctly updating its diff --git a/src/cmdparse.l b/src/cmdparse.l index 641265e6..9a0bc903 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -71,6 +71,11 @@ EOL (\r?\n) cmdyycolumn = 1; } + /* the next/prev tokens are here to recognize them *before* handling + * strings ('workspace' command) */ +next { return TOK_NEXT; } +prev { return TOK_PREV; } + \"[^\"]+\" { BEGIN(INITIAL); /* strip quotes */ @@ -120,7 +125,6 @@ workspace { WS_STRING; return TOK_WORKSPACE; } focus { return TOK_FOCUS; } move { return TOK_MOVE; } open { return TOK_OPEN; } -prev { return TOK_PREV; } split { return TOK_SPLIT; } horizontal { return TOK_HORIZONTAL; } vertical { return TOK_VERTICAL; } diff --git a/src/cmdparse.y b/src/cmdparse.y index f86bead9..32683112 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -142,6 +142,7 @@ char *parse_cmd(const char *new) { %token TOK_FOCUS "focus" %token TOK_MOVE "move" %token TOK_OPEN "open" +%token TOK_NEXT "next" %token TOK_PREV "prev" %token TOK_SPLIT "split" %token TOK_HORIZONTAL "horizontal" @@ -487,7 +488,17 @@ optional_kill_mode: ; workspace: - TOK_WORKSPACE STR + TOK_WORKSPACE TOK_NEXT + { + workspace_next(); + tree_render(); + } + | TOK_WORKSPACE TOK_PREV + { + workspace_prev(); + tree_render(); + } + | TOK_WORKSPACE STR { printf("should switch to workspace %s\n", $2); workspace_show($2); diff --git a/src/workspace.c b/src/workspace.c index 3a637b2f..ec57df8d 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -231,6 +231,32 @@ void workspace_show(const char *num) { ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); } +/* + * Focuses the next workspace. + * + */ +void workspace_next() { + Con *ws = con_get_workspace(focused); + Con *next = TAILQ_NEXT(ws, nodes); + if (!next) + next = TAILQ_FIRST(&(ws->parent->nodes_head)); + + workspace_show(next->name); +} + +/* + * Focuses the previous workspace. + * + */ +void workspace_prev() { + Con *ws = con_get_workspace(focused); + Con *prev = TAILQ_PREV(ws, nodes_head, nodes); + if (!prev) + prev = TAILQ_LAST(&(ws->parent->nodes_head), nodes_head); + + workspace_show(prev->name); +} + static bool get_urgency_flag(Con *con) { Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) diff --git a/testcases/t/17-workspace.t b/testcases/t/17-workspace.t index e7892b2b..32f82969 100644 --- a/testcases/t/17-workspace.t +++ b/testcases/t/17-workspace.t @@ -4,6 +4,7 @@ # Tests whether we can switch to a non-existant workspace # (necessary for further tests) # +use List::Util qw(first); use i3test; sub workspace_exists { @@ -32,4 +33,89 @@ cmd "workspace $otmp"; cmd "workspace $otmp"; ok(workspace_exists($otmp), 'other workspace still exists'); + +##################################################################### +# check if the workspace next / prev commands work +##################################################################### + +cmd 'workspace next'; + +ok(!workspace_exists('next'), 'workspace "next" does not exist'); + +cmd "workspace $tmp"; +cmd 'open'; + +ok(workspace_exists($tmp), 'workspace created'); + +cmd "workspace $otmp"; +cmd 'open'; + +ok(workspace_exists($tmp), 'workspace tmp still exists'); +ok(workspace_exists($otmp), 'workspace otmp created'); + +sub focused_ws_con { + my $i3 = i3("/tmp/nestedcons"); + my $tree = $i3->get_tree->recv; + my @outputs = @{$tree->{nodes}}; + my @cons; + for my $output (@outputs) { + # get the first CT_CON of each output + my $content = first { $_->{type} == 2 } @{$output->{nodes}}; + my @focused = @{$content->{focus}}; + return first { $_->{id} == $focused[0] } @{$content->{nodes}}; + } +} + +sub focused_ws { + my $con = focused_ws_con; + return $con->{name}; +} + +is(focused_ws(), $otmp, 'focused workspace is otmp'); + +cmd 'workspace prev'; +is(focused_ws(), $tmp, 'focused workspace is tmp after workspace prev'); + +cmd 'workspace next'; +is(focused_ws(), $otmp, 'focused workspace is otmp after workspace next'); + + +##################################################################### +# check that wrapping works +##################################################################### + +cmd 'workspace next'; +is(focused_ws(), '1', 'focused workspace is 1 after workspace next'); + +cmd 'workspace next'; +is(focused_ws(), $tmp, 'focused workspace is tmp after workspace next'); + +cmd 'workspace next'; +is(focused_ws(), $otmp, 'focused workspace is otmp after workspace next'); + + +cmd 'workspace prev'; +is(focused_ws(), $tmp, 'focused workspace is tmp after workspace prev'); + +cmd 'workspace prev'; +is(focused_ws(), '1', 'focused workspace is tmp after workspace prev'); + +cmd 'workspace prev'; +is(focused_ws(), $otmp, 'focused workspace is otmp after workspace prev'); + + +##################################################################### +# check if we can change to "next" / "prev" +##################################################################### + +cmd 'workspace "next"'; + +ok(workspace_exists('next'), 'workspace "next" exists'); +is(focused_ws(), 'next', 'now on workspace next'); + +cmd 'workspace "prev"'; + +ok(workspace_exists('prev'), 'workspace "prev" exists'); +is(focused_ws(), 'prev', 'now on workspace prev'); + done_testing; From bef25d72aa02066d087599c4b0ca313327979c98 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 10 Jun 2011 16:15:52 +0200 Subject: [PATCH 679/867] Implement 'border toggle' (+test) --- docs/userguide | 3 --- src/cmdparse.y | 6 ++++- testcases/t/69-border-toggle.t | 40 ++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 testcases/t/69-border-toggle.t diff --git a/docs/userguide b/docs/userguide index 0046028a..0cdb2566 100644 --- a/docs/userguide +++ b/docs/userguide @@ -794,10 +794,7 @@ To change the border of the current client, you can use +border normal+ to use t border (including window title), +border 1pixel+ to use a 1-pixel border (no window title) and +border none+ to make the client borderless. -//////////////////////////////////////////////////////////////////////////// -TODO: not yet implemented There is also +border toggle+ which will toggle the different border styles. -//////////////////////////////////////////////////////////////////////////// *Examples*: ---------------------------- diff --git a/src/cmdparse.y b/src/cmdparse.y index 32683112..1c65fbc5 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -597,7 +597,10 @@ border: TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); - current->con->border_style = $2; + if ($2 == TOK_TOGGLE) { + current->con->border_style++; + current->con->border_style %= 3; + } else current->con->border_style = $2; } tree_render(); @@ -608,6 +611,7 @@ border_style: TOK_NORMAL { $$ = BS_NORMAL; } | TOK_NONE { $$ = BS_NONE; } | TOK_1PIXEL { $$ = BS_1PIXEL; } + | TOK_TOGGLE { $$ = TOK_TOGGLE; } ; move: diff --git a/testcases/t/69-border-toggle.t b/testcases/t/69-border-toggle.t new file mode 100644 index 00000000..aec8df6c --- /dev/null +++ b/testcases/t/69-border-toggle.t @@ -0,0 +1,40 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Tests if the 'border toggle' command works correctly +# +use i3test; + +my $tmp = fresh_workspace; + +cmd 'open'; + +my @nodes = @{get_ws_content($tmp)}; +is(@nodes, 1, 'one container on this workspace'); +is($nodes[0]->{border}, 'normal', 'border style normal'); + +cmd 'border 1pixel'; +@nodes = @{get_ws_content($tmp)}; +is($nodes[0]->{border}, '1pixel', 'border style 1pixel'); + +cmd 'border none'; +@nodes = @{get_ws_content($tmp)}; +is($nodes[0]->{border}, 'none', 'border style none'); + +cmd 'border normal'; +@nodes = @{get_ws_content($tmp)}; +is($nodes[0]->{border}, 'normal', 'border style back to normal'); + +cmd 'border toggle'; +@nodes = @{get_ws_content($tmp)}; +is($nodes[0]->{border}, 'none', 'border style none'); + +cmd 'border toggle'; +@nodes = @{get_ws_content($tmp)}; +is($nodes[0]->{border}, '1pixel', 'border style 1pixel'); + +cmd 'border toggle'; +@nodes = @{get_ws_content($tmp)}; +is($nodes[0]->{border}, 'normal', 'border style back to normal'); + +done_testing; From fb9d77305e96e78247b25138978d9c6708446499 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 10 Jun 2011 18:27:20 +0200 Subject: [PATCH 680/867] Implement 'fullscreen global' --- docs/userguide | 4 ---- include/con.h | 4 ++-- include/tree.h | 4 ++-- src/cmdparse.y | 13 +++++++++---- src/con.c | 26 +++++++++++++++++--------- src/handlers.c | 2 +- src/ipc.c | 2 +- src/main.c | 14 ++++++++++++-- src/manage.c | 8 +++++--- src/render.c | 15 ++++++++++++--- src/tree.c | 24 ++++++++++++++++-------- src/workspace.c | 2 +- src/x.c | 2 +- 13 files changed, 79 insertions(+), 41 deletions(-) diff --git a/docs/userguide b/docs/userguide index 0cdb2566..6c5ba10b 100644 --- a/docs/userguide +++ b/docs/userguide @@ -99,12 +99,8 @@ image:modes.png[Container modes] To display a window fullscreen or to go out of fullscreen mode again, press +mod+f+. -///////////////////////////////////////////////////////////////////////////// -TODO: not yet implemented - There is also a global fullscreen mode in i3 in which the client will use all available outputs. To use it, or to get out of it again, press +mod+Shift+f+. -///////////////////////////////////////////////////////////////////////////// === Opening other applications diff --git a/include/con.h b/include/con.h index 37bca3bb..6ce7bf84 100644 --- a/include/con.h +++ b/include/con.h @@ -53,7 +53,7 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation); * Returns the first fullscreen node below this node. * */ -Con *con_get_fullscreen_con(Con *con); +Con *con_get_fullscreen_con(Con *con, int fullscreen_mode); /** * Returns true if the node is floating. @@ -126,7 +126,7 @@ void con_fix_percent(Con *con); * entered when there already is a fullscreen container on this workspace. * */ -void con_toggle_fullscreen(Con *con); +void con_toggle_fullscreen(Con *con, int fullscreen_mode); /** * Moves the given container to the currently focused container on the given diff --git a/include/tree.h b/include/tree.h index b66aa3f7..b9bf7f54 100644 --- a/include/tree.h +++ b/include/tree.h @@ -18,7 +18,7 @@ extern struct all_cons_head all_cons; * assigning a workspace to each RandR output. * */ -void tree_init(); +void tree_init(xcb_get_geometry_reply_t *geometry); /** * Opens an empty container in the current container @@ -77,7 +77,7 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent); * Loads tree from ~/.i3/_restart.json (used for in-place restarts). * */ -bool tree_restore(const char *path); +bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry); /** * tree_flatten() removes pairs of redundant split containers, e.g.: diff --git a/src/cmdparse.y b/src/cmdparse.y index 1c65fbc5..fbc2307f 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -173,6 +173,7 @@ char *parse_cmd(const char *new) { %type direction %type split_direction +%type fullscreen_mode %type level %type boolean %type border_style @@ -521,23 +522,27 @@ open: ; fullscreen: - TOK_FULLSCREEN + TOK_FULLSCREEN fullscreen_mode { - printf("toggling fullscreen\n"); + printf("toggling fullscreen, mode = %s\n", ($2 == CF_OUTPUT ? "normal" : "global")); owindow *current; - HANDLE_EMPTY_MATCH; TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); - con_toggle_fullscreen(current->con); + con_toggle_fullscreen(current->con, $2); } tree_render(); } ; +fullscreen_mode: + /* empty */ { $$ = CF_OUTPUT; } + | TOK_GLOBAL { $$ = CF_GLOBAL; } + ; + split: TOK_SPLIT split_direction { diff --git a/src/con.c b/src/con.c index 1a45076a..185cbd3f 100644 --- a/src/con.c +++ b/src/con.c @@ -290,7 +290,7 @@ struct bfs_entry { * Returns the first fullscreen node below this node. * */ -Con *con_get_fullscreen_con(Con *con) { +Con *con_get_fullscreen_con(Con *con, int fullscreen_mode) { Con *current, *child; /* TODO: is breadth-first-search really appropriate? (check as soon as @@ -303,7 +303,7 @@ Con *con_get_fullscreen_con(Con *con) { while (!TAILQ_EMPTY(&bfs_head)) { entry = TAILQ_FIRST(&bfs_head); current = entry->con; - if (current != con && current->fullscreen_mode != CF_NONE) { + if (current != con && current->fullscreen_mode == fullscreen_mode) { /* empty the queue */ while (!TAILQ_EMPTY(&bfs_head)) { entry = TAILQ_FIRST(&bfs_head); @@ -490,7 +490,7 @@ void con_fix_percent(Con *con) { * entered when there already is a fullscreen container on this workspace. * */ -void con_toggle_fullscreen(Con *con) { +void con_toggle_fullscreen(Con *con, int fullscreen_mode) { Con *workspace, *fullscreen; if (con->type == CT_WORKSPACE) { @@ -501,19 +501,27 @@ void con_toggle_fullscreen(Con *con) { DLOG("toggling fullscreen for %p / %s\n", con, con->name); if (con->fullscreen_mode == CF_NONE) { /* 1: check if there already is a fullscreen con */ - workspace = con_get_workspace(con); - if ((fullscreen = con_get_fullscreen_con(workspace)) != NULL) { + if (fullscreen_mode == CF_GLOBAL) + fullscreen = con_get_fullscreen_con(croot, CF_GLOBAL); + else { + workspace = con_get_workspace(con); + fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT); + } + if (fullscreen != NULL) { LOG("Not entering fullscreen mode, container (%p/%s) " "already is in fullscreen mode\n", fullscreen, fullscreen->name); - } else { - /* 2: enable fullscreen */ - con->fullscreen_mode = CF_OUTPUT; + goto update_netwm_state; } + + /* 2: enable fullscreen */ + con->fullscreen_mode = fullscreen_mode; } else { /* 1: disable fullscreen */ con->fullscreen_mode = CF_NONE; } + +update_netwm_state: DLOG("mode now: %d\n", con->fullscreen_mode); /* update _NET_WM_STATE if this container has a window */ @@ -797,7 +805,7 @@ Rect con_border_style_rect(Con *con) { * */ int con_border_style(Con *con) { - Con *fs = con_get_fullscreen_con(con->parent); + Con *fs = con_get_fullscreen_con(con->parent, CF_OUTPUT); if (fs == con) { DLOG("this one is fullscreen! overriding BS_NONE\n"); return BS_NONE; diff --git a/src/handlers.c b/src/handlers.c index eaf71ddb..b97dd043 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -648,7 +648,7 @@ static int handle_client_message(xcb_client_message_event_t *event) { (event->data.data32[0] == _NET_WM_STATE_ADD || event->data.data32[0] == _NET_WM_STATE_TOGGLE))) { DLOG("toggling fullscreen\n"); - con_toggle_fullscreen(con); + con_toggle_fullscreen(con, CF_OUTPUT); } tree_render(); diff --git a/src/ipc.c b/src/ipc.c index fa3513bc..1dd9512a 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -437,7 +437,7 @@ IPC_HANDLER(get_outputs) { ystr("current_workspace"); Con *ws = NULL; - if (output->con && (ws = con_get_fullscreen_con(output->con))) + if (output->con && (ws = con_get_fullscreen_con(output->con, CF_OUTPUT))) ystr(ws->name); else y(null); diff --git a/src/main.c b/src/main.c index c079d8dc..b4ed4a1a 100644 --- a/src/main.c +++ b/src/main.c @@ -257,6 +257,7 @@ int main(int argc, char *argv[]) { xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); root = root_screen->root; root_depth = root_screen->root_depth; + xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root); load_configuration(conn, override_configpath, false); if (only_check_config) { @@ -284,6 +285,13 @@ int main(int argc, char *argv[]) { cookie = xcb_change_window_attributes_checked(conn, root, mask, values); check_error(conn, cookie, "Another window manager seems to be running"); + xcb_get_geometry_reply_t *greply = xcb_get_geometry_reply(conn, gcookie, NULL); + if (greply == NULL) { + ELOG("Could not get geometry of the root window, exiting\n"); + return 1; + } + DLOG("root geometry reply: (%d, %d) %d x %d\n", greply->x, greply->y, greply->width, greply->height); + /* Place requests for the atoms we need as soon as possible */ #define xmacro(atom) \ xcb_intern_atom_cookie_t atom ## _cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom); @@ -368,13 +376,15 @@ int main(int argc, char *argv[]) { bool needs_tree_init = true; if (layout_path) { LOG("Trying to restore the layout from %s...", layout_path); - needs_tree_init = !tree_restore(layout_path); + needs_tree_init = !tree_restore(layout_path, greply); if (delete_layout_path) unlink(layout_path); free(layout_path); } if (needs_tree_init) - tree_init(); + tree_init(greply); + + free(greply); if (force_xinerama) { xinerama_init(); diff --git a/src/manage.c b/src/manage.c index 85beb4cb..e609501a 100644 --- a/src/manage.c +++ b/src/manage.c @@ -251,7 +251,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki free(name); Con *ws = con_get_workspace(nc); - Con *fs = (ws ? con_get_fullscreen_con(ws) : NULL); + Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL); + if (fs == NULL) + fs = con_get_fullscreen_con(croot, CF_GLOBAL); if (fs == NULL) { DLOG("Not in fullscreen mode, focusing\n"); @@ -293,7 +295,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if (config.popup_during_fullscreen == PDF_LEAVE_FULLSCREEN && fs != NULL) { LOG("There is a fullscreen window, leaving fullscreen mode\n"); - con_toggle_fullscreen(fs); + con_toggle_fullscreen(fs, CF_OUTPUT); } } @@ -331,7 +333,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki reply = xcb_get_property_reply(conn, state_cookie, NULL); if (xcb_reply_contains_atom(reply, A__NET_WM_STATE_FULLSCREEN)) - con_toggle_fullscreen(nc); + con_toggle_fullscreen(nc, CF_OUTPUT); /* Put the client inside the save set. Upon termination (whether killed or * normal exit does not matter) of the window manager, these clients will diff --git a/src/render.c b/src/render.c index a59d418b..b1388b31 100644 --- a/src/render.c +++ b/src/render.c @@ -42,8 +42,8 @@ static void render_l_output(Con *con) { /* We need to find out if there is a fullscreen con on the current workspace * and take the short-cut to render it directly (the user does not want to * see the dockareas in that case) */ - Con *ws = con_get_fullscreen_con(content); - Con *fullscreen = con_get_fullscreen_con(ws); + Con *ws = con_get_fullscreen_con(content, CF_OUTPUT); + Con *fullscreen = con_get_fullscreen_con(ws, CF_OUTPUT); if (fullscreen) { DLOG("got fs node: %p\n", fullscreen); fullscreen->rect = con->rect; @@ -185,7 +185,10 @@ void render_con(Con *con, bool render_fullscreen) { } /* Check for fullscreen nodes */ - Con *fullscreen = (con->type == CT_OUTPUT ? NULL : con_get_fullscreen_con(con)); + Con *fullscreen = NULL; + if (con->type != CT_OUTPUT) { + fullscreen = con_get_fullscreen_con(con, (con->type == CT_ROOT ? CF_GLOBAL : CF_OUTPUT)); + } if (fullscreen) { DLOG("got fs node: %p\n", fullscreen); fullscreen->rect = rect; @@ -222,6 +225,12 @@ void render_con(Con *con, bool render_fullscreen) { if (con->layout == L_OUTPUT) { render_l_output(con); + } else if (con->type == CT_ROOT) { + DLOG("Root node, rendering outputs\n"); + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + render_con(child, false); + } } else { /* FIXME: refactor this into separate functions: */ diff --git a/src/tree.c b/src/tree.c index a32723df..7e006485 100644 --- a/src/tree.c +++ b/src/tree.c @@ -13,7 +13,7 @@ struct all_cons_head all_cons = TAILQ_HEAD_INITIALIZER(all_cons); * Loads tree from ~/.i3/_restart.json (used for in-place restarts). * */ -bool tree_restore(const char *path) { +bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) { char *globbed = resolve_tilde(path); if (!path_exists(globbed)) { @@ -24,6 +24,12 @@ bool tree_restore(const char *path) { /* TODO: refactor the following */ croot = con_new(NULL, NULL); + croot->rect = (Rect){ + geometry->x, + geometry->y, + geometry->width, + geometry->height + }; focused = croot; tree_append_json(globbed); @@ -44,11 +50,17 @@ bool tree_restore(const char *path) { * root node are created in randr.c for each Output. * */ -void tree_init() { +void tree_init(xcb_get_geometry_reply_t *geometry) { croot = con_new(NULL, NULL); FREE(croot->name); croot->name = "root"; croot->type = CT_ROOT; + croot->rect = (Rect){ + geometry->x, + geometry->y, + geometry->width, + geometry->height + }; } /* @@ -343,12 +355,8 @@ void tree_render() { mark_unmapped(croot); croot->mapped = true; - /* We start rendering at an output */ - Con *output; - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - DLOG("output %p / %s\n", output, output->name); - render_con(output, false); - } + render_con(croot, false); + x_push_changes(croot); DLOG("-- END RENDERING --\n"); } diff --git a/src/workspace.c b/src/workspace.c index ec57df8d..4021dd14 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -95,7 +95,7 @@ bool workspace_is_visible(Con *ws) { Con *output = con_get_output(ws); if (output == NULL) return false; - Con *fs = con_get_fullscreen_con(output); + Con *fs = con_get_fullscreen_con(output, CF_OUTPUT); LOG("workspace visible? fs = %p, ws = %p\n", fs, ws); return (fs == ws); } diff --git a/src/x.c b/src/x.c index 0228cd23..64f5394b 100644 --- a/src/x.c +++ b/src/x.c @@ -308,7 +308,7 @@ void x_draw_decoration(Con *con) { /* If the con is in fullscreen mode, the decoration height we work with is set to 0 */ Rect deco_rect = con->deco_rect; - if (con_get_fullscreen_con(parent) == con) + if (con_get_fullscreen_con(parent, CF_OUTPUT) == con) deco_rect.height = 0; /* 2: draw the client.background, but only for the parts around the client_rect */ From b35ff6a7c98bc983e9cd3745a5faa6207936c381 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 11 Jun 2011 14:36:09 +0200 Subject: [PATCH 681/867] Bugfix: Correctly attach new output cons to the root con (Thanks mseed) --- src/randr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/randr.c b/src/randr.c index 4a33458c..f13cab67 100644 --- a/src/randr.c +++ b/src/randr.c @@ -198,6 +198,7 @@ void output_init_con(Output *output) { con->name = sstrdup(output->name); con->type = CT_OUTPUT; con->layout = L_OUTPUT; + con_fix_percent(croot); } con->rect = output->rect; output->con = con; From 931a5c749a7770188594501e63e58d659c44fd1f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 11 Jun 2011 14:39:13 +0200 Subject: [PATCH 682/867] default config: remove ipc-socket directive, has no effect anyways MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ipc socket directive needs a path, otherwise it’s useless. A socket will be created in any case. --- i3.config | 4 ---- 1 file changed, 4 deletions(-) diff --git a/i3.config b/i3.config index 55bd5cb8..026442e0 100644 --- a/i3.config +++ b/i3.config @@ -7,10 +7,6 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 # Use Mouse+Mod1 to drag floating windows to their wanted position floating_modifier Mod1 -# Enable Inter Process Communication (IPC) for i3bar, i3msg, etc. -# socket will be stored in /tmp/i3-/ipc-socket. -ipc-socket - # Open empty container bindsym Mod1+Shift+Return open From d641e1da3ba1586bc563f09e216ee7577d4a8b6f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 11 Jun 2011 19:15:16 +0200 Subject: [PATCH 683/867] =?UTF-8?q?Don=E2=80=99t=20force=20wrapping=20when?= =?UTF-8?q?=20focusing=20in=20a=20direction=20would=20work=20(+test)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Think of the following layout: ------------- | tab | | | con | win | | | | ------------- The tabbed container on the left has two children. Assume you have focused the second/right child in the tabbed container. i3 used to focus the first/left container of the tabbed container when using 'focus right' (it wrapped focus). With this commit, the default behaviour is to instead focus the window on the right of the screen. The intention is to make focus switching more intuitive, especially with tabbed containers supporting 'focus left'/'focus right' in tree. You should end up using less 'focus parent' :). You can force the old behaviour with 'force_focus_wrapping true' in your config. Code coverage is 62.5% with this commit. --- include/config.h | 10 +++ src/cfgparse.l | 1 + src/cfgparse.y | 10 +++ src/tree.c | 76 +++++++++++------- testcases/t/70-force_focus_wrapping.t | 110 ++++++++++++++++++++++++++ 5 files changed, 180 insertions(+), 27 deletions(-) create mode 100644 testcases/t/70-force_focus_wrapping.t diff --git a/include/config.h b/include/config.h index f05de324..9ba5e0f9 100644 --- a/include/config.h +++ b/include/config.h @@ -111,6 +111,16 @@ struct Config { * comes with i3. Thus, you can turn it off entirely. */ bool disable_workspace_bar; + /** Think of the following layout: Horizontal workspace with a tabbed + * con on the left of the screen and a terminal on the right of the + * screen. You are in the second container in the tabbed container and + * focus to the right. By default, i3 will set focus to the terminal on + * the right. If you are in the first container in the tabbed container + * however, focusing to the left will wrap. This option forces i3 to + * always wrap, which will result in you having to use "focus parent" + * more often. */ + bool force_focus_wrapping; + /** The default border style for new windows. */ border_style_t default_border; diff --git a/src/cfgparse.l b/src/cfgparse.l index fd9613f0..9194fbe6 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -122,6 +122,7 @@ normal { return TOK_NORMAL; } none { return TOK_NONE; } 1pixel { return TOK_1PIXEL; } focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; } +force_focus_wrapping { return TOK_FORCE_FOCUS_WRAPPING; } workspace_bar { return TOKWORKSPACEBAR; } popup_during_fullscreen { return TOK_POPUP_DURING_FULLSCREEN; } ignore { return TOK_IGNORE; } diff --git a/src/cfgparse.y b/src/cfgparse.y index d6eb12cd..4b443b89 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -235,6 +235,7 @@ void parse_file(const char *f) { %token TOK_NONE "none" %token TOK_1PIXEL "1pixel" %token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse" +%token TOK_FORCE_FOCUS_WRAPPING "force_focus_wrapping" %token TOKWORKSPACEBAR "workspace_bar" %token TOK_DEFAULT "default" %token TOK_STACKING "stacking" @@ -285,6 +286,7 @@ line: | workspace_layout | new_window | focus_follows_mouse + | force_focus_wrapping | workspace_bar | workspace | assign @@ -592,6 +594,14 @@ focus_follows_mouse: } ; +force_focus_wrapping: + TOK_FORCE_FOCUS_WRAPPING bool + { + DLOG("force focus wrapping = %d\n", $2); + config.force_focus_wrapping = $2; + } + ; + workspace_bar: TOKWORKSPACEBAR bool { diff --git a/src/tree.c b/src/tree.c index 7e006485..caf29678 100644 --- a/src/tree.c +++ b/src/tree.c @@ -362,49 +362,71 @@ void tree_render() { } /* - * Changes focus in the given way (next/previous) and given orientation - * (horizontal/vertical). + * Recursive function to walk the tree until a con can be found to focus. * */ -void tree_next(char way, orientation_t orientation) { - /* 1: get the first parent with the same orientation */ - Con *parent = focused->parent; - while (focused->type != CT_WORKSPACE && - (con_orientation(parent) != orientation || - con_num_children(parent) == 1)) { - LOG("need to go one level further up\n"); - /* if the current parent is an output, we are at a workspace - * and the orientation still does not match */ - if (parent->type == CT_WORKSPACE) - return; - parent = parent->parent; +static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) { + /* Stop recursing at workspaces */ + if (con->type == CT_WORKSPACE) + return false; + + if (con->type == CT_FLOATING_CON) { + /* TODO: implement focus for floating windows */ + return false; } + + Con *parent = con->parent; + + /* If the orientation does not match or there is no other con to focus, we + * need to go higher in the hierarchy */ + if (con_orientation(parent) != orientation || + con_num_children(parent) == 1) + return _tree_next(parent, way, orientation, wrap); + Con *current = TAILQ_FIRST(&(parent->focus_head)); - assert(current != TAILQ_END(&(parent->focus_head))); - + /* TODO: when can the following happen (except for floating windows, which + * are handled above)? */ if (TAILQ_EMPTY(&(parent->nodes_head))) { - DLOG("Nothing to focus here, move along...\n"); - return; + DLOG("nothing to focus\n"); + return false; } - /* 2: chose next (or previous) */ Con *next; - if (way == 'n') { + if (way == 'n') next = TAILQ_NEXT(current, nodes); - /* if we are at the end of the list, we need to wrap */ - if (next == TAILQ_END(&(parent->nodes_head))) + else next = TAILQ_PREV(current, nodes_head, nodes); + + if (!next) { + if (!config.force_focus_wrapping) { + /* If there is no next/previous container, we check if we can focus one + * when going higher (without wrapping, though). If so, we are done, if + * not, we wrap */ + if (_tree_next(parent, way, orientation, false)) + return true; + + if (!wrap) + return false; + } + + if (way == 'n') next = TAILQ_FIRST(&(parent->nodes_head)); - } else { - next = TAILQ_PREV(current, nodes_head, nodes); - /* if we are at the end of the list, we need to wrap */ - if (next == TAILQ_END(&(parent->nodes_head))) - next = TAILQ_LAST(&(parent->nodes_head), nodes_head); + else next = TAILQ_LAST(&(parent->nodes_head), nodes_head); } /* 3: focus choice comes in here. at the moment we will go down * until we find a window */ /* TODO: check for window, atm we only go down as far as possible */ con_focus(con_descend_focused(next)); + return true; +} + +/* + * Changes focus in the given way (next/previous) and given orientation + * (horizontal/vertical). + * + */ +void tree_next(char way, orientation_t orientation) { + _tree_next(focused, way, orientation, true); } /* diff --git a/testcases/t/70-force_focus_wrapping.t b/testcases/t/70-force_focus_wrapping.t new file mode 100644 index 00000000..fededf58 --- /dev/null +++ b/testcases/t/70-force_focus_wrapping.t @@ -0,0 +1,110 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3 +# +# Tests if the 'force_focus_wrapping' config directive works correctly. +# +use i3test; +use Cwd qw(abs_path); +use Proc::Background; +use File::Temp qw(tempfile tempdir); +use X11::XCB qw(:all); +use X11::XCB::Connection; + +my $x = X11::XCB::Connection->new; + +# assuming we are run by complete-run.pl +my $i3_path = abs_path("../i3"); + +##################################################################### +# 1: test the wrapping behaviour without force_focus_wrapping +##################################################################### + +my ($fh, $tmpfile) = tempfile(); +say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; +say $fh "ipc-socket /tmp/nestedcons"; +close($fh); + +diag("Starting i3"); +my $i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null"; +my $process = Proc::Background->new($i3cmd); +sleep 1; + +diag("pid = " . $process->pid); + +my $tmp = fresh_workspace; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + +my $first = open_standard_window($x); +my $second = open_standard_window($x); + +cmd 'layout tabbed'; +cmd 'focus parent'; + +my $third = open_standard_window($x); +is($x->input_focus, $third->id, 'third window focused'); + +cmd 'focus left'; +is($x->input_focus, $second->id, 'second window focused'); + +cmd 'focus left'; +is($x->input_focus, $first->id, 'first window focused'); + +# now test the wrapping +cmd 'focus left'; +is($x->input_focus, $second->id, 'second window focused'); + +# but focusing right should not wrap now, but instead focus the third window +cmd 'focus right'; +is($x->input_focus, $third->id, 'third window focused'); + +exit_gracefully($process->pid); + +##################################################################### +# 2: test the wrapping behaviour with force_focus_wrapping +##################################################################### + +($fh, $tmpfile) = tempfile(); +say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; +say $fh "ipc-socket /tmp/nestedcons"; +say $fh "force_focus_wrapping true"; +close($fh); + +diag("Starting i3"); +$i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null"; +$process = Proc::Background->new($i3cmd); +sleep 1; + +diag("pid = " . $process->pid); + +$tmp = fresh_workspace; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + +$first = open_standard_window($x); +$second = open_standard_window($x); + +cmd 'layout tabbed'; +cmd 'focus parent'; + +$third = open_standard_window($x); +is($x->input_focus, $third->id, 'third window focused'); + +cmd 'focus left'; +is($x->input_focus, $second->id, 'second window focused'); + +cmd 'focus left'; +is($x->input_focus, $first->id, 'first window focused'); + +# now test the wrapping +cmd 'focus left'; +is($x->input_focus, $second->id, 'second window focused'); + +# focusing right should now be forced to wrap +cmd 'focus right'; +is($x->input_focus, $first->id, 'first window focused'); + +exit_gracefully($process->pid); + +done_testing; From 49f1c2d8ca2a2b3a77a4d0ee5f5ed74b7e41699f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 12 Jun 2011 13:01:16 +0200 Subject: [PATCH 684/867] add *.gcda and *.gcno (code coverage files) to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index f62aac51..0ae9eaeb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ tags loglevels.tmp *.swp +*.gcda +*.gcno testcases/testsuite-* testcases/latest *.output From e5c811248f68eb77f8558becc31042fc2faa4f03 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 13 Jun 2011 17:42:59 +0200 Subject: [PATCH 685/867] Bugfix: Fix floating assignments, extend test for the assign command (Thanks Tucos) --- src/cfgparse.y | 4 ++-- testcases/t/66-assign.t | 46 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 4b443b89..2258268f 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -692,7 +692,7 @@ assign: if (*workspace == '\0') { /* This assignment was *only* for floating */ assignment->type = A_COMMAND; - assignment->dest.command = sstrdup("mode floating"); + assignment->dest.command = sstrdup("floating enable"); TAILQ_INSERT_TAIL(&assignments, assignment, assignments); break; } else { @@ -700,7 +700,7 @@ assign: Assignment *floating = scalloc(sizeof(Assignment)); match_copy(&(floating->match), match); floating->type = A_COMMAND; - floating->dest.command = sstrdup("mode floating"); + floating->dest.command = sstrdup("floating enable"); TAILQ_INSERT_TAIL(&assignments, floating, assignments); } } diff --git a/testcases/t/66-assign.t b/testcases/t/66-assign.t index 56b90548..84d71930 100644 --- a/testcases/t/66-assign.t +++ b/testcases/t/66-assign.t @@ -162,4 +162,50 @@ ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws'); exit_gracefully($process->pid); +##################################################################### +# start a window and see that it gets assigned to a workspace which has content +# already, next to the existing node. +##################################################################### + +($fh, $tmpfile) = tempfile(); +say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; +say $fh "ipc-socket /tmp/nestedcons"; +say $fh q|assign "special" → ~|; +close($fh); + +diag("Starting i3"); +$i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null"; +$process = Proc::Background->new($i3cmd); +sleep 1; + +diag("pid = " . $process->pid); + +$tmp = fresh_workspace; + +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); +my $workspaces = get_workspace_names; +ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet'); + +my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#0000ff', +); + +$window->_create; +set_wm_class($window->id, 'special', 'special'); +$window->name('special window'); +$window->map; +sleep 0.25; + +my $content = get_ws($tmp); +ok(@{$content->{nodes}} == 0, 'no tiling cons'); +ok(@{$content->{floating_nodes}} == 1, 'one floating con'); + +$window->destroy; + +exit_gracefully($process->pid); + +sleep 0.25; + done_testing; From 6fe3d842653c22d73ab308b5dc2a5d84f4a068ab Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 16 Jun 2011 18:58:49 +0200 Subject: [PATCH 686/867] config: change order, normalize comments, more comments, remove advanced/experimental bindings --- i3.config | 90 +++++++++++++++++++++++++------------------------------ 1 file changed, 40 insertions(+), 50 deletions(-) diff --git a/i3.config b/i3.config index 026442e0..232dce41 100644 --- a/i3.config +++ b/i3.config @@ -1,62 +1,22 @@ # This configuration file was written for the NEO layout. If you are using a # different layout, you should change it. -# ISO 10646 = Unicode +# font for window titles. ISO 10646 = Unicode font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 -# Use Mouse+Mod1 to drag floating windows to their wanted position +# use Mouse+Mod1 to drag floating windows to their wanted position floating_modifier Mod1 -# Open empty container -bindsym Mod1+Shift+Return open - -# Start terminal +# start a terminal bindsym Mod1+Return exec /usr/bin/urxvt -# Start dmenu -bindsym Mod1+p exec /usr/bin/dmenu_run - -# Horizontal orientation -bindsym Mod1+h split h - -# Vertical orientation -bindsym Mod1+v split v - -# Enter fullscreen mode for the focused container -bindsym Mod1+f fullscreen - -# Stacking (Mod1+s) -bindsym Mod1+s layout stacking - -# Tabbed (Mod1+w) -bindsym Mod1+w layout tabbed - -# Default (Mod1+l) -bindsym Mod1+l layout default - -# toggle tiling / floating -bindsym Mod1+Shift+space floating toggle - -# focus the parent container -bindsym Mod1+u focus parent - -# focus the child container -#bindsym Mod1+d focus child - -# Kill current window +# kill focused window bindsym Mod1+c kill -# Restore saved JSON layout -bindsym Mod1+y append_layout /home/michael/i3/layout.json +# start dmenu (a program launcher) +bindsym Mod1+p exec /usr/bin/dmenu_run -# Reload the configuration file -bindsym Mod1+Shift+j reload -# Restart i3 inplace -bindsym Mod1+Shift+c restart -# Exit i3 (logs you out of your X session) -bindsym Mod1+Shift+l exit - -# Focus +# change focus bindsym Mod1+n focus left bindsym Mod1+r focus down bindsym Mod1+t focus up @@ -68,7 +28,7 @@ bindsym Mod1+Down focus down bindsym Mod1+Up focus up bindsym Mod1+Right focus right -# Move +# move focused window bindsym Mod1+Shift+n move left bindsym Mod1+Shift+r move down bindsym Mod1+Shift+t move up @@ -80,7 +40,30 @@ bindsym Mod1+Shift+Right move right bindsym Mod1+Shift+Down move down bindsym Mod1+Shift+Up move up -# Switch to workspace +# split in horizontal orientation +bindsym Mod1+h split h + +# split in vertical orientation +bindsym Mod1+v split v + +# enter fullscreen mode for the focused container +bindsym Mod1+f fullscreen + +# change container layout (stacked, tabbed, default) +bindsym Mod1+s layout stacking +bindsym Mod1+w layout tabbed +bindsym Mod1+l layout default + +# toggle tiling / floating +bindsym Mod1+Shift+space floating toggle + +# focus the parent container +bindsym Mod1+u focus parent + +# focus the child container +#bindsym Mod1+d focus child + +# switch to workspace bindsym Mod1+1 workspace 1 bindsym Mod1+2 workspace 2 bindsym Mod1+3 workspace 3 @@ -92,7 +75,7 @@ bindsym Mod1+8 workspace 8 bindsym Mod1+9 workspace 9 bindsym Mod1+0 workspace 10 -# Move focused container to workspace +# move focused container to workspace bindsym Mod1+Shift+1 move workspace 1 bindsym Mod1+Shift+2 move workspace 2 bindsym Mod1+Shift+3 move workspace 3 @@ -103,3 +86,10 @@ bindsym Mod1+Shift+7 move workspace 7 bindsym Mod1+Shift+8 move workspace 8 bindsym Mod1+Shift+9 move workspace 9 bindsym Mod1+Shift+0 move workspace 10 + +# reload the configuration file +bindsym Mod1+Shift+j reload +# restart i3 inplace (preserves your layout/session, can be used to upgrade i3) +bindsym Mod1+Shift+c restart +# exit i3 (logs you out of your X session) +bindsym Mod1+Shift+l exit From 9611e46eb6e9e5dc3a527c85cc8e0abbdcd44713 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 1 Jul 2011 00:37:30 +0200 Subject: [PATCH 687/867] Bugfix: testcase was still using 'mode floating' instead of 'floating enable' --- testcases/t/35-floating-focus.t | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/testcases/t/35-floating-focus.t b/testcases/t/35-floating-focus.t index d037b9db..d0f8814f 100644 --- a/testcases/t/35-floating-focus.t +++ b/testcases/t/35-floating-focus.t @@ -18,8 +18,8 @@ my $second = open_standard_window($x); is($x->input_focus, $second->id, 'second window focused'); -cmd 'mode floating'; -cmd 'mode tiling'; +cmd 'floating enable'; +cmd 'floating disable'; is($x->input_focus, $second->id, 'second window still focused after mode toggle'); @@ -36,13 +36,13 @@ my $third = open_standard_window($x); # window 4 is($x->input_focus, $third->id, 'last container focused'); -cmd 'mode floating'; +cmd 'floating enable'; cmd '[id="' . $second->id . '"] focus'; is($x->input_focus, $second->id, 'second con focused'); -cmd 'mode floating'; +cmd 'floating enable'; # now kill the third one (it's floating). focus should stay unchanged cmd '[id="' . $third->id . '"] kill'; @@ -65,13 +65,13 @@ my $third = open_standard_window($x); # window 7 is($x->input_focus, $third->id, 'last container focused'); -cmd 'mode floating'; +cmd 'floating enable'; cmd '[id="' . $second->id . '"] focus'; is($x->input_focus, $second->id, 'second con focused'); -cmd 'mode floating'; +cmd 'floating enable'; # now kill the second one. focus should fall back to the third one, which is # also floating From 23d4917e435d94e8b7a30f58740fdd5b8dc82fec Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 1 Jul 2011 01:10:43 +0200 Subject: [PATCH 688/867] Bugfix: Correctly revert floating focus when killing the last floating window (+test) --- src/con.c | 7 +++++- testcases/t/35-floating-focus.t | 44 ++++++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/con.c b/src/con.c index 185cbd3f..7ce782bc 100644 --- a/src/con.c +++ b/src/con.c @@ -646,7 +646,12 @@ Con *con_next_focused(Con *con) { if (con->type == CT_FLOATING_CON) { DLOG("selecting next for CT_FLOATING_CON\n"); next = TAILQ_NEXT(con, floating_windows); - if (next == TAILQ_END(&(parent->floating_head))) { + DLOG("next = %p\n", next); + if (!next) { + next = TAILQ_PREV(con, floating_head, floating_windows); + DLOG("using prev, next = %p\n", next); + } + if (!next) { Con *ws = con_get_workspace(con); next = ws; DLOG("no more floating containers for next = %p, restoring workspace focus\n", next); diff --git a/testcases/t/35-floating-focus.t b/testcases/t/35-floating-focus.t index d0f8814f..7d5cf53b 100644 --- a/testcases/t/35-floating-focus.t +++ b/testcases/t/35-floating-focus.t @@ -59,9 +59,9 @@ is($x->input_focus, $second->id, 'second con still focused after killing third') $tmp = fresh_workspace; -$first = open_standard_window($x); # window 5 -$second = open_standard_window($x); # window 6 -my $third = open_standard_window($x); # window 7 +$first = open_standard_window($x, '#ff0000'); # window 5 +$second = open_standard_window($x, '#00ff00'); # window 6 +my $third = open_standard_window($x, '#0000ff'); # window 7 is($x->input_focus, $third->id, 'last container focused'); @@ -87,4 +87,42 @@ sleep 0.25; is($x->input_focus, $first->id, 'first con focused after killing all floating cons'); +############################################################################# +# 4: same test as 3, but with another split con +############################################################################# + +$tmp = fresh_workspace; + +$first = open_standard_window($x, '#ff0000'); # window 5 +cmd 'split v'; +cmd 'layout stacked'; +$second = open_standard_window($x, '#00ff00'); # window 6 +$third = open_standard_window($x, '#0000ff'); # window 7 + +is($x->input_focus, $third->id, 'last container focused'); + +cmd 'floating enable'; + +cmd '[id="' . $second->id . '"] focus'; + +is($x->input_focus, $second->id, 'second con focused'); + +cmd 'floating enable'; + +sleep 0.5; + +# now kill the second one. focus should fall back to the third one, which is +# also floating +cmd 'kill'; + +sleep 0.25; + +is($x->input_focus, $third->id, 'second con focused'); + +cmd 'kill'; + +sleep 0.25; + +is($x->input_focus, $first->id, 'first con focused after killing all floating cons'); + done_testing; From 71741d7620825c9176b17bdcbe21b3aac034970b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 4 Jul 2011 13:41:02 +0200 Subject: [PATCH 689/867] Bugfix: Only set ENTER_WINDOW event mask for mapped windows (fixes focus problems) Fixes focus problems when switching to empty workspaces or when going in/out of fullscreen. --- src/handlers.c | 3 ++- src/manage.c | 9 +++------ src/x.c | 47 +++++++++++++++++++++++++++++++++++------------ 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index b97dd043..583b0c5a 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -439,9 +439,10 @@ static int handle_screen_change(xcb_generic_event_t *e) { * */ static int handle_unmap_notify_event(xcb_unmap_notify_event_t *event) { + // XXX: this is commented out because in src/x.c we disable EnterNotify events /* we need to ignore EnterNotify events which will be generated because a * different window is visible now */ - add_ignore_event(event->sequence, XCB_ENTER_NOTIFY); + //add_ignore_event(event->sequence, XCB_ENTER_NOTIFY); DLOG("UnmapNotify for 0x%08x (received from 0x%08x), serial %d\n", event->window, event->event, event->sequence); Con *con = con_by_window_id(event->window); diff --git a/src/manage.c b/src/manage.c index e609501a..bcce8ab1 100644 --- a/src/manage.c +++ b/src/manage.c @@ -117,16 +117,14 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki goto out; } - uint32_t mask = 0; uint32_t values[1]; /* Set a temporary event mask for the new window, consisting only of * PropertyChange. We need to be notified of PropertyChanges because the * client can change its properties *after* we requested them but *before* * we actually reparented it and have set our final event mask. */ - mask = XCB_CW_EVENT_MASK; values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE; - xcb_change_window_attributes(conn, window, mask, values); + xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK, values); #define GET_PROPERTY(atom, len) xcb_get_property_unchecked(conn, false, window, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, len) @@ -326,9 +324,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki goto out; } - mask = XCB_CW_EVENT_MASK; - values[0] = CHILD_EVENT_MASK; - xcb_change_window_attributes(conn, window, mask, values); + values[0] = CHILD_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW; + xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK, values); xcb_flush(conn); reply = xcb_get_property_reply(conn, state_cookie, NULL); diff --git a/src/x.c b/src/x.c index 64f5394b..1201fb97 100644 --- a/src/x.c +++ b/src/x.c @@ -18,6 +18,7 @@ xcb_window_t focused_id = XCB_NONE; typedef struct con_state { xcb_window_t id; bool mapped; + bool unmap_now; bool child_mapped; /* For reparenting, we have a flag (need_reparent) and the X ID of the old @@ -78,9 +79,9 @@ void x_con_init(Con *con) { mask |= XCB_CW_OVERRIDE_REDIRECT; values[0] = 1; - /* We want to know when… */ + /* see include/xcb.h for the FRAME_EVENT_MASK */ mask |= XCB_CW_EVENT_MASK; - values[1] = FRAME_EVENT_MASK; + values[1] = FRAME_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW; Rect dims = { -15, -15, 10, 10 }; con->frame = create_window(conn, dims, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER, false, mask, values); @@ -612,21 +613,29 @@ void x_push_node(Con *con) { A_WM_STATE, A_WM_STATE, 32, 2, data); } + uint32_t values[1]; if (!state->child_mapped && con->window != NULL) { cookie = xcb_map_window(conn, con->window->id); + + /* We are interested in EnterNotifys as soon as the window is + * mapped */ + values[0] = CHILD_EVENT_MASK; + xcb_change_window_attributes(conn, con->window->id, XCB_CW_EVENT_MASK, values); DLOG("mapping child window (serial %d)\n", cookie.sequence); - /* Ignore enter_notifies which are generated when mapping */ - add_ignore_event(cookie.sequence, 0); state->child_mapped = true; } cookie = xcb_map_window(conn, con->frame); - DLOG("mapping container (serial %d)\n", cookie.sequence); - /* Ignore enter_notifies which are generated when mapping */ - add_ignore_event(cookie.sequence, 0); + + values[0] = FRAME_EVENT_MASK; + xcb_change_window_attributes(conn, con->frame, XCB_CW_EVENT_MASK, values); + + DLOG("mapping container %08x (serial %d)\n", con->frame, cookie.sequence); state->mapped = con->mapped; } + state->unmap_now = (state->mapped != con->mapped) && !con->mapped; + if (fake_notify) { DLOG("Sending fake configure notify\n"); fake_absolute_configure_notify(con); @@ -658,8 +667,7 @@ static void x_push_node_unmaps(Con *con) { /* map/unmap if map state changed, also ensure that the child window * is changed if we are mapped *and* in initial state (meaning the * container was empty before, but now got a child) */ - if ((state->mapped != con->mapped || (con->mapped && state->initial)) && - !con->mapped) { + if (state->unmap_now) { xcb_void_cookie_t cookie; if (con->window != NULL) { /* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */ @@ -677,8 +685,6 @@ static void x_push_node_unmaps(Con *con) { con->ignore_unmap++; DLOG("ignore_unmap for con %p (frame 0x%08x) now %d\n", con, con->frame, con->ignore_unmap); } - /* Ignore enter_notifies which are generated when unmapping */ - add_ignore_event(cookie.sequence, 0); state->mapped = con->mapped; } @@ -731,8 +737,10 @@ void x_push_changes(Con *con) { state->initial = false; } //DLOG("Re-enabling EnterNotify\n"); - values[0] = FRAME_EVENT_MASK; CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { + values[0] = FRAME_EVENT_MASK; + if (!state->mapped) + values[0] &= ~XCB_EVENT_MASK_ENTER_WINDOW; xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); } //DLOG("Done, EnterNotify re-enabled\n"); @@ -785,6 +793,21 @@ void x_push_changes(Con *con) { xcb_flush(conn); DLOG("\n\n ENDING CHANGES\n\n"); + /* Disable EnterWindow events for windows which will be unmapped in + * x_push_node_unmaps() now. Unmapping windows happens when switching + * workspaces. We want to avoid getting EnterNotifies during that phase + * because they would screw up our focus. One of these cases is having a + * stack with two windows. If the first window is focused and gets + * unmapped, the second one appears under the cursor and therefore gets an + * EnterNotify event. */ + values[0] = FRAME_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW; + CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { + if (!state->unmap_now) + continue; + xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); + } + + /* Push all pending unmaps */ x_push_node_unmaps(con); /* save the current stack as old stack */ From c408fef021aba127f4d627a1bbbd42f6cf04575b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 4 Jul 2011 17:09:52 +0200 Subject: [PATCH 690/867] Implement 'focus floating', 'focus tiling' and 'focus mode_toggle' (+test +docs) --- docs/userguide | 46 ++++++++++++++------------------- src/cmdparse.l | 1 + src/cmdparse.y | 40 ++++++++++++++++++++++++++++ testcases/t/35-floating-focus.t | 46 +++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 26 deletions(-) diff --git a/docs/userguide b/docs/userguide index 6c5ba10b..60d8d63d 100644 --- a/docs/userguide +++ b/docs/userguide @@ -627,6 +627,20 @@ bindsym Mod1+t floating toggle To change the focus, use the focus command: +focus left+, +focus right+, +focus down+ and +focus up+. +There are a few special parameters you can use for the focus command: + +parent:: + Sets focus to the +Parent Container+ of the current +Container+. +child:: + The opposite of +focus parent+, sets the focus to the last focused + child container. +floating:: + Sets focus to the last focused floating container. +tiling:: + Sets focus to the last focused tiling container. +mode_toggle:: + Toggles between floating/tiling containers. + For moving, use +move left+, +move right+, +move down+ and +move up+. *Examples*: @@ -637,6 +651,12 @@ bindsym Mod1+k focus down bindsym Mod1+l focus up bindsym Mod1+semicolon focus right +# Focus parent container +bindsym Mod1+u focus parent + +# Focus last floating/tiling container +bindsym Mod1+g focus mode_toggle + # Move client to the left, bottom, top, right: bindsym Mod1+j move left bindsym Mod1+k move down @@ -758,32 +778,6 @@ Alternatively, if you do not want to mess with +i3-input+, you could create seperate bindings for a specific set of labels and then only use those labels. /////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -TODO: not yet implemented -=== Traveling the focus stack - -This mechanism can be thought of as the opposite of the +jump+ command. -It travels the focus stack and jumps to the window which had focus previously. - -*Syntax*: --------------- -focus [number] | floating | tiling | ft --------------- - -Where +number+ by default is 1 meaning that the next client in the focus stack -will be selected. - -The special values have the following meaning: - -floating:: - The next floating window is selected. -tiling:: - The next tiling window is selected. -ft:: - If the current window is floating, the next tiling window will be - selected; and vice-versa. -/////////////////////////////////////////////////////////////////////////////// - === Changing border style To change the border of the current client, you can use +border normal+ to use the normal diff --git a/src/cmdparse.l b/src/cmdparse.l index 9a0bc903..898416c2 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -121,6 +121,7 @@ mode { BEGIN(WANT_QSTRING); return TOK_MODE; } tiling { return TOK_TILING; } floating { return TOK_FLOATING; } toggle { return TOK_TOGGLE; } +mode_toggle { return TOK_MODE_TOGGLE; } workspace { WS_STRING; return TOK_WORKSPACE; } focus { return TOK_FOCUS; } move { return TOK_MOVE; } diff --git a/src/cmdparse.y b/src/cmdparse.y index fbc2307f..95dc27b6 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -135,6 +135,7 @@ char *parse_cmd(const char *new) { %token TOK_MODE "mode" %token TOK_TILING "tiling" %token TOK_FLOATING "floating" +%token TOK_MODE_TOGGLE "mode_toggle" %token TOK_ENABLE "enable" %token TOK_DISABLE "disable" %token TOK_WORKSPACE "workspace" @@ -175,6 +176,7 @@ char *parse_cmd(const char *new) { %type split_direction %type fullscreen_mode %type level +%type window_mode %type boolean %type border_style %type layout_mode @@ -447,6 +449,38 @@ focus: tree_render(); } + | TOK_FOCUS window_mode + { + printf("should focus: "); + + if ($2 == TOK_TILING) + printf("tiling\n"); + else if ($2 == TOK_FLOATING) + printf("floating\n"); + else printf("mode toggle\n"); + + Con *ws = con_get_workspace(focused); + Con *current; + if (ws != NULL) { + int to_focus = $2; + if ($2 == TOK_MODE_TOGGLE) { + current = TAILQ_FIRST(&(ws->focus_head)); + if (current->type == CT_FLOATING_CON) + to_focus = TOK_TILING; + else to_focus = TOK_FLOATING; + } + TAILQ_FOREACH(current, &(ws->focus_head), focused) { + if ((to_focus == TOK_FLOATING && current->type != CT_FLOATING_CON) || + (to_focus == TOK_TILING && current->type == CT_FLOATING_CON)) + continue; + + con_focus(con_descend_focused(current)); + break; + } + } + + tree_render(); + } | TOK_FOCUS level { if ($2 == TOK_PARENT) @@ -457,6 +491,12 @@ focus: } ; +window_mode: + TOK_TILING { $$ = TOK_TILING; } + | TOK_FLOATING { $$ = TOK_FLOATING; } + | TOK_MODE_TOGGLE { $$ = TOK_MODE_TOGGLE; } + ; + level: TOK_PARENT { $$ = TOK_PARENT; } | TOK_CHILD { $$ = TOK_CHILD; } diff --git a/testcases/t/35-floating-focus.t b/testcases/t/35-floating-focus.t index 7d5cf53b..3f820ea5 100644 --- a/testcases/t/35-floating-focus.t +++ b/testcases/t/35-floating-focus.t @@ -125,4 +125,50 @@ sleep 0.25; is($x->input_focus, $first->id, 'first con focused after killing all floating cons'); +############################################################################# +# 5: see if the 'focus tiling' and 'focus floating' commands work +############################################################################# + +$tmp = fresh_workspace; + +$first = open_standard_window($x, '#ff0000'); # window 8 +$second = open_standard_window($x, '#00ff00'); # window 9 + +is($x->input_focus, $second->id, 'second container focused'); + +cmd 'floating enable'; + +is($x->input_focus, $second->id, 'second container focused'); + +cmd 'focus tiling'; + +sleep 0.25; + +is($x->input_focus, $first->id, 'first (tiling) container focused'); + +cmd 'focus floating'; + +sleep 0.25; + +is($x->input_focus, $second->id, 'second (floating) container focused'); + +cmd 'focus floating'; + +sleep 0.25; + +is($x->input_focus, $second->id, 'second (floating) container still focused'); + +cmd 'focus mode_toggle'; + +sleep 0.25; + +is($x->input_focus, $first->id, 'first (tiling) container focused'); + +cmd 'focus mode_toggle'; + +sleep 0.25; + +is($x->input_focus, $second->id, 'second (floating) container focused'); + + done_testing; From a2f297bd399baca0b6631a17881086d780da1f8d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 6 Jul 2011 13:56:58 +0200 Subject: [PATCH 691/867] Add script to migrate a v3 config to v4 (tree). Please test this! Run ./i3-migrate-config-to-v4.pl < ~/.i3/config > /tmp/i3.config and see if /tmp/i3.config is fine (especially check the comments starting with XXX, they are inserted by the script). --- i3-migrate-config-to-v4.pl | 317 +++++++++++++++++++++++++++++++ testcases/t/71-config-migrate.t | 325 ++++++++++++++++++++++++++++++++ 2 files changed, 642 insertions(+) create mode 100755 i3-migrate-config-to-v4.pl create mode 100644 testcases/t/71-config-migrate.t diff --git a/i3-migrate-config-to-v4.pl b/i3-migrate-config-to-v4.pl new file mode 100755 index 00000000..ecffa8d3 --- /dev/null +++ b/i3-migrate-config-to-v4.pl @@ -0,0 +1,317 @@ +#!/usr/bin/env perl +# vim:ts=4:sw=4:expandtab +# +# script to migrate an old config file (i3 < 4.0) to the new format (>= 4.0) +# this script only uses modules which come with perl 5.10 +# +# reads an i3 v3 config from stdin and spits out a v4 config on stdout +# exit codes: +# 0 = the input was a v3 config +# 1 = the input was already a v4 config +# (the config is printed to stdout nevertheless) +# +# © 2011 Michael Stapelberg and contributors, see LICENSE + +use strict; +use warnings; +use Getopt::Long; +use v5.10; + +# is this a version 3 config file? disables auto-detection +my $v3 = 0; +my $result = GetOptions('v3' => \$v3); + +# reads stdin +sub slurp { + local $/; + <>; +} + +my @unchanged = qw( + font + set + mode + exec + assign + floating_modifier + focus_follows_mouse + ipc-socket + ipc_socket + client.focused + client.focused_inactive + client.unfocused + client.urgent +); + +my %workspace_names; +my $workspace_bar = 1; + +my $input = slurp(); +my @lines = split /\n/, $input; + +# remove whitespaces in the beginning of lines +@lines = map { s/^[ \t]*//g; $_ } @lines; + +# Try to auto-detect if this is a v3 config file. +sub need_to_convert { + # If the user passed --v3, we need to convert in any case + return 1 if $v3; + + for my $line (@lines) { + # only v4 configfiles can use bindcode or workspace_layout + return 0 if $line =~ /^bindcode/ || $line =~ /^workspace_layout/; + + # have a look at bindings + next unless $line =~ /^bind/; + + my ($statement, $key, $command) = ($line =~ /([a-zA-Z_-]+)[ \t]+([^ \t]+)[ \t]+(.*)/); + return 0 if $command =~ /^layout/ || + $command =~ /^floating/ || + $command =~ /^workspace/ || + $command =~ /^focus (left|right|up|down)/ || + $command =~ /^border (normal|1pixel|borderless)/; + } + + return 1; +} + +if (!need_to_convert()) { + # If this is already a v4 config file, we will spit out the lines + # and exit with return code 1 + print $input; + exit 1; +} + +for my $line (@lines) { + # directly use comments and empty lines + if ($line =~ /^#/ || $line =~ /^$/) { + print "$line\n"; + next; + } + + my ($statement, $parameters) = ($line =~ /([a-zA-Z._-]+)(.*)/); + + # directly use lines which have not changed between 3.x and 4.x + if (!defined($statement) || (lc $statement ~~ @unchanged)) { + print "$line\n"; + next; + } + + # new_container changed only the statement name to workspace_layout + if ($statement eq 'new_container') { + # TODO: new_container stack-limit + print "workspace_layout$parameters\n"; + next; + } + + # workspace_bar is gone, you should use i3bar now + if ($statement eq 'workspace_bar') { + $workspace_bar = ($parameters =~ /[ \t+](yes|true|on|enable|active)/); + print "# XXX: REMOVED workspace_bar line. There is no internal workspace bar in v4.\n"; + next; + } + + # new_window changed the parameters from bb to borderless etc. + if ($statement eq 'new_window') { + if ($parameters =~ /bb/) { + print "new_window borderless\n"; + } elsif ($parameters =~ /bp/) { + print "new_window 1pixel\n"; + } elsif ($parameters =~ /bn/) { + print "new_window normal\n"; + } else { + print "# XXX: Invalid parameter for new_window, not touching line:\n"; + print "$line\n"; + } + next; + } + + # bar colors are obsolete, need to be configured in i3bar + if ($statement =~ /^bar\./) { + print "# XXX: REMOVED $statement, configure i3bar instead.\n"; + next; + } + + # one form of this is still ok (workspace assignments), the other (named workspaces) isn’t + if ($statement eq 'workspace') { + my ($number, $params) = ($parameters =~ /[ \t]+([0-9]+) (.+)/); + if ($params =~ /^output/) { + print "$line\n"; + next; + } else { + print "# XXX: workspace name will end up in the bindings, see below\n"; + $workspace_names{$number} = $params; + next; + } + } + + if ($statement eq 'bind' || $statement eq 'bindsym') { + convert_command($line); + next; + } + + print "# XXX: migration script could not handle line: $line\n"; +} + +# +# Converts a command (after bind/bindsym) +# +sub convert_command { + my ($line) = @_; + + my @unchanged_cmds = qw( + exec + mode + mark + kill + restart + reload + exit + stack-limit + ); + + my ($statement, $key, $command) = ($line =~ /([a-zA-Z_-]+)[ \t]+([^ \t]+)[ \t]+(.*)/); + + # turn 'bind' to 'bindcode' + $statement = 'bindcode' if $statement eq 'bind'; + + # check if it’s one of the unchanged commands + my ($cmd) = ($command =~ /([a-zA-Z_-]+)/); + if ($cmd ~~ @unchanged_cmds) { + print "$statement $key $command\n"; + return; + } + + # simple replacements + my @replace = ( + qr/^s/ => 'layout stacking', + qr/^d/ => 'layout default', + qr/^T/ => 'layout tabbed', + qr/^f($|[^go])/ => 'fullscreen', + qr/^fg/ => 'fullscreen global', + qr/^t/ => 'focus mode_toggle', + qr/^h/ => 'focus left', + qr/^j($|[^u])/ => 'focus down', + qr/^k/ => 'focus up', + qr/^l/ => 'focus right', + qr/^mh/ => 'move left', + qr/^mj/ => 'move down', + qr/^mk/ => 'move up', + qr/^ml/ => 'move right', + qr/^bn/ => 'border normal', + qr/^bp/ => 'border 1pixel', + qr/^bb/ => 'border borderless', + qr/^pw/ => 'workspace prev', + qr/^nw/ => 'workspace next', + ); + + my $replaced = 0; + for (my $c = 0; $c < @replace; $c += 2) { + if ($command =~ $replace[$c]) { + $command = $replace[$c+1]; + $replaced = 1; + last; + } + } + + # goto command is now obsolete due to criteria + focus command + if ($command =~ /^goto/) { + my ($mark) = ($command =~ /^goto (.*)/); + print qq|$statement $key [con_mark="$mark"] focus\n|; + return; + } + + # the jump command is also obsolete due to criteria + focus + if ($command =~ /^jump/) { + my ($params) = ($command =~ /^jump (.*)/); + if ($params =~ /^"/) { + # jump ["]window class[/window title]["] + ($params) = ($params =~ /^"([^"]+)"/); + + # check if a window title was specified + if ($params =~ m,/,) { + my ($class, $title) = ($params =~ m,([^/]+)/(.+),); + print qq|$statement $key [class="$class" title="$title"] focus\n|; + } else { + print qq|$statement $key [class="$params"] focus\n|; + } + return; + } else { + # jump [ column row ] + print "# XXX: jump workspace is obsolete in 4.x: $line\n"; + return; + } + } + + if (!$replaced && $command =~ /^focus/) { + my ($what) = ($command =~ /^focus (.*)/); + $what =~ s/[ \t]//g; + if ($what eq 'ft') { + $what = 'mode_toggle'; + } elsif ($what eq 'floating' || $what eq 'tiling') { + # those are unchanged + } else { + print "# XXX: focus is obsolete in 4.x: $line\n"; + return; + } + print qq|$statement $key focus $what\n|; + return; + } + + # the parameters of the resize command have changed + if ($command =~ /^resize/) { + # OLD: resize [+|-]\n") + # NEW: resize [ px] [or ppt] + my ($direction, $sign, $px) = ($command =~ /^resize (left|right|top|bottom) ([+-])([0-9]+)/); + my $cmd = 'resize '; + $cmd .= ($sign eq '+' ? 'grow' : 'shrink') . ' '; + $cmd .= "$direction "; + $cmd .= "$px px"; + print qq|$statement $key $cmd\n|; + return; + } + + # switch workspace + if ($command =~ /^[0-9]+/) { + my ($number) = ($command =~ /^([0-9]+)/); + if (exists $workspace_names{$number}) { + print qq|$statement $key workspace $workspace_names{$number}\n|; + return; + } else { + print qq|$statement $key workspace $number\n|; + return; + } + } + + # move to workspace + if ($command =~ /^m[0-9]+/) { + my ($number) = ($command =~ /^m([0-9]+)/); + if (exists $workspace_names{$number}) { + print qq|$statement $key move workspace $workspace_names{$number}\n|; + return; + } else { + print qq|$statement $key move workspace $number\n|; + return; + } + } + + # With Container-commands are now obsolete due to different abstraction + if ($command =~ /^wc/) { + print "# XXX: 'with container' commands are obsolete in 4.x: $line\n"; + return; + } + + if ($replaced) { + print "$statement $key $command\n"; + } else { + print "# XXX: migration script could not handle command: $line\n"; + } +} + + +# add an i3bar invocation automatically if no 'workspace_bar no' was found +if ($workspace_bar) { + print "\n"; + print "# XXX: Automatically added a call to i3bar to provide a workspace bar\n"; + print "exec i3status | i3bar -d\n"; +} diff --git a/testcases/t/71-config-migrate.t b/testcases/t/71-config-migrate.t new file mode 100644 index 00000000..9903d545 --- /dev/null +++ b/testcases/t/71-config-migrate.t @@ -0,0 +1,325 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3 +# +# Tests if i3-migrate-config-to-v4.pl correctly migrates all config file +# directives and commands +# +use i3test; +use Cwd qw(abs_path); +use Proc::Background; +use File::Temp qw(tempfile tempdir); +use POSIX qw(getuid); +use Data::Dumper; +use v5.10; + +# reads in a whole file +sub slurp { + open my $fh, '<', shift; + local $/; + <$fh>; +} + +sub migrate_config { + my ($config) = @_; + + my ($fh, $tmpfile) = tempfile(); + print $fh $config; + close($fh); + + my $cmd = "sh -c 'exec " . abs_path("../i3-migrate-config-to-v4.pl") . " --v3 <$tmpfile'"; + return [ split /\n/, qx($cmd) ]; +} + +sub line_exists { + my ($lines, $pattern) = @_; + + for my $line (@$lines) { + return 1 if $line =~ $pattern; + } + + return 0 +} + +##################################################################### +# check that some directives remain untouched +##################################################################### + +my $input = < Date: Wed, 6 Jul 2011 17:50:14 +0200 Subject: [PATCH 692/867] migrate-config: include the old bar color line in new config (Thanks cloud) --- i3-migrate-config-to-v4.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/i3-migrate-config-to-v4.pl b/i3-migrate-config-to-v4.pl index ecffa8d3..7dc4e27e 100755 --- a/i3-migrate-config-to-v4.pl +++ b/i3-migrate-config-to-v4.pl @@ -129,6 +129,7 @@ for my $line (@lines) { # bar colors are obsolete, need to be configured in i3bar if ($statement =~ /^bar\./) { print "# XXX: REMOVED $statement, configure i3bar instead.\n"; + print "# Old line: $line\n"; next; } From 95ee21dc3bcaf1890d155397762fb5dd3cb2c18b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 6 Jul 2011 17:51:32 +0200 Subject: [PATCH 693/867] migrate-config: also leave client.background lines unchanged (Thanks cloud) --- i3-migrate-config-to-v4.pl | 1 + testcases/t/71-config-migrate.t | 2 ++ 2 files changed, 3 insertions(+) diff --git a/i3-migrate-config-to-v4.pl b/i3-migrate-config-to-v4.pl index 7dc4e27e..3a27c943 100755 --- a/i3-migrate-config-to-v4.pl +++ b/i3-migrate-config-to-v4.pl @@ -41,6 +41,7 @@ my @unchanged = qw( client.focused_inactive client.unfocused client.urgent + client.background ); my %workspace_names; diff --git a/testcases/t/71-config-migrate.t b/testcases/t/71-config-migrate.t index 9903d545..11c70bfa 100644 --- a/testcases/t/71-config-migrate.t +++ b/testcases/t/71-config-migrate.t @@ -65,6 +65,7 @@ $input = < Date: Wed, 6 Jul 2011 17:56:09 +0200 Subject: [PATCH 694/867] migrate-config: also handle named workspaces correctly when their names come after the bindings (Thanks xpt) --- i3-migrate-config-to-v4.pl | 18 ++++++++++++++++-- testcases/t/71-config-migrate.t | 9 +++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/i3-migrate-config-to-v4.pl b/i3-migrate-config-to-v4.pl index 3a27c943..7e4f61ce 100755 --- a/i3-migrate-config-to-v4.pl +++ b/i3-migrate-config-to-v4.pl @@ -83,6 +83,21 @@ if (!need_to_convert()) { exit 1; } +# first pass: get workspace names +for my $line (@lines) { + next if $line =~ /^#/ || $line =~ /^$/; + + my ($statement, $parameters) = ($line =~ /([a-zA-Z._-]+)(.*)/); + + # skip everything but workspace lines + next unless defined($statement) and $statement eq 'workspace'; + + my ($number, $params) = ($parameters =~ /[ \t]+([0-9]+) (.+)/); + + # save workspace name (unless the line is actually a workspace assignment) + $workspace_names{$number} = $params unless $params =~ /^output/; +} + for my $line (@lines) { # directly use comments and empty lines if ($line =~ /^#/ || $line =~ /^$/) { @@ -141,8 +156,7 @@ for my $line (@lines) { print "$line\n"; next; } else { - print "# XXX: workspace name will end up in the bindings, see below\n"; - $workspace_names{$number} = $params; + print "# XXX: workspace name will end up in the corresponding bindings.\n"; next; } } diff --git a/testcases/t/71-config-migrate.t b/testcases/t/71-config-migrate.t index 11c70bfa..ec46eb33 100644 --- a/testcases/t/71-config-migrate.t +++ b/testcases/t/71-config-migrate.t @@ -297,6 +297,15 @@ $output = migrate_config($input); ok(!line_exists($output, qr|^workspace|), 'workspace name not present'); ok(line_exists($output, qr|^bindsym Mod1\+3 workspace work|), 'named workspace in bindings'); +# The same, but in reverse order +$input = < Date: Wed, 6 Jul 2011 20:21:39 +0200 Subject: [PATCH 695/867] migrate-config: Bugfix: 'borderless' has to be 'none' --- i3-migrate-config-to-v4.pl | 8 ++++---- testcases/t/71-config-migrate.t | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/i3-migrate-config-to-v4.pl b/i3-migrate-config-to-v4.pl index 7e4f61ce..eee730d1 100755 --- a/i3-migrate-config-to-v4.pl +++ b/i3-migrate-config-to-v4.pl @@ -70,7 +70,7 @@ sub need_to_convert { $command =~ /^floating/ || $command =~ /^workspace/ || $command =~ /^focus (left|right|up|down)/ || - $command =~ /^border (normal|1pixel|borderless)/; + $command =~ /^border (normal|1pixel|none)/; } return 1; @@ -127,10 +127,10 @@ for my $line (@lines) { next; } - # new_window changed the parameters from bb to borderless etc. + # new_window changed the parameters from bb to none etc. if ($statement eq 'new_window') { if ($parameters =~ /bb/) { - print "new_window borderless\n"; + print "new_window none\n"; } elsif ($parameters =~ /bp/) { print "new_window 1pixel\n"; } elsif ($parameters =~ /bn/) { @@ -216,7 +216,7 @@ sub convert_command { qr/^ml/ => 'move right', qr/^bn/ => 'border normal', qr/^bp/ => 'border 1pixel', - qr/^bb/ => 'border borderless', + qr/^bb/ => 'border none', qr/^pw/ => 'workspace prev', qr/^nw/ => 'workspace next', ); diff --git a/testcases/t/71-config-migrate.t b/testcases/t/71-config-migrate.t index ec46eb33..1d81aaac 100644 --- a/testcases/t/71-config-migrate.t +++ b/testcases/t/71-config-migrate.t @@ -112,14 +112,14 @@ $output = migrate_config($input); ok(line_exists($output, qr|^workspace_layout stacking$|), 'new_container changed'); ok(line_exists($output, qr|REMOVED workspace_bar|), 'workspace_bar removed'); ok(!line_exists($output, qr|^workspace_bar|), 'no workspace_bar in the output'); -ok(line_exists($output, qr|^new_window borderless$|), 'new_window changed'); +ok(line_exists($output, qr|^new_window none$|), 'new_window changed'); ##################################################################### # check whether new_window's parameters get changed correctly ##################################################################### $output = migrate_config('new_window bb'); -ok(line_exists($output, qr|^new_window borderless$|), 'new_window bb changed'); +ok(line_exists($output, qr|^new_window none$|), 'new_window bb changed'); $output = migrate_config('new_window bn'); ok(line_exists($output, qr|^new_window normal$|), 'new_window bn changed'); @@ -212,7 +212,7 @@ ok(line_exists($output, qr|^bindsym Mod1\+s move up$|), 'mk replaced'); ok(line_exists($output, qr|^bindsym Mod1\+s move right$|), 'ml replaced'); ok(line_exists($output, qr|^bindsym Mod1\+s border normal$|), 'bn replaced'); ok(line_exists($output, qr|^bindsym Mod1\+s border 1pixel$|), 'bp replaced'); -ok(line_exists($output, qr|^bindsym Mod1\+s border borderless$|), 'bb replaced'); +ok(line_exists($output, qr|^bindsym Mod1\+s border none$|), 'bb replaced'); ok(line_exists($output, qr|^#.*with container.*obsolete.*wch$|), 'with container removed'); ok(line_exists($output, qr|^#.*with container.*obsolete.*wcml$|), 'with container removed'); ok(line_exists($output, qr|^bindsym Mod1\+k kill$|), 'kill unchanged'); From ac335fcffa06417fe190df97b8fc749f20da0523 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 6 Jul 2011 20:40:46 +0200 Subject: [PATCH 696/867] Automatically call the migration script when the config does not look like v4 --- i3.config | 2 + src/cfgparse.y | 219 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+) diff --git a/i3.config b/i3.config index 232dce41..4c49e4bc 100644 --- a/i3.config +++ b/i3.config @@ -1,3 +1,5 @@ +# i3 config file (v4) +# # This configuration file was written for the NEO layout. If you are using a # different layout, you should change it. diff --git a/src/cfgparse.y b/src/cfgparse.y index 2258268f..42438893 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -5,9 +5,11 @@ */ #include #include +#include #include #include #include +#include #include "all.h" @@ -46,6 +48,194 @@ int yywrap() { return 1; } +/* + * Goes through each line of buf (separated by \n) and checks for statements / + * commands which only occur in i3 v4 configuration files. If it finds any, it + * returns version 4, otherwise it returns version 3. + * + */ +static int detect_version(char *buf) { + char *walk = buf; + char *line = buf; + while (*walk != '\0') { + if (*walk != '\n') { + walk++; + continue; + } + + /* check for some v4-only statements */ + if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 || + strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 || + strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) { + printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line); + return 4; + } + + /* if this is a bind statement, we can check the command */ + if (strncasecmp(line, "bind", strlen("bind")) == 0) { + char *bind = strchr(line, ' '); + if (bind == NULL) + goto next; + while ((*bind == ' ' || *bind == '\t') && *bind != '\0') + bind++; + if (*bind == '\0') + goto next; + if ((bind = strchr(bind, ' ')) == NULL) + goto next; + while ((*bind == ' ' || *bind == '\t') && *bind != '\0') + bind++; + if (*bind == '\0') + goto next; + if (strncasecmp(bind, "layout", strlen("layout")) == 0 || + strncasecmp(bind, "floating", strlen("floating")) == 0 || + strncasecmp(bind, "workspace", strlen("workspace")) == 0 || + strncasecmp(bind, "focus left", strlen("focus left")) == 0 || + strncasecmp(bind, "focus right", strlen("focus right")) == 0 || + strncasecmp(bind, "focus up", strlen("focus up")) == 0 || + strncasecmp(bind, "focus down", strlen("focus down")) == 0 || + strncasecmp(bind, "border normal", strlen("border normal")) == 0 || + strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 || + strncasecmp(bind, "border borderless", strlen("border borderless")) == 0) { + printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line); + return 4; + } + } + +next: + /* advance to the next line */ + walk++; + line = walk; + } + + return 3; +} + +/* + * Calls i3-migrate-config-to-v4.pl to migrate a configuration file (input + * buffer). + * + * Returns the converted config file or NULL if there was an error (for + * example the script could not be found in $PATH or the i3 executable’s + * directory). + * + */ +static char *migrate_config(char *input, off_t size) { + int writepipe[2]; + int readpipe[2]; + + if (pipe(writepipe) != 0 || + pipe(readpipe) != 0) { + warn("migrate_config: Could not create pipes"); + return NULL; + } + + pid_t pid = fork(); + if (pid == -1) { + warn("Could not fork()"); + return NULL; + } + + /* child */ + if (pid == 0) { + /* close writing end of writepipe, connect reading side to stdin */ + close(writepipe[1]); + dup2(writepipe[0], 0); + + /* close reading end of readpipe, connect writing side to stdout */ + close(readpipe[0]); + dup2(readpipe[1], 1); + + /* start the migration script, search PATH first */ + char *migratepath = "i3-migrate-config-to-v4.pl"; + execlp(migratepath, migratepath, NULL); + + /* if the script is not in path, maybe the user installed to a strange + * location and runs the i3 binary with an absolute path. We use + * argv[0]’s dirname */ + char *pathbuf = strdup(start_argv[0]); + char *dir = dirname(pathbuf); + asprintf(&migratepath, "%s/%s", dir, "i3-migrate-config-to-v4.pl"); + execlp(migratepath, migratepath, NULL); + +#if defined(__linux__) + /* on linux, we have one more fall-back: dirname(/proc/self/exe) */ + char buffer[BUFSIZ]; + if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) { + warn("could not read /proc/self/exe"); + exit(1); + } + dir = dirname(buffer); + asprintf(&migratepath, "%s/%s", dir, "i3-migrate-config-to-v4.pl"); + execlp(migratepath, migratepath, NULL); +#endif + + warn("Could not start i3-migrate-config-to-v4.pl"); + exit(2); + } + + /* parent */ + + /* close reading end of the writepipe (connected to the script’s stdin) */ + close(writepipe[0]); + + /* write the whole config file to the pipe, the script will read everything + * immediately */ + int written = 0; + int ret; + while (written < size) { + if ((ret = write(writepipe[1], input + written, size - written)) < 0) { + warn("Could not write to pipe"); + return NULL; + } + written += ret; + } + close(writepipe[1]); + + /* close writing end of the readpipe (connected to the script’s stdout) */ + close(readpipe[1]); + + /* read the script’s output */ + int conv_size = 65535; + char *converted = malloc(conv_size); + int read_bytes = 0; + do { + if (read_bytes == conv_size) { + conv_size += 65535; + converted = realloc(converted, conv_size); + } + ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes); + if (ret == -1) { + warn("Cannot read from pipe"); + return NULL; + } + read_bytes += ret; + } while (ret > 0); + + /* get the returncode */ + int status; + wait(&status); + if (!WIFEXITED(status)) { + fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n"); + return NULL; + } + + int returncode = WEXITSTATUS(status); + if (returncode != 0) { + fprintf(stderr, "Migration process exit code was != 0\n"); + if (returncode == 2) { + fprintf(stderr, "could not start the migration script\n"); + /* TODO: script was not found. tell the user to fix his system or create a v4 config */ + } else if (returncode == 1) { + fprintf(stderr, "This already was a v4 config. Please add the following line to your config file:\n"); + fprintf(stderr, "# i3 config file (v4)\n"); + /* TODO: nag the user with a message to include a hint for i3 in his config file */ + } + return NULL; + } + + return converted; +} + void parse_file(const char *f) { SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables); int fd, ret, read_bytes = 0; @@ -160,6 +350,35 @@ void parse_file(const char *f) { } } + /* analyze the string to find out whether this is an old config file (3.x) + * or a new config file (4.x). If it’s old, we run the converter script. */ + int version = detect_version(buf); + if (version == 3) { + /* We need to convert this v3 configuration */ + char *converted = migrate_config(new, stbuf.st_size); + if (converted != NULL) { + printf("\n"); + printf("****************************************************************\n"); + printf("NOTE: Automatically converted configuration file from v3 to v4.\n"); + printf("\n"); + printf("Please convert your config file to v4. You can use this command:\n"); + printf(" mv %s %s.O\n", f, f); + printf(" i3-migrate-config-to-v4.pl %s.O > %s\n", f, f); + printf("****************************************************************\n"); + printf("\n"); + free(new); + new = converted; + } else { + printf("\n"); + printf("**********************************************************************\n"); + printf("ERROR: Could not convert config file. Maybe i3-migrate-config-to-v4.pl\n"); + printf("was not correctly installed on your system?\n"); + printf("**********************************************************************\n"); + printf("\n"); + } + } + + /* now lex/parse it */ yy_scan_string(new); context = scalloc(sizeof(struct context)); From cf124e18f9870f2b98d5b2723a819eb014e9135a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 6 Jul 2011 20:51:49 +0200 Subject: [PATCH 697/867] Makefile: install i3-migrate-config-to-v4.pl --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 4e2761a9..6d734063 100644 --- a/Makefile +++ b/Makefile @@ -68,6 +68,7 @@ install: all $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3 $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/ + $(INSTALL) -m 0755 i3-migrate-config-to-v4.pl $(DESTDIR)$(PREFIX)/bin/ test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config $(INSTALL) -m 0644 i3.welcome $(DESTDIR)$(SYSCONFDIR)/i3/welcome $(INSTALL) -m 0644 i3.desktop $(DESTDIR)$(PREFIX)/share/xsessions/ From 0ca229ceb3d53067ebe1b0fff5c3e67f54bb608c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 8 Jul 2011 00:17:48 +0200 Subject: [PATCH 698/867] migrate-config: also make force_focus_wrapping a v4-only statement --- i3-migrate-config-to-v4.pl | 4 +++- src/cfgparse.y | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/i3-migrate-config-to-v4.pl b/i3-migrate-config-to-v4.pl index eee730d1..02981457 100755 --- a/i3-migrate-config-to-v4.pl +++ b/i3-migrate-config-to-v4.pl @@ -60,7 +60,9 @@ sub need_to_convert { for my $line (@lines) { # only v4 configfiles can use bindcode or workspace_layout - return 0 if $line =~ /^bindcode/ || $line =~ /^workspace_layout/; + return 0 if $line =~ /^bindcode/ || + $line =~ /^workspace_layout/ || + $line =~ /^force_focus_wrapping/; # have a look at bindings next unless $line =~ /^bind/; diff --git a/src/cfgparse.y b/src/cfgparse.y index 42438893..83d95fc5 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -65,6 +65,7 @@ static int detect_version(char *buf) { /* check for some v4-only statements */ if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 || + strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 || strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 || strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) { printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line); From 0add5634485abcb7bffe7e748082b2d8e4418f42 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 8 Jul 2011 00:21:29 +0200 Subject: [PATCH 699/867] Bugfix: multiple criteria should use a logical AND (+test) (Thanks f8l) --- src/match.c | 66 +++++++++++++++++++++++++++++------------- testcases/t/19-match.t | 60 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 20 deletions(-) diff --git a/src/match.c b/src/match.c index 2449bad7..3a346117 100644 --- a/src/match.c +++ b/src/match.c @@ -69,40 +69,66 @@ void match_copy(Match *dest, Match *src) { * */ bool match_matches_window(Match *match, i3Window *window) { + LOG("checking window %d (%s)\n", window->id, window->class_class); + /* TODO: pcre, full matching, … */ - if (match->class != NULL && window->class_class != NULL && strcasecmp(match->class, window->class_class) == 0) { - LOG("match made by window class (%s)\n", window->class_class); - return true; + if (match->class != NULL) { + if (window->class_class != NULL && strcasecmp(match->class, window->class_class) == 0) { + LOG("window class matches (%s)\n", window->class_class); + } else { + LOG("window class does not match\n"); + return false; + } } - if (match->instance != NULL && window->class_instance != NULL && strcasecmp(match->instance, window->class_instance) == 0) { - LOG("match made by window instance (%s)\n", window->class_instance); - return true; + if (match->instance != NULL) { + if (window->class_instance != NULL && strcasecmp(match->instance, window->class_instance) == 0) { + LOG("window instance matches (%s)\n", window->class_instance); + } else { + LOG("window instance does not match\n"); + return false; + } } - if (match->id != XCB_NONE && window->id == match->id) { - LOG("match made by window id (%d)\n", window->id); - return true; + if (match->id != XCB_NONE) { + if (window->id == match->id) { + LOG("match made by window id (%d)\n", window->id); + } else { + LOG("window id does not match\n"); + return false; + } } /* TODO: pcre match */ - if (match->title != NULL && window->name_json != NULL && strcasecmp(match->title, window->name_json) == 0) { - LOG("match made by title (%s)\n", window->name_json); - return true; + if (match->title != NULL) { + if (window->name_json != NULL && strcasecmp(match->title, window->name_json) == 0) { + LOG("title matches (%s)\n", window->name_json); + } else { + LOG("title does not match\n"); + return false; + } } - LOG("match->dock = %d, window->dock = %d\n", match->dock, window->dock); - if (match->dock != -1 && - ((window->dock == W_DOCK_TOP && match->dock == M_DOCK_TOP) || + if (match->dock != -1) { + LOG("match->dock = %d, window->dock = %d\n", match->dock, window->dock); + if ((window->dock == W_DOCK_TOP && match->dock == M_DOCK_TOP) || (window->dock == W_DOCK_BOTTOM && match->dock == M_DOCK_BOTTOM) || ((window->dock == W_DOCK_TOP || window->dock == W_DOCK_BOTTOM) && match->dock == M_DOCK_ANY) || - (window->dock == W_NODOCK && match->dock == M_NODOCK))) { - LOG("match made by dock\n"); - return true; + (window->dock == W_NODOCK && match->dock == M_NODOCK)) { + LOG("dock status matches\n"); + } else { + LOG("dock status does not match\n"); + return false; + } } - LOG("window %d (%s) could not be matched\n", window->id, window->class_class); + /* We don’t check the mark because this function is not even called when + * the mark would have matched - it is checked in cmdparse.y itself */ + if (match->mark != NULL) { + LOG("mark does not match\n"); + return false; + } - return false; + return true; } diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t index 7efdd95c..15f18891 100644 --- a/testcases/t/19-match.t +++ b/testcases/t/19-match.t @@ -56,4 +56,64 @@ ok(@{$content} == 0, 'window killed'); # TODO: same test, but with pcre expressions +###################################################################### +# check that multiple criteria work are checked with a logical AND, +# not a logical OR (that is, matching is not cancelled after the first +# criterion matches). +###################################################################### + +$tmp = fresh_workspace; + +# TODO: move to X11::XCB +sub set_wm_class { + my ($id, $class, $instance) = @_; + + # Add a _NET_WM_STRUT_PARTIAL hint + my $atomname = $x->atom(name => 'WM_CLASS'); + my $atomtype = $x->atom(name => 'STRING'); + + $x->change_property( + PROP_MODE_REPLACE, + $id, + $atomname->id, + $atomtype->id, + 8, + length($class) + length($instance) + 2, + "$instance\x00$class\x00" + ); +} + +my $left = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#0000ff', +); + +$left->_create; +set_wm_class($left->id, 'special', 'special'); +$left->name('left'); +$left->map; +sleep 0.25; + +my $right = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#0000ff', +); + +$right->_create; +set_wm_class($right->id, 'special', 'special'); +$right->name('right'); +$right->map; +sleep 0.25; + +# two windows should be here +$content = get_ws_content($tmp); +ok(@{$content} == 2, 'two windows opened'); + +cmd '[class="special" title="left"] kill'; + +$content = get_ws_content($tmp); +is(@{$content}, 1, 'one window still there'); + done_testing; From 0fe564d122737824d4b004e4be2cb3843e7a0007 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jul 2011 01:01:52 +0200 Subject: [PATCH 700/867] tests: t/19-match needs a little delay --- testcases/t/19-match.t | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t index 15f18891..2332bc71 100644 --- a/testcases/t/19-match.t +++ b/testcases/t/19-match.t @@ -113,6 +113,8 @@ ok(@{$content} == 2, 'two windows opened'); cmd '[class="special" title="left"] kill'; +sleep 0.25; + $content = get_ws_content($tmp); is(@{$content}, 1, 'one window still there'); From b63a559c2801c0b9f7f23b917ab351541eb67a2f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jul 2011 01:02:13 +0200 Subject: [PATCH 701/867] migrate-config: also migrate border toggle (bt) (Thanks woddf2) --- i3-migrate-config-to-v4.pl | 1 + testcases/t/71-config-migrate.t | 2 ++ 2 files changed, 3 insertions(+) diff --git a/i3-migrate-config-to-v4.pl b/i3-migrate-config-to-v4.pl index 02981457..f4f970df 100755 --- a/i3-migrate-config-to-v4.pl +++ b/i3-migrate-config-to-v4.pl @@ -219,6 +219,7 @@ sub convert_command { qr/^bn/ => 'border normal', qr/^bp/ => 'border 1pixel', qr/^bb/ => 'border none', + qr/^bt/ => 'border toggle', qr/^pw/ => 'workspace prev', qr/^nw/ => 'workspace next', ); diff --git a/testcases/t/71-config-migrate.t b/testcases/t/71-config-migrate.t index 1d81aaac..922ddc45 100644 --- a/testcases/t/71-config-migrate.t +++ b/testcases/t/71-config-migrate.t @@ -185,6 +185,7 @@ $input = < Date: Sun, 10 Jul 2011 14:33:19 +0200 Subject: [PATCH 702/867] add i3-nagbar. tells you about config file errors (for example) --- common.mk | 2 + i3-nagbar/Makefile | 28 +++ i3-nagbar/atoms.xmacro | 6 + i3-nagbar/i3-nagbar.h | 26 +++ i3-nagbar/main.c | 392 +++++++++++++++++++++++++++++++++++++++++ i3-nagbar/xcb.c | 132 ++++++++++++++ include/config.h | 13 ++ include/i3.h | 1 + include/log.h | 12 +- include/util.h | 17 ++ man/Makefile | 3 +- man/i3-nagbar.man | 34 ++++ src/cfgparse.y | 141 +++++++++++---- src/cmdparse.y | 1 + src/log.c | 24 +++ src/main.c | 27 +-- src/util.c | 50 ++++++ 17 files changed, 864 insertions(+), 45 deletions(-) create mode 100644 i3-nagbar/Makefile create mode 100644 i3-nagbar/atoms.xmacro create mode 100644 i3-nagbar/i3-nagbar.h create mode 100644 i3-nagbar/main.c create mode 100644 i3-nagbar/xcb.c create mode 100644 man/i3-nagbar.man diff --git a/common.mk b/common.mk index fed147b8..41946d41 100644 --- a/common.mk +++ b/common.mk @@ -8,6 +8,7 @@ SYSCONFDIR=/etc else SYSCONFDIR=$(PREFIX)/etc endif +TERM_EMU=xterm # The escaping is absurd, but we need to escape for shell, sed, make, define GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f .git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' .git/HEAD || echo 'unknown'))" VERSION:=$(shell git describe --tags --abbrev=0) @@ -46,6 +47,7 @@ CFLAGS += $(call cflags_for_lib, yajl) CFLAGS += $(call cflags_for_lib, libev) CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" CFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" +CFLAGS += -DTERM_EMU=\"$(TERM_EMU)\" LDFLAGS += -lm LDFLAGS += $(call ldflags_for_lib, xcb-event, xcb-event) diff --git a/i3-nagbar/Makefile b/i3-nagbar/Makefile new file mode 100644 index 00000000..8d9283bc --- /dev/null +++ b/i3-nagbar/Makefile @@ -0,0 +1,28 @@ +# Default value so one can compile i3-nagbar standalone +TOPDIR=.. + +include $(TOPDIR)/common.mk + +# Depend on the object files of all source-files in src/*.c and on all header files +FILES=$(patsubst %.c,%.o,$(wildcard *.c)) +HEADERS=$(wildcard *.h) + +# Depend on the specific file (.c for each .o) and on all headers +%.o: %.c ${HEADERS} + echo "CC $<" + $(CC) $(CFLAGS) -c -o $@ $< + +all: ${FILES} + echo "LINK i3-nagbar" + $(CC) -o i3-nagbar ${FILES} $(LDFLAGS) + +install: all + echo "INSTALL" + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 i3-nagbar $(DESTDIR)$(PREFIX)/bin/ + +clean: + rm -f *.o + +distclean: clean + rm -f i3-nagbar diff --git a/i3-nagbar/atoms.xmacro b/i3-nagbar/atoms.xmacro new file mode 100644 index 00000000..333ba2d6 --- /dev/null +++ b/i3-nagbar/atoms.xmacro @@ -0,0 +1,6 @@ +xmacro(_NET_WM_WINDOW_TYPE) +xmacro(_NET_WM_WINDOW_TYPE_DOCK) +xmacro(_NET_WM_STRUT_PARTIAL) +xmacro(I3_SOCKET_PATH) +xmacro(ATOM) +xmacro(CARDINAL) diff --git a/i3-nagbar/i3-nagbar.h b/i3-nagbar/i3-nagbar.h new file mode 100644 index 00000000..2fbe3cbb --- /dev/null +++ b/i3-nagbar/i3-nagbar.h @@ -0,0 +1,26 @@ +#ifndef _I3_NAGBAR +#define _I3_NAGBAR + +#include + +#define die(...) errx(EXIT_FAILURE, __VA_ARGS__); +#define FREE(pointer) do { \ + if (pointer != NULL) { \ + free(pointer); \ + pointer = NULL; \ + } \ +} \ +while (0) + +#define xmacro(atom) xcb_atom_t A_ ## atom; +#include "atoms.xmacro" +#undef xmacro + +extern xcb_window_t root; + +uint32_t get_colorpixel(xcb_connection_t *conn, char *hex); +xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height); +int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height); +void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value); + +#endif diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c new file mode 100644 index 00000000..73d6f63b --- /dev/null +++ b/i3-nagbar/main.c @@ -0,0 +1,392 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009-2011 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * i3-nagbar is a utility which displays a nag message. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "i3-nagbar.h" + +typedef struct { + char *label; + char *action; + int16_t x; + uint16_t width; +} button_t; + +static xcb_window_t win; +static xcb_pixmap_t pixmap; +static xcb_gcontext_t pixmap_gc; +static xcb_rectangle_t rect = { 0, 0, 600, 20 }; +static char *glyphs_ucs[512]; +static int input_position; +static int font_height; +static char *prompt = "You have an error in your i3 config file!"; +static button_t *buttons; +static int buttoncnt; +xcb_window_t root; + +/* + * Starts the given application by passing it through a shell. We use double fork + * to avoid zombie processes. As the started application’s parent exits (immediately), + * the application is reparented to init (process-id 1), which correctly handles + * childs, so we don’t have to do it :-). + * + * The shell is determined by looking for the SHELL environment variable. If it + * does not exist, /bin/sh is used. + * + */ +static void start_application(const char *command) { + printf("executing: %s\n", command); + if (fork() == 0) { + /* Child process */ + setsid(); + if (fork() == 0) { + /* Stores the path of the shell */ + static const char *shell = NULL; + + if (shell == NULL) + if ((shell = getenv("SHELL")) == NULL) + shell = "/bin/sh"; + + /* This is the child */ + execl(shell, shell, "-c", command, (void*)NULL); + /* not reached */ + } + exit(0); + } + wait(0); +} + +static button_t *get_button_at(int16_t x, int16_t y) { + for (int c = 0; c < buttoncnt; c++) + if (x >= (buttons[c].x) && x <= (buttons[c].x + buttons[c].width)) + return &buttons[c]; + + return NULL; +} + +static void handle_button_press(xcb_connection_t *conn, xcb_button_press_event_t *event) { + printf("button pressed on x = %d, y = %d\n", + event->event_x, event->event_y); + /* TODO: set a flag for the button, re-render */ +} + +/* + * Called when the user releases the mouse button. Checks whether the + * coordinates are over a button and executes the appropriate action. + * + */ +static void handle_button_release(xcb_connection_t *conn, xcb_button_release_event_t *event) { + printf("button released on x = %d, y = %d\n", + event->event_x, event->event_y); + /* If the user hits the close button, we exit(0) */ + if (event->event_x >= (rect.width - 32)) + exit(0); + button_t *button = get_button_at(event->event_x, event->event_y); + if (!button) + return; + start_application(button->action); + + /* TODO: unset flag, re-render */ +} + +/* + * Handles expose events (redraws of the window) and rendering in general. Will + * be called from the code with event == NULL or from X with event != NULL. + * + */ +static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { + printf("expose!\n"); + + /* re-draw the background */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#900000")); + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect); + + /* restore font color */ + uint32_t values[3]; + values[0] = get_colorpixel(conn, "#FFFFFF"); + values[1] = get_colorpixel(conn, "#900000"); + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values); + xcb_image_text_8(conn, strlen(prompt), pixmap, pixmap_gc, 4 + 4/* X */, + font_height + 2 + 4 /* Y = baseline of font */, prompt); + + /* render close button */ + int line_width = 4; + int w = 20; + int y = rect.width; + values[0] = get_colorpixel(conn, "#680a0a"); + values[1] = line_width; + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); + + xcb_rectangle_t close = { y - w - (2 * line_width), 0, w + (2 * line_width), rect.height }; + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); + + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424")); + xcb_point_t points[] = { + { y - w - (2 * line_width), line_width / 2 }, + { y - (line_width / 2), line_width / 2 }, + { y - (line_width / 2), (rect.height - (line_width / 2)) - 2 }, + { y - w - (2 * line_width), (rect.height - (line_width / 2)) - 2 }, + { y - w - (2 * line_width), line_width / 2 } + }; + xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points); + + values[0] = get_colorpixel(conn, "#ffffff"); + values[1] = get_colorpixel(conn, "#680a0a"); + values[2] = 1; + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH, values); + xcb_image_text_8(conn, strlen("x"), pixmap, pixmap_gc, y - w - line_width + (w / 2) - 4/* X */, + font_height + 2 + 4 - 1/* Y = baseline of font */, "X"); + y -= w; + + y -= 20; + + /* render custom buttons */ + line_width = 1; + for (int c = 0; c < buttoncnt; c++) { + /* TODO: make w = text extents of the label */ + w = 90; + y -= 30; + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#680a0a")); + close = (xcb_rectangle_t){ y - w - (2 * line_width), 2, w + (2 * line_width), rect.height - 6 }; + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); + + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424")); + buttons[c].x = y - w - (2 * line_width); + buttons[c].width = w; + xcb_point_t points2[] = { + { y - w - (2 * line_width), (line_width / 2) + 2 }, + { y - (line_width / 2), (line_width / 2) + 2 }, + { y - (line_width / 2), (rect.height - 4 - (line_width / 2)) }, + { y - w - (2 * line_width), (rect.height - 4 - (line_width / 2)) }, + { y - w - (2 * line_width), (line_width / 2) + 2 } + }; + xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2); + + values[0] = get_colorpixel(conn, "#ffffff"); + values[1] = get_colorpixel(conn, "#680a0a"); + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values); + xcb_image_text_8(conn, strlen(buttons[c].label), pixmap, pixmap_gc, y - w - line_width + 6/* X */, + font_height + 2 + 3/* Y = baseline of font */, buttons[c].label); + + y -= w; + } + + /* border line at the bottom */ + line_width = 2; + values[0] = get_colorpixel(conn, "#470909"); + values[1] = line_width; + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); + xcb_point_t bottom[] = { + { 0, rect.height - 0 }, + { rect.width, rect.height - 0 } + }; + xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 2, bottom); + + + /* Copy the contents of the pixmap to the real window */ + xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, rect.width, rect.height); + xcb_flush(conn); + + return 1; +} + +int main(int argc, char *argv[]) { + char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; + int o, option_index = 0; + + static struct option long_options[] = { + {"version", no_argument, 0, 'v'}, + {"font", required_argument, 0, 'f'}, + {"button", required_argument, 0, 'b'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + char *options_string = "b:f:vh"; + + while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { + switch (o) { + case 'v': + printf("i3-nagbar " I3_VERSION); + return 0; + case 'f': + FREE(pattern); + pattern = strdup(optarg); + break; + case 'h': + printf("i3-nagbar " I3_VERSION); + printf("i3-nagbar [-s ] [-p ] [-l ] [-P ] [-f ] [-v]\n"); + return 0; + case 'b': + buttons = realloc(buttons, sizeof(button_t) * (buttoncnt + 1)); + buttons[buttoncnt].label = optarg; + buttons[buttoncnt].action = argv[optind]; + printf("button with label *%s* and action *%s*\n", + buttons[buttoncnt].label, + buttons[buttoncnt].action); + buttoncnt++; + printf("now %d buttons\n", buttoncnt); + if (optind < argc) + optind++; + break; + } + } + + int screens; + xcb_connection_t *conn = xcb_connect(NULL, &screens); + if (xcb_connection_has_error(conn)) + die("Cannot open display\n"); + + /* Place requests for the atoms we need as soon as possible */ + #define xmacro(atom) \ + xcb_intern_atom_cookie_t atom ## _cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom); + #include "atoms.xmacro" + #undef xmacro + + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); + root = root_screen->root; + + uint32_t font_id = get_font_id(conn, pattern, &font_height); + + /* Open an input window */ + win = open_input_window(conn, 500, font_height + 8 + 8 /* 8px padding */); + + /* Setup NetWM atoms */ + #define xmacro(name) \ + do { \ + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name ## _cookie, NULL); \ + if (!reply) \ + die("Could not get atom " # name "\n"); \ + \ + A_ ## name = reply->atom; \ + free(reply); \ + } while (0); + #include "atoms.xmacro" + #undef xmacro + + /* Set dock mode */ + xcb_void_cookie_t dock_cookie = xcb_change_property(conn, + XCB_PROP_MODE_REPLACE, + win, + A__NET_WM_WINDOW_TYPE, + A_ATOM, + 32, + 1, + (unsigned char*) &A__NET_WM_WINDOW_TYPE_DOCK); + + /* Reserve some space at the top of the screen */ + struct { + uint32_t left; + uint32_t right; + uint32_t top; + uint32_t bottom; + uint32_t left_start_y; + uint32_t left_end_y; + uint32_t right_start_y; + uint32_t right_end_y; + uint32_t top_start_x; + uint32_t top_end_x; + uint32_t bottom_start_x; + uint32_t bottom_end_x; + } __attribute__((__packed__)) strut_partial = {0,}; + + strut_partial.top = font_height + 6; + strut_partial.top_start_x = 0; + strut_partial.top_end_x = 800; + + xcb_void_cookie_t strut_cookie = xcb_change_property(conn, + XCB_PROP_MODE_REPLACE, + win, + A__NET_WM_STRUT_PARTIAL, + A_CARDINAL, + 32, + 12, + &strut_partial); + + /* Create pixmap */ + pixmap = xcb_generate_id(conn); + pixmap_gc = xcb_generate_id(conn); + xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8); + xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + + /* Create graphics context */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id); + + /* Grab the keyboard to get all input */ + xcb_flush(conn); + + xcb_generic_event_t *event; + while ((event = xcb_wait_for_event(conn)) != NULL) { + if (event->response_type == 0) { + fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence); + continue; + } + + /* Strip off the highest bit (set if the event is generated) */ + int type = (event->response_type & 0x7F); + + switch (type) { + case XCB_EXPOSE: + handle_expose(conn, (xcb_expose_event_t*)event); + break; + + case XCB_BUTTON_PRESS: + handle_button_press(conn, (xcb_button_press_event_t*)event); + break; + + case XCB_BUTTON_RELEASE: + handle_button_release(conn, (xcb_button_release_event_t*)event); + break; + + case XCB_CONFIGURE_NOTIFY: { + xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t*)event; + rect = (xcb_rectangle_t){ + configure_notify->x, + configure_notify->y, + configure_notify->width, + configure_notify->height + }; + + /* Recreate the pixmap / gc */ + xcb_free_pixmap(conn, pixmap); + xcb_free_gc(conn, pixmap_gc); + + xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, rect.width, rect.height); + xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + + /* Create graphics context */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id); + break; + } + } + + free(event); + } + + return 0; +} diff --git a/i3-nagbar/xcb.c b/i3-nagbar/xcb.c new file mode 100644 index 00000000..ed1bfd89 --- /dev/null +++ b/i3-nagbar/xcb.c @@ -0,0 +1,132 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include +#include +#include +#include + +#include +#include + +#include + +#include "i3-nagbar.h" + +/* + * Convenience-wrapper around xcb_change_gc which saves us declaring a variable + * + */ +void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) { + xcb_change_gc(conn, gc, mask, &value); +} + +/* + * Returns the colorpixel to use for the given hex color (think of HTML). + * + * The hex_color has to start with #, for example #FF00FF. + * + * NOTE that get_colorpixel() does _NOT_ check the given color code for validity. + * This has to be done by the caller. + * + */ +uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) { + char strgroups[3][3] = {{hex[1], hex[2], '\0'}, + {hex[3], hex[4], '\0'}, + {hex[5], hex[6], '\0'}}; + uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)), + (strtol(strgroups[1], NULL, 16)), + (strtol(strgroups[2], NULL, 16))}; + + return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2]; +} + +/* + * Opens the window we use for input/output and maps it + * + */ +xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) { + xcb_window_t win = xcb_generate_id(conn); + //xcb_cursor_t cursor_id = xcb_generate_id(conn); + +#if 0 + /* Use the default cursor (left pointer) */ + if (cursor > -1) { + i3Font *cursor_font = load_font(conn, "cursor"); + xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id, + XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1, + 0, 0, 0, 65535, 65535, 65535); + } +#endif + + uint32_t mask = 0; + uint32_t values[3]; + + mask |= XCB_CW_BACK_PIXEL; + values[0] = 0; + + mask |= XCB_CW_EVENT_MASK; + values[1] = XCB_EVENT_MASK_EXPOSURE | + XCB_EVENT_MASK_STRUCTURE_NOTIFY | + XCB_EVENT_MASK_BUTTON_PRESS | + XCB_EVENT_MASK_BUTTON_RELEASE; + + xcb_create_window(conn, + XCB_COPY_FROM_PARENT, + win, /* the window id */ + root, /* parent == root */ + 50, 50, width, height, /* dimensions */ + 0, /* border = 0, we draw our own */ + XCB_WINDOW_CLASS_INPUT_OUTPUT, + XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ + mask, + values); + +#if 0 + if (cursor > -1) + xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id); +#endif + + /* Map the window (= make it visible) */ + xcb_map_window(conn, win); + + return win; +} + +/* + * Returns the ID of the font matching the given pattern and stores the height + * of the font (in pixels) in *font_height. die()s if no font matches. + * + */ +int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height) { + xcb_void_cookie_t font_cookie; + xcb_list_fonts_with_info_cookie_t info_cookie; + + /* Send all our requests first */ + int result; + result = xcb_generate_id(conn); + font_cookie = xcb_open_font_checked(conn, result, strlen(pattern), pattern); + info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern); + + xcb_generic_error_t *error = xcb_request_check(conn, font_cookie); + if (error != NULL) { + fprintf(stderr, "ERROR: Could not open font: %d\n", error->error_code); + exit(1); + } + + /* Get information (height/name) for this font */ + xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL); + if (reply == NULL) + die("Could not load font \"%s\"\n", pattern); + + *font_height = reply->font_ascent + reply->font_descent; + + return result; +} diff --git a/include/config.h b/include/config.h index 9ba5e0f9..1021a612 100644 --- a/include/config.h +++ b/include/config.h @@ -32,6 +32,8 @@ extern SLIST_HEAD(modes_head, Mode) modes; * */ struct context { + bool has_errors; + int line_number; char *line_copy; const char *filename; @@ -190,6 +192,17 @@ void switch_mode(const char *new_mode); */ Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode); +/** + * Kills the configerror i3-nagbar process, if any. + * + * Called when reloading/restarting. + * + * If wait_for_it is set (restarting), this function will waitpid(), otherwise, + * ev is assumed to handle it (reloading). + * + */ +void kill_configerror_nagbar(bool wait_for_it); + /* prototype for src/cfgparse.y */ void parse_file(const char *f); diff --git a/include/i3.h b/include/i3.h index 7eb48ecc..73b61178 100644 --- a/include/i3.h +++ b/include/i3.h @@ -32,5 +32,6 @@ extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; extern uint8_t root_depth; extern bool xcursor_supported, xkb_supported; extern xcb_window_t root; +extern struct ev_loop *main_loop; #endif diff --git a/include/log.h b/include/log.h index 9b284f0a..c1e10b06 100644 --- a/include/log.h +++ b/include/log.h @@ -1,9 +1,9 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * - * © 2009-2010 Michael Stapelberg and contributors + * © 2009-2011 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -21,6 +21,14 @@ #define DLOG(fmt, ...) debuglog(LOGLEVEL, "%s:%s:%d - " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__) extern char *loglevels[]; +extern char *errorfilename; + +/** + * Initializes logging by creating an error logfile in /tmp (or + * XDG_RUNTIME_DIR, see get_process_filename()). + * + */ +void init_logging(); /** * Enables the given loglevel. diff --git a/include/util.h b/include/util.h index df0e3065..276ea5b9 100644 --- a/include/util.h +++ b/include/util.h @@ -102,6 +102,23 @@ char *sstrdup(const char *str); */ void start_application(const char *command); +/** + * exec()s an i3 utility, for example the config file migration script or + * i3-nagbar. This function first searches $PATH for the given utility named, + * then falls back to the dirname() of the i3 executable path and then falls + * back to the dirname() of the target of /proc/self/exe (on linux). + * + * This function should be called after fork()ing. + * + * The first argument of the given argv vector will be overwritten with the + * executable name, so pass NULL. + * + * If the utility cannot be found in any of these locations, it exits with + * return code 2. + * + */ +void exec_i3_utility(char *name, char *argv[]); + /** * Checks a generic cookie for errors and quits with the given message if * there was an error. diff --git a/man/Makefile b/man/Makefile index 151b9abc..cea07ed5 100644 --- a/man/Makefile +++ b/man/Makefile @@ -2,8 +2,9 @@ all: a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3.man a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-msg.man a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-input.man + a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-nagbar.man clean: - for file in "i3 i3-msg i3-input"; \ + for file in "i3 i3-msg i3-input i3-nagbar"; \ do \ rm -f $${file}.1 $${file}.html $${file}.xml; \ done diff --git a/man/i3-nagbar.man b/man/i3-nagbar.man new file mode 100644 index 00000000..3dd37bb7 --- /dev/null +++ b/man/i3-nagbar.man @@ -0,0 +1,34 @@ +i3-nagbar(1) +============ +Michael Stapelberg +v4.0, July 2011 + +== NAME + +i3-nagbar - displays an error bar on top of your screen + +== SYNOPSIS + +i3-nagbar -m 'message' -b 'label' 'action' + +== DESCRIPTION + +i3-nagbar is used by i3 to tell you about errors in your configuration file +(for example). While these errors are logged to the logfile (if any), the past +has proven that users are either not aware of their logfile or do not check it +after modifying the configuration file. + +== EXAMPLE + +------------------------------------------------ +i3-nagbar -m 'You have an error in your i3 config file!' \ +-b 'edit config' 'xterm $EDITOR ~/.i3/config' +------------------------------------------------ + +== SEE ALSO + +i3(1) + +== AUTHOR + +Michael Stapelberg and contributors diff --git a/src/cfgparse.y b/src/cfgparse.y index 83d95fc5..22747108 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -9,10 +9,11 @@ #include #include #include -#include #include "all.h" +static pid_t configerror_pid = -1; + static Match current_match; typedef struct yy_buffer_state *YY_BUFFER_STATE; @@ -30,17 +31,18 @@ static struct context *context; //int yydebug = 1; void yyerror(const char *error_message) { + context->has_errors = true; + ELOG("\n"); ELOG("CONFIG: %s\n", error_message); ELOG("CONFIG: in file \"%s\", line %d:\n", context->filename, context->line_number); ELOG("CONFIG: %s\n", context->line_copy); - ELOG("CONFIG: "); + char buffer[context->last_column+1]; + buffer[context->last_column] = '\0'; for (int c = 1; c <= context->last_column; c++) - if (c >= context->first_column) - printf("^"); - else printf(" "); - printf("\n"); + buffer[c-1] = (c >= context->first_column ? '^' : ' '); + ELOG("CONFIG: %s\n", buffer); ELOG("\n"); } @@ -146,32 +148,11 @@ static char *migrate_config(char *input, off_t size) { close(readpipe[0]); dup2(readpipe[1], 1); - /* start the migration script, search PATH first */ - char *migratepath = "i3-migrate-config-to-v4.pl"; - execlp(migratepath, migratepath, NULL); - - /* if the script is not in path, maybe the user installed to a strange - * location and runs the i3 binary with an absolute path. We use - * argv[0]’s dirname */ - char *pathbuf = strdup(start_argv[0]); - char *dir = dirname(pathbuf); - asprintf(&migratepath, "%s/%s", dir, "i3-migrate-config-to-v4.pl"); - execlp(migratepath, migratepath, NULL); - -#if defined(__linux__) - /* on linux, we have one more fall-back: dirname(/proc/self/exe) */ - char buffer[BUFSIZ]; - if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) { - warn("could not read /proc/self/exe"); - exit(1); - } - dir = dirname(buffer); - asprintf(&migratepath, "%s/%s", dir, "i3-migrate-config-to-v4.pl"); - execlp(migratepath, migratepath, NULL); -#endif - - warn("Could not start i3-migrate-config-to-v4.pl"); - exit(2); + static char *argv[] = { + NULL, /* will be replaced by the executable path */ + NULL + }; + exec_i3_utility("i3-migrate-config-to-v4.pl", argv); } /* parent */ @@ -237,6 +218,98 @@ static char *migrate_config(char *input, off_t size) { return converted; } +/* + * Handler which will be called when we get a SIGCHLD for the nagbar, meaning + * it exited (or could not be started, depending on the exit code). + * + */ +static void nagbar_exited(EV_P_ ev_child *watcher, int revents) { + ev_child_stop(EV_A_ watcher); + if (!WIFEXITED(watcher->rstatus)) { + fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n"); + return; + } + + int exitcode = WEXITSTATUS(watcher->rstatus); + printf("i3-nagbar process exited with status %d\n", exitcode); + if (exitcode == 2) { + fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n"); + } + + configerror_pid = -1; +} + +/* + * Starts an i3-nagbar process which alerts the user that his configuration + * file contains one or more errors. Also offers two buttons: One to launch an + * $EDITOR on the config file and another one to launch a $PAGER on the error + * logfile. + * + */ +static void start_configerror_nagbar(const char *config_path) { + fprintf(stderr, "Would start i3-nagscreen now\n"); + configerror_pid = fork(); + if (configerror_pid == -1) { + warn("Could not fork()"); + return; + } + + /* child */ + if (configerror_pid == 0) { + char *editaction, + *pageraction; + if (asprintf(&editaction, TERM_EMU " -e $EDITOR \"%s\"", config_path) == -1) + exit(1); + if (asprintf(&pageraction, TERM_EMU " -e $PAGER \"%s\"", errorfilename) == -1) + exit(1); + char *argv[] = { + NULL, /* will be replaced by the executable path */ + "-m", + "You have an error in your i3 config file!", + "-b", + "edit config", + editaction, + (errorfilename ? "-b" : NULL), + "show errors", + pageraction, + NULL + }; + exec_i3_utility("i3-nagbar", argv); + } + + /* parent */ + /* install a child watcher */ + ev_child *child = smalloc(sizeof(ev_child)); + ev_child_init(child, &nagbar_exited, configerror_pid, 0); + ev_child_start(main_loop, child); +} + +/* + * Kills the configerror i3-nagbar process, if any. + * + * Called when reloading/restarting. + * + * If wait_for_it is set (restarting), this function will waitpid(), otherwise, + * ev is assumed to handle it (reloading). + * + */ +void kill_configerror_nagbar(bool wait_for_it) { + if (configerror_pid == -1) + return; + + if (kill(configerror_pid, SIGTERM) == -1) + warn("kill(configerror_nagbar) failed"); + + if (!wait_for_it) + return; + + /* When restarting, we don’t enter the ev main loop anymore and after the + * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD + * for us and we would end up with a process. Therefore we + * waitpid() here. */ + waitpid(configerror_pid, NULL, 0); +} + void parse_file(const char *f) { SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables); int fd, ret, read_bytes = 0; @@ -390,6 +463,10 @@ void parse_file(const char *f) { exit(1); } + if (context->has_errors) { + start_configerror_nagbar(f); + } + FREE(context->line_copy); free(context); free(new); diff --git a/src/cmdparse.y b/src/cmdparse.y index 95dc27b6..0b80b6b3 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -380,6 +380,7 @@ reload: TOK_RELOAD { printf("reloading\n"); + kill_configerror_nagbar(false); load_configuration(conn, NULL, true); x_set_i3_atoms(); /* Send an IPC event just in case the ws names have changed */ diff --git a/src/log.c b/src/log.c index 0371e9be..99c2d4d3 100644 --- a/src/log.c +++ b/src/log.c @@ -14,6 +14,7 @@ #include #include #include +#include #include "util.h" #include "log.h" @@ -23,6 +24,23 @@ static uint64_t loglevel = 0; static bool verbose = true; +static FILE *errorfile; +char *errorfilename; + +/* + * Initializes logging by creating an error logfile in /tmp (or + * XDG_RUNTIME_DIR, see get_process_filename()). + * + */ +void init_logging() { + errorfilename = get_process_filename("errorlog"); + if (errorfilename == NULL) { + ELOG("Could not initialize errorlog\n"); + return; + } + + errorfile = fopen(errorfilename, "w"); +} /* * Set verbosity of i3. If verbose is set to true, informative messages will @@ -101,6 +119,12 @@ void errorlog(char *fmt, ...) { va_start(args, fmt); vlog(fmt, args); va_end(args); + + /* also log to the error logfile, if opened */ + va_start(args, fmt); + vfprintf(errorfile, fmt, args); + fflush(errorfile); + va_end(args); } /* diff --git a/src/main.c b/src/main.c index b4ed4a1a..a2764cc1 100644 --- a/src/main.c +++ b/src/main.c @@ -19,6 +19,8 @@ xcb_connection_t *conn; xcb_window_t root; uint8_t root_depth; +struct ev_loop *main_loop; + xcb_key_symbols_t *keysyms; /* Those are our connections to X11 for use with libXcursor and XKB */ @@ -178,6 +180,8 @@ int main(int argc, char *argv[]) { if (!isatty(fileno(stdout))) setbuf(stdout, NULL); + init_logging(); + start_argv = argv; while ((opt = getopt_long(argc, argv, "c:CvaL:hld:V", long_options, &option_index)) != -1) { @@ -254,6 +258,13 @@ int main(int argc, char *argv[]) { if (xcb_connection_has_error(conn)) errx(EXIT_FAILURE, "Cannot open display\n"); + /* Initialize the libev event loop. This needs to be done before loading + * the config file because the parser will install an ev_child watcher + * for the nagbar when config errors are found. */ + main_loop = EV_DEFAULT; + if (main_loop == NULL) + die("Could not initialize libev. Bad LIBEV_FLAGS?\n"); + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); root = root_screen->root; root_depth = root_screen->root_depth; @@ -395,10 +406,6 @@ int main(int argc, char *argv[]) { tree_render(); - struct ev_loop *loop = ev_loop_new(0); - if (loop == NULL) - die("Could not initialize libev. Bad LIBEV_FLAGS?\n"); - /* Create the UNIX domain socket for IPC */ int ipc_socket = ipc_create_socket(config.ipc_socket_path); if (ipc_socket == -1) { @@ -407,7 +414,7 @@ int main(int argc, char *argv[]) { free(config.ipc_socket_path); struct ev_io *ipc_io = scalloc(sizeof(struct ev_io)); ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ); - ev_io_start(loop, ipc_io); + ev_io_start(main_loop, ipc_io); } /* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */ @@ -419,22 +426,22 @@ int main(int argc, char *argv[]) { struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare)); ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); - ev_io_start(loop, xcb_watcher); + ev_io_start(main_loop, xcb_watcher); if (xkb_supported) { ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ); - ev_io_start(loop, xkb); + ev_io_start(main_loop, xkb); /* Flush the buffer so that libev can properly get new events */ XFlush(xkbdpy); } ev_check_init(xcb_check, xcb_check_cb); - ev_check_start(loop, xcb_check); + ev_check_start(main_loop, xcb_check); ev_prepare_init(xcb_prepare, xcb_prepare_cb); - ev_prepare_start(loop, xcb_prepare); + ev_prepare_start(main_loop, xcb_prepare); xcb_flush(conn); @@ -456,5 +463,5 @@ int main(int argc, char *argv[]) { } } - ev_loop(loop, 0); + ev_loop(main_loop, 0); } diff --git a/src/util.c b/src/util.c index f95ccaf3..cc93df21 100644 --- a/src/util.c +++ b/src/util.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "all.h" @@ -118,6 +119,53 @@ void start_application(const char *command) { wait(0); } +/* + * exec()s an i3 utility, for example the config file migration script or + * i3-nagbar. This function first searches $PATH for the given utility named, + * then falls back to the dirname() of the i3 executable path and then falls + * back to the dirname() of the target of /proc/self/exe (on linux). + * + * This function should be called after fork()ing. + * + * The first argument of the given argv vector will be overwritten with the + * executable name, so pass NULL. + * + * If the utility cannot be found in any of these locations, it exits with + * return code 2. + * + */ +void exec_i3_utility(char *name, char *argv[]) { + /* start the migration script, search PATH first */ + char *migratepath = name; + argv[0] = migratepath; + execvp(migratepath, argv); + + /* if the script is not in path, maybe the user installed to a strange + * location and runs the i3 binary with an absolute path. We use + * argv[0]’s dirname */ + char *pathbuf = strdup(start_argv[0]); + char *dir = dirname(pathbuf); + asprintf(&migratepath, "%s/%s", dir, name); + argv[0] = migratepath; + execvp(migratepath, argv); + +#if defined(__linux__) + /* on linux, we have one more fall-back: dirname(/proc/self/exe) */ + char buffer[BUFSIZ]; + if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) { + warn("could not read /proc/self/exe"); + exit(1); + } + dir = dirname(buffer); + asprintf(&migratepath, "%s/%s", dir, name); + argv[0] = migratepath; + execvp(migratepath, argv); +#endif + + warn("Could not start %s", name); + exit(2); +} + /* * Checks a generic cookie for errors and quits with the given message if there * was an error. @@ -358,6 +406,8 @@ char *store_restart_layout() { void i3_restart(bool forget_layout) { char *restart_filename = forget_layout ? NULL : store_restart_layout(); + kill_configerror_nagbar(true); + restore_geometry(); ipc_shutdown(); From 43b97a1cee499086b984d62d828dbdd0477bf206 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 14:34:14 +0200 Subject: [PATCH 703/867] add binaries to .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 0ae9eaeb..3284b125 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ testcases/latest *.tab.h *.yy.c *.tar.bz2* +i3 +i3-input/i3-input +i3-nagbar/i3-nagbar +i3-msg/i3-msg From 263d6b81d0efc91ea2ac34c0a77476426136d718 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 14:36:53 +0200 Subject: [PATCH 704/867] document TERM_EMU make variable in PACKAGE-MAINTAINER --- PACKAGE-MAINTAINER | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/PACKAGE-MAINTAINER b/PACKAGE-MAINTAINER index 40222803..ff39c0c0 100644 --- a/PACKAGE-MAINTAINER +++ b/PACKAGE-MAINTAINER @@ -10,16 +10,21 @@ packages for them. Please make sure the manpage for i3 will be properly created and installed in your package. +Also please provide the path to a suitable terminal emulator which is installed +as a dependency of your package (e.g. urxvt). On systems which have a special +commend to launch the best available terminal emulator, please use this one +(e.g. x-terminal-emulator on debian). + On debian, this looks like this: # Compilation - $(MAKE) + $(MAKE) TERM_EMU=x-terminal-emulator $(MAKE) -C man # Installation $(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install mkdir -p $(CURDIR)/debian/i3-wm/usr/share/man/man1 - cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 + cp man/*.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 If you have any questions, ideas, hints, problems or whatever, please do not hesitate to contact me. I will help you out. Just drop me an E-Mail (find the From d0a3e673b8e44155a0ee503690d1f71a594f5fbd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 14:41:00 +0200 Subject: [PATCH 705/867] Makefile: add i3-nagbar to install target --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 6d734063..43e2d2ab 100644 --- a/Makefile +++ b/Makefile @@ -75,6 +75,7 @@ install: all $(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)$(PREFIX)/include/i3/ $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg install $(MAKE) TOPDIR=$(TOPDIR) -C i3-input install + $(MAKE) TOPDIR=$(TOPDIR) -C i3-nagbar install dist: distclean [ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION} From 64cfb2dbb1f58f9ef012ec3033bcad4b2ba65a39 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 16:22:09 +0200 Subject: [PATCH 706/867] Revert "x: Set pixmap as background window, saves a lot of CopyAreas" This reverts commit 84b804cda61c9fda33521c11882b926ef4fc7f79. Turns out that it triggered graphic corruptions on ATI graphics cards (Thanks aniou) with certain drivers. --- src/x.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/x.c b/src/x.c index 1201fb97..a4e85c2b 100644 --- a/src/x.c +++ b/src/x.c @@ -286,9 +286,9 @@ void x_draw_decoration(Con *con) { !parent->pixmap_recreated && !con->pixmap_recreated && memcmp(p, con->deco_render_params, sizeof(struct deco_render_params)) == 0) { - DLOG("CACHE HIT, not re-rendering\n"); + DLOG("CACHE HIT, copying existing pixmaps\n"); free(p); - return; + goto copy_pixmaps; } DLOG("CACHE MISS\n"); @@ -369,7 +369,7 @@ void x_draw_decoration(Con *con) { * decoration. */ if (p->border_style != BS_NORMAL) { DLOG("border style not BS_NORMAL, aborting rendering of decoration\n"); - goto update_pixmaps; + goto copy_pixmaps; } /* 4: paint the bar */ @@ -409,7 +409,7 @@ void x_draw_decoration(Con *con) { "another container" ); - goto update_pixmaps; + goto copy_pixmaps; } int indent_level = 0, @@ -450,9 +450,9 @@ void x_draw_decoration(Con *con) { win->name_x ); -update_pixmaps: - xcb_clear_area(conn, false, con->frame, 0, 0, con->rect.width, con->rect.height); - xcb_clear_area(conn, false, parent->frame, 0, 0, parent->rect.width, parent->rect.height); +copy_pixmaps: + xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); + xcb_copy_area(conn, parent->pixmap, parent->frame, parent->pm_gc, 0, 0, 0, 0, parent->rect.width, parent->rect.height); } /* @@ -575,9 +575,6 @@ void x_push_node(Con *con) { * from the very first moment. Later calls will be cached, so this * doesn’t hurt performance. */ x_deco_recurse(con); - - uint32_t values[] = { con->pixmap }; - xcb_change_window_attributes(conn, con->frame, XCB_CW_BACK_PIXMAP, values); } DLOG("setting rect (%d, %d, %d, %d)\n", rect.x, rect.y, rect.width, rect.height); From 8be26c9a229a2f8770772f449f0b3d6ac2d841ff Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 19:58:46 +0200 Subject: [PATCH 707/867] =?UTF-8?q?x:=20Don=E2=80=99t=20set=20background?= =?UTF-8?q?=20color=20on=20frame=20windows,=20reduces=20flickering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/con.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/con.c b/src/con.c index 7ce782bc..1cf1779e 100644 --- a/src/con.c +++ b/src/con.c @@ -52,9 +52,6 @@ Con *con_new(Con *parent, i3Window *window) { x_con_init(new); - // TODO: this needs to be integrated into src/x.c and updated on config file reloads - xcb_change_window_attributes(conn, new->frame, XCB_CW_BACK_PIXEL, &config.client.background); - TAILQ_INIT(&(new->floating_head)); TAILQ_INIT(&(new->nodes_head)); TAILQ_INIT(&(new->focus_head)); From 0d8dd571f8ffcf7a589537b7fc4bbdd643955d20 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 20:01:29 +0200 Subject: [PATCH 708/867] x: when rendering stacked/tabbed cons, only update decoration once, saves some CopyAreas --- src/x.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/x.c b/src/x.c index a4e85c2b..061984f6 100644 --- a/src/x.c +++ b/src/x.c @@ -452,7 +452,6 @@ void x_draw_decoration(Con *con) { copy_pixmaps: xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); - xcb_copy_area(conn, parent->pixmap, parent->frame, parent->pm_gc, 0, 0, 0, 0, parent->rect.width, parent->rect.height); } /* @@ -463,12 +462,20 @@ copy_pixmaps: */ static void x_deco_recurse(Con *con) { Con *current; + bool leaf = TAILQ_EMPTY(&(con->nodes_head)) && + TAILQ_EMPTY(&(con->floating_head)); + con_state *state = state_for_frame(con->frame); - TAILQ_FOREACH(current, &(con->nodes_head), nodes) - x_deco_recurse(current); + if (!leaf) { + TAILQ_FOREACH(current, &(con->nodes_head), nodes) + x_deco_recurse(current); - TAILQ_FOREACH(current, &(con->floating_head), floating_windows) - x_deco_recurse(current); + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) + x_deco_recurse(current); + + if (state->mapped) + xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); + } if ((con->type != CT_ROOT && con->type != CT_OUTPUT) && con->mapped) From 517833569d340e5fd923135717eb5844fac5c50f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 20:05:49 +0200 Subject: [PATCH 709/867] x: raise the stack decoration above the stack windows (reduces flickering) This reduces flickering when opening new windows in a stack, see the comment. --- src/render.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/render.c b/src/render.c index b1388b31..17c1fde7 100644 --- a/src/render.c +++ b/src/render.c @@ -344,6 +344,12 @@ void render_con(Con *con, bool render_fullscreen) { * aswell. */ render_con(child, false); } + + /* Raise the stack con itself. This will put the stack decoration on + * top of every stack window. That way, when a new window is opened in + * the stack, the old window will not obscure part of the decoration + * (it’s unmapped afterwards). */ + x_raise_con(con); } } From afef42a4acbc6c4808fef6c0ae128f01be0e1271 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 20:08:40 +0200 Subject: [PATCH 710/867] x: set the contents of a decoration window immediately after changing its size Reduces flickering. A window loses its contents when being resized, so we have to restore them as fast as possible. --- src/x.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/x.c b/src/x.c index 061984f6..492442a7 100644 --- a/src/x.c +++ b/src/x.c @@ -585,7 +585,14 @@ void x_push_node(Con *con) { } DLOG("setting rect (%d, %d, %d, %d)\n", rect.x, rect.y, rect.width, rect.height); + /* flush to ensure that the following commands are sent in a single + * buffer and will be processed directly afterwards (the contents of a + * window get lost when resizing it, therefore we want to provide it as + * fast as possible) */ + xcb_flush(conn); xcb_set_window_rect(conn, con->frame, rect); + xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); + xcb_flush(conn); memcpy(&(state->rect), &rect, sizeof(Rect)); fake_notify = true; From 37e0cf8346f7ce87a5a637bd0e6f9b7176051e97 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 20:15:22 +0200 Subject: [PATCH 711/867] x: copy the pre-rendered pixmap contents to a decoration window immediately after mapping Reduces flickering. --- src/x.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/x.c b/src/x.c index 492442a7..759a0be1 100644 --- a/src/x.c +++ b/src/x.c @@ -641,6 +641,10 @@ void x_push_node(Con *con) { values[0] = FRAME_EVENT_MASK; xcb_change_window_attributes(conn, con->frame, XCB_CW_EVENT_MASK, values); + /* copy the pixmap contents to the frame window immediately after mapping */ + xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); + xcb_flush(conn); + DLOG("mapping container %08x (serial %d)\n", con->frame, cookie.sequence); state->mapped = con->mapped; } From 8f4b95dccd41058da447251f133a6c953a596621 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 20:18:06 +0200 Subject: [PATCH 712/867] x: change EnterNotify event mask only for mapped windows Saves a few ChangeWindowAttributes requests. --- src/x.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/x.c b/src/x.c index 759a0be1..45fd347f 100644 --- a/src/x.c +++ b/src/x.c @@ -729,7 +729,8 @@ void x_push_changes(Con *con) { //DLOG("Disabling EnterNotify\n"); uint32_t values[1] = { XCB_NONE }; CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { - xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); + if (state->mapped) + xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); } //DLOG("Done, EnterNotify disabled\n"); bool order_changed = false; @@ -752,11 +753,10 @@ void x_push_changes(Con *con) { state->initial = false; } //DLOG("Re-enabling EnterNotify\n"); + values[0] = FRAME_EVENT_MASK; CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { - values[0] = FRAME_EVENT_MASK; - if (!state->mapped) - values[0] &= ~XCB_EVENT_MASK_ENTER_WINDOW; - xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); + if (state->mapped) + xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); } //DLOG("Done, EnterNotify re-enabled\n"); From d7f9700ba41db61788a7b0f22350cdd9d008a907 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 21:19:31 +0200 Subject: [PATCH 713/867] x: use PolySegment instead of two PolyLine requests --- src/x.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/x.c b/src/x.c index 45fd347f..b40a5c38 100644 --- a/src/x.c +++ b/src/x.c @@ -377,17 +377,17 @@ void x_draw_decoration(Con *con) { xcb_rectangle_t drect = { con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height }; xcb_poly_fill_rectangle(conn, parent->pixmap, parent->pm_gc, 1, &drect); - /* 5: draw the two lines in border color */ - xcb_draw_line(conn, parent->pixmap, parent->pm_gc, p->color->border, - con->deco_rect.x, /* x */ - con->deco_rect.y, /* y */ - con->deco_rect.x + con->deco_rect.width, /* to_x */ - con->deco_rect.y); /* to_y */ - xcb_draw_line(conn, parent->pixmap, parent->pm_gc, p->color->border, - con->deco_rect.x, /* x */ - con->deco_rect.y + con->deco_rect.height - 1, /* y */ - con->deco_rect.x + con->deco_rect.width, /* to_x */ - con->deco_rect.y + con->deco_rect.height - 1); /* to_y */ + /* 5: draw two unconnected lines in border color */ + xcb_change_gc_single(conn, parent->pm_gc, XCB_GC_FOREGROUND, p->color->border); + Rect *dr = &(con->deco_rect); + xcb_segment_t segments[] = { + { dr->x, dr->y, + dr->x + dr->width, dr->y }, + + { dr->x, dr->y + dr->height - 1, + dr->x + dr->width, dr->y + dr->height - 1 } + }; + xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments); /* 6: draw the title */ uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; From 7d9ba707b31be69ca07c931d0920928d25b895ab Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 21:54:34 +0200 Subject: [PATCH 714/867] =?UTF-8?q?x:=20Don=E2=80=99t=20pre-render=20the?= =?UTF-8?q?=20decoration=20for=20windows=20inside=20a=20stack=20which=20ar?= =?UTF-8?q?e=20not=20visible?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Saves n-1 decoration renderings for n windows in a stack whenever a new window gets added or removed. --- src/x.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/x.c b/src/x.c index b40a5c38..85e9f41a 100644 --- a/src/x.c +++ b/src/x.c @@ -578,10 +578,15 @@ void x_push_node(Con *con) { con->pixmap_recreated = true; - /* Render the decoration now to make the correct decoration visible - * from the very first moment. Later calls will be cached, so this - * doesn’t hurt performance. */ - x_deco_recurse(con); + /* Don’t render the decoration for windows inside a stack which are + * not visible right now */ + if (!con->parent || + con->parent->layout != L_STACKED || + TAILQ_FIRST(&(con->parent->focus_head)) == con) + /* Render the decoration now to make the correct decoration visible + * from the very first moment. Later calls will be cached, so this + * doesn’t hurt performance. */ + x_deco_recurse(con); } DLOG("setting rect (%d, %d, %d, %d)\n", rect.x, rect.y, rect.width, rect.height); From 78d25c4cd9d11213070728db96354dd51b657c3c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 22:06:16 +0200 Subject: [PATCH 715/867] expose event handler: use x_deco_recurse --- include/x.h | 8 ++++++++ src/handlers.c | 20 +++----------------- src/x.c | 2 +- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/include/x.h b/include/x.h index b8ead5ee..df4ee276 100644 --- a/include/x.h +++ b/include/x.h @@ -60,6 +60,14 @@ void x_window_kill(xcb_window_t window, kill_window_t kill_window); */ void x_draw_decoration(Con *con); +/** + * Recursively calls x_draw_decoration. This cannot be done in x_push_node + * because x_push_node uses focus order to recurse (see the comment above) + * while drawing the decoration needs to happen in the actual order. + * + */ +void x_deco_recurse(Con *con); + /** * This function pushes the properties of each node of the layout tree to * X11 if they have changed (like the map state, position of the window, …). diff --git a/src/handlers.c b/src/handlers.c index 583b0c5a..8e9bbb04 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -586,7 +586,7 @@ static int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t * */ static int handle_expose_event(xcb_expose_event_t *event) { - Con *parent, *con; + Con *parent; /* event->count is the number of minimum remaining expose events for this * window, so we skip all events but the last one */ @@ -600,22 +600,8 @@ static int handle_expose_event(xcb_expose_event_t *event) { return 1; } - if (parent->window) - x_draw_decoration(parent); - - TAILQ_FOREACH(con, &(parent->nodes_head), nodes) { - DLOG("expose for con %p / %s\n", con, con->name); - if (con->window) - x_draw_decoration(con); - } - - /* We also need to render the decorations of other Cons nearby the Con - * itself to not get overlapping decorations */ - TAILQ_FOREACH(con, &(parent->parent->nodes_head), nodes) { - DLOG("expose for con %p / %s\n", con, con->name); - if (con->window) - x_draw_decoration(con); - } + /* re-render the parent (recursively, if it’s a split con) */ + x_deco_recurse(parent); xcb_flush(conn); return 1; diff --git a/src/x.c b/src/x.c index 85e9f41a..96c9d806 100644 --- a/src/x.c +++ b/src/x.c @@ -460,7 +460,7 @@ copy_pixmaps: * while drawing the decoration needs to happen in the actual order. * */ -static void x_deco_recurse(Con *con) { +void x_deco_recurse(Con *con) { Con *current; bool leaf = TAILQ_EMPTY(&(con->nodes_head)) && TAILQ_EMPTY(&(con->floating_head)); From 85d851de5f90d7dc8a9bf61878f5a1467aa24a1f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 22:27:31 +0200 Subject: [PATCH 716/867] x: disable GraphicsExposure events on our pixmap graphics contexts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gets rid of GraphicsExposure and NoExpose events, which we don’t use anyways. --- src/x.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/x.c b/src/x.c index 96c9d806..525d9bbd 100644 --- a/src/x.c +++ b/src/x.c @@ -574,7 +574,13 @@ void x_push_node(Con *con) { xcb_free_gc(conn, con->pm_gc); } xcb_create_pixmap(conn, root_depth, con->pixmap, con->frame, rect.width, rect.height); - xcb_create_gc(conn, con->pm_gc, con->pixmap, 0, 0); + /* For the graphics context, we disable GraphicsExposure events. + * Those will be sent when a CopyArea request cannot be fulfilled + * properly due to parts of the source being unmapped or otherwise + * unavailable. Since we always copy from pixmaps to windows, this + * is not a concern for us. */ + uint32_t values[] = { 0 }; + xcb_create_gc(conn, con->pm_gc, con->pixmap, XCB_GC_GRAPHICS_EXPOSURES, values); con->pixmap_recreated = true; From d9038cdb8079f2c73210635d55e3eee5f51b7345 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 22:57:52 +0200 Subject: [PATCH 717/867] x: Only render / copy pixmap when the pixmap was actually created Fixes some X11 errors --- src/x.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/x.c b/src/x.c index 525d9bbd..791069d5 100644 --- a/src/x.c +++ b/src/x.c @@ -257,6 +257,14 @@ void x_draw_decoration(Con *con) { return; } + /* Skip containers whose pixmap has not yet been created (can happen when + * decoration rendering happens recursively for a window for which + * x_push_node() was not yet called) */ + if (con->pixmap == XCB_NONE) { + DLOG("pixmap not yet created, not rendering\n"); + return; + } + /* 1: build deco_params and compare with cache */ struct deco_render_params *p = scalloc(sizeof(struct deco_render_params)); @@ -602,7 +610,8 @@ void x_push_node(Con *con) { * fast as possible) */ xcb_flush(conn); xcb_set_window_rect(conn, con->frame, rect); - xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); + if (con->pixmap != XCB_NONE) + xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); xcb_flush(conn); memcpy(&(state->rect), &rect, sizeof(Rect)); @@ -653,7 +662,8 @@ void x_push_node(Con *con) { xcb_change_window_attributes(conn, con->frame, XCB_CW_EVENT_MASK, values); /* copy the pixmap contents to the frame window immediately after mapping */ - xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); + if (con->pixmap != XCB_NONE) + xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); xcb_flush(conn); DLOG("mapping container %08x (serial %d)\n", con->frame, cookie.sequence); From 937a80511a887f88e1ae3888ba533283ad146a18 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 22:59:04 +0200 Subject: [PATCH 718/867] x: only configure window coordinates when height > 0 Fixes some X11 errors --- src/x.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/x.c b/src/x.c index 791069d5..3c2e1ef6 100644 --- a/src/x.c +++ b/src/x.c @@ -558,8 +558,9 @@ void x_push_node(Con *con) { } bool fake_notify = false; - /* set new position if rect changed */ - if (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0) { + /* Set new position if rect changed (and if height > 0) */ + if (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0 && + rect.height > 0) { /* We first create the new pixmap, then render to it, set it as the * background and only afterwards change the window size. This reduces * flickering. */ @@ -568,8 +569,7 @@ void x_push_node(Con *con) { * is enough to check if width/height have changed. Also, we don’t * create a pixmap at all when the window is actually not visible * (height == 0). */ - if (rect.height > 0 && - (state->rect.width != rect.width || + if ((state->rect.width != rect.width || state->rect.height != rect.height)) { DLOG("CACHE: creating new pixmap for con %p (old: %d x %d, new: %d x %d)\n", con, state->rect.width, state->rect.height, From 48f5166daf684c53a319824222909d0d9225d557 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 23:11:21 +0200 Subject: [PATCH 719/867] log: display time in microseconds when DEBUG_TIMING is set --- src/log.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/log.c b/src/log.c index 99c2d4d3..22b7fffe 100644 --- a/src/log.c +++ b/src/log.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "util.h" #include "log.h" @@ -89,7 +90,13 @@ void vlog(char *fmt, va_list args) { struct tm *tmp = localtime(&t); /* Generate time prefix */ strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp); +#ifdef DEBUG_TIMING + struct timeval tv; + gettimeofday(&tv, NULL); + printf("%s%d.%d - ", timebuf, tv.tv_sec, tv.tv_usec); +#else printf("%s", timebuf); +#endif vprintf(fmt, args); } From 05e39c1c480de807b59bd13386d2cffe35e564b6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Jul 2011 23:44:13 +0200 Subject: [PATCH 720/867] Ignore X11 errors caused by ReparentWindow / ChangeProperty on already destroyed windows These errors can happen because a DestroyWindow request by a client will trigger an UnmapNotify, then a DestroyNotify. We cannot distinguish this UnmapNotify from an UnmapNotify not followed by a DestroyNotify, so we just try to send the ReparentWindow / ChangeProperty and ignore the errors, if any. --- include/handlers.h | 14 ++++++++++++++ src/handlers.c | 14 +++++++++++--- src/main.c | 4 +++- src/tree.c | 16 +++++++++++++--- src/xcb.c | 2 +- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/include/handlers.h b/include/handlers.h index 839bac61..0aaaf158 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -15,8 +15,22 @@ extern int randr_base; +/** + * Adds the given sequence to the list of events which are ignored. + * If this ignore should only affect a specific response_type, pass + * response_type, otherwise, pass -1. + * + * Every ignored sequence number gets garbage collected after 5 seconds. + * + */ void add_ignore_event(const int sequence, const int response_type); +/** + * Checks if the given sequence is ignored and returns true if so. + * + */ +bool event_is_ignored(const int sequence, const int response_type); + /** * Takes an xcb_generic_event_t and calls the appropriate handler, based on the * event type. diff --git a/src/handlers.c b/src/handlers.c index 8e9bbb04..0b64d141 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -21,6 +21,14 @@ int randr_base = -1; changing workspaces */ static SLIST_HEAD(ignore_head, Ignore_Event) ignore_events; +/* + * Adds the given sequence to the list of events which are ignored. + * If this ignore should only affect a specific response_type, pass + * response_type, otherwise, pass -1. + * + * Every ignored sequence number gets garbage collected after 5 seconds. + * + */ void add_ignore_event(const int sequence, const int response_type) { struct Ignore_Event *event = smalloc(sizeof(struct Ignore_Event)); @@ -35,7 +43,7 @@ void add_ignore_event(const int sequence, const int response_type) { * Checks if the given sequence is ignored and returns true if so. * */ -static bool event_is_ignored(const int sequence, const int response_type) { +bool event_is_ignored(const int sequence, const int response_type) { struct Ignore_Event *event; time_t now = time(NULL); for (event = SLIST_FIRST(&ignore_events); event != SLIST_END(&ignore_events);) { @@ -51,7 +59,7 @@ static bool event_is_ignored(const int sequence, const int response_type) { if (event->sequence != sequence) continue; - if (event->response_type != 0 && + if (event->response_type != -1 && event->response_type != response_type) continue; @@ -286,7 +294,7 @@ static int handle_map_request(xcb_map_request_event_t *event) { cookie = xcb_get_window_attributes_unchecked(conn, event->window); DLOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence); - add_ignore_event(event->sequence, 0); + add_ignore_event(event->sequence, -1); manage_window(event->window, cookie, false); x_push_changes(croot); diff --git a/src/main.c b/src/main.c index a2764cc1..7910c8b1 100644 --- a/src/main.c +++ b/src/main.c @@ -70,7 +70,9 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { while ((event = xcb_poll_for_event(conn)) != NULL) { if (event->response_type == 0) { - ELOG("X11 Error received! sequence %x\n", event->sequence); + if (event_is_ignored(event->sequence, 0)) + DLOG("Expected X11 Error received for sequence %x\n", event->sequence); + else ELOG("X11 Error received! sequence %x\n", event->sequence); continue; } diff --git a/src/tree.c b/src/tree.c index caf29678..272276f4 100644 --- a/src/tree.c +++ b/src/tree.c @@ -153,13 +153,23 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent) { x_window_kill(con->window->id, kill_window); return false; } else { + xcb_void_cookie_t cookie; /* un-parent the window */ - xcb_reparent_window(conn, con->window->id, root, 0, 0); + cookie = xcb_reparent_window(conn, con->window->id, root, 0, 0); + + /* Ignore X11 errors for the ReparentWindow request. + * X11 Errors are returned when the window was already destroyed */ + add_ignore_event(cookie.sequence, 0); + /* We are no longer handling this window, thus set WM_STATE to * WM_STATE_WITHDRAWN (see ICCCM 4.1.3.1) */ long data[] = { XCB_ICCCM_WM_STATE_WITHDRAWN, XCB_NONE }; - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, - A_WM_STATE, A_WM_STATE, 32, 2, data); + cookie = xcb_change_property(conn, XCB_PROP_MODE_REPLACE, + con->window->id, A_WM_STATE, A_WM_STATE, 32, 2, data); + + /* Ignore X11 errors for the ReparentWindow request. + * X11 Errors are returned when the window was already destroyed */ + add_ignore_event(cookie.sequence, 0); } FREE(con->window->class_class); FREE(con->window->class_instance); diff --git a/src/xcb.c b/src/xcb.c index 3fd0bfcd..07f3ce1e 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -328,7 +328,7 @@ void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r) { XCB_CONFIG_WINDOW_HEIGHT, &(r.x)); /* ignore events which are generated because we configured a window */ - add_ignore_event(cookie.sequence, 0); + add_ignore_event(cookie.sequence, -1); } /* From 429d3100118d205f7d50ce323ae47a11181afee5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 16:40:10 +0200 Subject: [PATCH 721/867] nagbar: use less / vi as fallbacks for PAGER / EDITOR --- src/cfgparse.y | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 22747108..64be7908 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -258,9 +258,9 @@ static void start_configerror_nagbar(const char *config_path) { if (configerror_pid == 0) { char *editaction, *pageraction; - if (asprintf(&editaction, TERM_EMU " -e $EDITOR \"%s\"", config_path) == -1) + if (asprintf(&editaction, TERM_EMU " -e ${EDITOR:-vi} \"%s\"", config_path) == -1) exit(1); - if (asprintf(&pageraction, TERM_EMU " -e $PAGER \"%s\"", errorfilename) == -1) + if (asprintf(&pageraction, TERM_EMU " -e ${PAGER:-less} \"%s\"", errorfilename) == -1) exit(1); char *argv[] = { NULL, /* will be replaced by the executable path */ From 897b53f1c28d10564c401bfe467b98f60beed2bc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 16:51:57 +0200 Subject: [PATCH 722/867] Bugfix: run nagbar commands through sh(1) (Thanks Tucos) --- src/cfgparse.y | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 64be7908..09201c71 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -258,9 +258,9 @@ static void start_configerror_nagbar(const char *config_path) { if (configerror_pid == 0) { char *editaction, *pageraction; - if (asprintf(&editaction, TERM_EMU " -e ${EDITOR:-vi} \"%s\"", config_path) == -1) + if (asprintf(&editaction, TERM_EMU " -e sh -c \"${EDITOR:-vi} \"%s\"\"", config_path) == -1) exit(1); - if (asprintf(&pageraction, TERM_EMU " -e ${PAGER:-less} \"%s\"", errorfilename) == -1) + if (asprintf(&pageraction, TERM_EMU " -e sh -c \"${PAGER:-less} \"%s\"\"", errorfilename) == -1) exit(1); char *argv[] = { NULL, /* will be replaced by the executable path */ From fa0c8e41403556153d9e006b329fa2ff19091091 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 16:57:16 +0200 Subject: [PATCH 723/867] default config: add binding to change focus between tiling / floating windows --- i3.config | 3 +++ 1 file changed, 3 insertions(+) diff --git a/i3.config b/i3.config index 4c49e4bc..b1a75bb3 100644 --- a/i3.config +++ b/i3.config @@ -59,6 +59,9 @@ bindsym Mod1+l layout default # toggle tiling / floating bindsym Mod1+Shift+space floating toggle +# change focus between tiling / floating windows +bindsym Mod1+space focus mode_toggle + # focus the parent container bindsym Mod1+u focus parent From 675785005d865283519f54b4488674397874c16c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 17:11:13 +0200 Subject: [PATCH 724/867] default config: add resize mode --- i3.config | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/i3.config b/i3.config index b1a75bb3..e2e5b4b6 100644 --- a/i3.config +++ b/i3.config @@ -98,3 +98,29 @@ bindsym Mod1+Shift+j reload bindsym Mod1+Shift+c restart # exit i3 (logs you out of your X session) bindsym Mod1+Shift+l exit + +# resize window (you can also use the mouse for that) +mode "resize" { + # These bindings trigger as soon as you enter the resize mode + + # They resize the border in the direction you pressed, e.g. + # when pressing left, the window is resized so that it has + # more space on its left + + bindsym n resize shrink left 10 px or 10 ppt + bindsym Shift+n resize grow left 10 px or 10 ppt + + bindsym r resize shrink down 10 px or 10 ppt + bindsym Shift+r resize grow down 10 px or 10 ppt + + bindsym t resize shrink up 10 px or 10 ppt + bindsym Shift+t resize grow up 10 px or 10 ppt + + bindsym d resize shrink right 10 px or 10 ppt + bindsym Shift+d resize grow right 10 px or 10 ppt + + bindsym Return mode "default" + bindsym Escape mode "default" +} + +bindsym Mod1+r mode "resize" From 6fb4d91daf9c7ef5d3e71aa5575a47757842bfdc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 17:22:10 +0200 Subject: [PATCH 725/867] default config: convert to QWERTY, add comments --- i3.config | 70 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/i3.config b/i3.config index e2e5b4b6..afd2fe5d 100644 --- a/i3.config +++ b/i3.config @@ -1,7 +1,13 @@ # i3 config file (v4) # -# This configuration file was written for the NEO layout. If you are using a -# different layout, you should change it. +# Please see http://i3wm.org/docs/userguide.html for a complete reference! +# +# This config file uses keycodes (bindsym) and was written for the QWERTY +# layout. +# +# To get a config file with the same key positions, but for your current +# layout, use the i3-config-wizard +# # font for window titles. ISO 10646 = Unicode font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 @@ -13,16 +19,16 @@ floating_modifier Mod1 bindsym Mod1+Return exec /usr/bin/urxvt # kill focused window -bindsym Mod1+c kill +bindsym Mod1+Shift+q kill # start dmenu (a program launcher) -bindsym Mod1+p exec /usr/bin/dmenu_run +bindsym Mod1+d exec /usr/bin/dmenu_run # change focus -bindsym Mod1+n focus left -bindsym Mod1+r focus down -bindsym Mod1+t focus up -bindsym Mod1+d focus right +bindsym Mod1+j focus left +bindsym Mod1+k focus down +bindsym Mod1+l focus up +bindsym Mod1+semicolon focus right # alternatively, you can use the cursor keys: bindsym Mod1+Left focus left @@ -31,10 +37,10 @@ bindsym Mod1+Up focus up bindsym Mod1+Right focus right # move focused window -bindsym Mod1+Shift+n move left -bindsym Mod1+Shift+r move down -bindsym Mod1+Shift+t move up -bindsym Mod1+Shift+d move right +bindsym Mod1+Shift+j move left +bindsym Mod1+Shift+k move down +bindsym Mod1+Shift+l move up +bindsym Mod1+Shift+semicolon move right # alternatively, you can use the cursor keys: bindsym Mod1+Shift+Left move left @@ -54,7 +60,7 @@ bindsym Mod1+f fullscreen # change container layout (stacked, tabbed, default) bindsym Mod1+s layout stacking bindsym Mod1+w layout tabbed -bindsym Mod1+l layout default +bindsym Mod1+e layout default # toggle tiling / floating bindsym Mod1+Shift+space floating toggle @@ -63,7 +69,7 @@ bindsym Mod1+Shift+space floating toggle bindsym Mod1+space focus mode_toggle # focus the parent container -bindsym Mod1+u focus parent +bindsym Mod1+a focus parent # focus the child container #bindsym Mod1+d focus child @@ -93,11 +99,11 @@ bindsym Mod1+Shift+9 move workspace 9 bindsym Mod1+Shift+0 move workspace 10 # reload the configuration file -bindsym Mod1+Shift+j reload +bindsym Mod1+Shift+c reload # restart i3 inplace (preserves your layout/session, can be used to upgrade i3) -bindsym Mod1+Shift+c restart +bindsym Mod1+Shift+r restart # exit i3 (logs you out of your X session) -bindsym Mod1+Shift+l exit +bindsym Mod1+Shift+e exit # resize window (you can also use the mouse for that) mode "resize" { @@ -107,18 +113,32 @@ mode "resize" { # when pressing left, the window is resized so that it has # more space on its left - bindsym n resize shrink left 10 px or 10 ppt - bindsym Shift+n resize grow left 10 px or 10 ppt + bindsym j resize shrink left 10 px or 10 ppt + bindsym Shift+j resize grow left 10 px or 10 ppt - bindsym r resize shrink down 10 px or 10 ppt - bindsym Shift+r resize grow down 10 px or 10 ppt + bindsym k resize shrink down 10 px or 10 ppt + bindsym Shift+k resize grow down 10 px or 10 ppt - bindsym t resize shrink up 10 px or 10 ppt - bindsym Shift+t resize grow up 10 px or 10 ppt + bindsym l resize shrink up 10 px or 10 ppt + bindsym Shift+l resize grow up 10 px or 10 ppt - bindsym d resize shrink right 10 px or 10 ppt - bindsym Shift+d resize grow right 10 px or 10 ppt + bindsym semicolon resize shrink right 10 px or 10 ppt + bindsym Shift+semicolon resize grow right 10 px or 10 ppt + # same bindings, but for the arrow keys + bindsym Left resize shrink left 10 px or 10 ppt + bindsym Shift+Left resize grow left 10 px or 10 ppt + + bindsym Down resize shrink down 10 px or 10 ppt + bindsym Shift+Down resize grow down 10 px or 10 ppt + + bindsym Up resize shrink up 10 px or 10 ppt + bindsym Shift+Up resize grow up 10 px or 10 ppt + + bindsym Right resize shrink right 10 px or 10 ppt + bindsym Shift+Right resize grow right 10 px or 10 ppt + + # back to normal: Enter or Escape bindsym Return mode "default" bindsym Escape mode "default" } From 7584ef48796ab031369e996ca0cb08a2833e9f6e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 17:40:18 +0200 Subject: [PATCH 726/867] add i3.config.keycodes, default config, but using bindcode (template for i3-config-wizard) --- i3.config.keycodes | 143 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 i3.config.keycodes diff --git a/i3.config.keycodes b/i3.config.keycodes new file mode 100644 index 00000000..c22b0f2c --- /dev/null +++ b/i3.config.keycodes @@ -0,0 +1,143 @@ +# WARNING +# WARNING: This configuration file is a template for the i3-config-wizard to +# WARNING: generate a config which uses keysyms in your current layout. It does +# WARNING: not get loaded by i3. Please do not change it. +# WARNING + +set $mod Mod1 + +# font for window titles. ISO 10646 = Unicode +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +# Use Mouse+$mod to drag floating windows to their wanted position +floating_modifier $mod + +# start a terminal +bindcode $mod+36 exec /usr/bin/urxvt + +# kill focused window +bindcode $mod+Shift+24 kill + +# start dmenu (a program launcher) +bindcode $mod+40 exec /usr/bin/dmenu_run + +# change focus +bindcode $mod+44 focus left +bindcode $mod+45 focus down +bindcode $mod+46 focus up +bindcode $mod+47 focus right + +# alternatively, you can use the cursor keys: +bindcode $mod+113 focus left +bindcode $mod+116 focus down +bindcode $mod+111 focus up +bindcode $mod+114 focus right + +# move focused window +bindcode $mod+Shift+44 move left +bindcode $mod+Shift+45 move down +bindcode $mod+Shift+46 move up +bindcode $mod+Shift+47 move right + +# alternatively, you can use the cursor keys: +bindcode $mod+Shift+113 move left +bindcode $mod+Shift+116 move down +bindcode $mod+Shift+111 move up +bindcode $mod+Shift+114 move right + +# split in horizontal orientation +bindcode $mod+43 split h + +# split in vertical orientation +bindcode $mod+55 split v + +# enter fullscreen mode for the focused container +bindcode $mod+41 fullscreen + +# change container layout (stacked, tabbed, default) +bindcode $mod+39 layout stacking +bindcode $mod+25 layout tabbed +bindcode $mod+26 layout default + +# toggle tiling / floating +bindcode $mod+Shift+65 floating toggle + +# change focus between tiling / floating windows +bindcode $mod+65 focus mode_toggle + +# focus the parent container +bindcode $mod+38 focus parent + +# focus the child container +#bindcode $mod+d focus child + +# switch to workspace +bindcode $mod+10 workspace 1 +bindcode $mod+11 workspace 2 +bindcode $mod+12 workspace 3 +bindcode $mod+13 workspace 4 +bindcode $mod+14 workspace 5 +bindcode $mod+15 workspace 6 +bindcode $mod+16 workspace 7 +bindcode $mod+17 workspace 8 +bindcode $mod+18 workspace 9 +bindcode $mod+19 workspace 10 + +# move focused container to workspace +bindcode $mod+Shift+10 move workspace 1 +bindcode $mod+Shift+11 move workspace 2 +bindcode $mod+Shift+12 move workspace 3 +bindcode $mod+Shift+13 move workspace 4 +bindcode $mod+Shift+14 move workspace 5 +bindcode $mod+Shift+15 move workspace 6 +bindcode $mod+Shift+16 move workspace 7 +bindcode $mod+Shift+17 move workspace 8 +bindcode $mod+Shift+18 move workspace 9 +bindcode $mod+Shift+19 move workspace 10 + +# reload the configuration file +bindcode $mod+Shift+54 reload +# restart i3 inplace (preserves your layout/session, can be used to upgrade i3) +bindcode $mod+Shift+27 restart +# exit i3 (logs you out of your X session) +bindcode $mod+Shift+26 exit + +# resize window (you can also use the mouse for that) +mode "resize" { + # These bindings trigger as soon as you enter the resize mode + + # They resize the border in the direction you pressed, e.g. + # when pressing left, the window is resized so that it has + # more space on its left + + bindcode 44 resize shrink left 10 px or 10 ppt + bindcode Shift+44 resize grow left 10 px or 10 ppt + + bindcode 45 resize shrink down 10 px or 10 ppt + bindcode Shift+45 resize grow down 10 px or 10 ppt + + bindcode 46 resize shrink up 10 px or 10 ppt + bindcode Shift+46 resize grow up 10 px or 10 ppt + + bindcode 47 resize shrink right 10 px or 10 ppt + bindcode Shift+47 resize grow right 10 px or 10 ppt + + # same bindings, but for the arrow keys + bindcode 113 resize shrink left 10 px or 10 ppt + bindcode Shift+113 resize grow left 10 px or 10 ppt + + bindcode 116 resize shrink down 10 px or 10 ppt + bindcode Shift+116 resize grow down 10 px or 10 ppt + + bindcode 111 resize shrink up 10 px or 10 ppt + bindcode Shift+111 resize grow up 10 px or 10 ppt + + bindcode 114 resize shrink right 10 px or 10 ppt + bindcode Shift+114 resize grow right 10 px or 10 ppt + + # back to normal: Enter or Escape + bindcode 36 mode "default" + bindcode 9 mode "default" +} + +bindcode $mod+27 mode "resize" From 6fb186c77cf5494c5db15164a32091c4923bf421 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 18:12:57 +0200 Subject: [PATCH 727/867] common.mk: correctly determine git branch for subfolders --- common.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.mk b/common.mk index 41946d41..ff12bdb0 100644 --- a/common.mk +++ b/common.mk @@ -10,7 +10,7 @@ SYSCONFDIR=$(PREFIX)/etc endif TERM_EMU=xterm # The escaping is absurd, but we need to escape for shell, sed, make, define -GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f .git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' .git/HEAD || echo 'unknown'))" +GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f $(TOPDIR)/.git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' $(TOPDIR)/.git/HEAD || echo 'unknown'))" VERSION:=$(shell git describe --tags --abbrev=0) ifeq ($(shell which pkg-config 2>/dev/null 1>/dev/null || echo 1),1) From 05f7a50d449b037aede423bdf149854d25485865 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 18:20:50 +0200 Subject: [PATCH 728/867] makefile: use src/*.c again, we no longer have files which should not be compiled --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 43e2d2ab..cca9ab32 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c -FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c src/move.c src/output.c src/ewmh.c src/assignments.c +FILES:=$(wildcard src/*.c) FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) From 06054642fe5fb91d481ac5041c6892441b488a2b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 18:21:25 +0200 Subject: [PATCH 729/867] debug.c: remove handle_event --- src/debug.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/debug.c b/src/debug.c index de47fca2..1e0f828d 100644 --- a/src/debug.c +++ b/src/debug.c @@ -244,7 +244,3 @@ int format_event(xcb_generic_event_t *e) { fflush(stdout); return 1; } - -int handle_event(void *ignored, xcb_connection_t *c, xcb_generic_event_t *e) { - return format_event(e); -} From 29e2e696ed807de0de3fe35f8d599aae8f84fb19 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 19:24:04 +0200 Subject: [PATCH 730/867] makefile: install ${SYSCONFDIR}/i3/config.keycodes --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index cca9ab32..e35019e6 100644 --- a/Makefile +++ b/Makefile @@ -70,6 +70,7 @@ install: all $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/ $(INSTALL) -m 0755 i3-migrate-config-to-v4.pl $(DESTDIR)$(PREFIX)/bin/ test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config + test -e $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes || $(INSTALL) -m 0644 i3.config.keycodes $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes $(INSTALL) -m 0644 i3.welcome $(DESTDIR)$(SYSCONFDIR)/i3/welcome $(INSTALL) -m 0644 i3.desktop $(DESTDIR)$(PREFIX)/share/xsessions/ $(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)$(PREFIX)/include/i3/ From 2897a761c8e95d8ab701b7d8c5d09f9c3a4e972a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 19:30:26 +0200 Subject: [PATCH 731/867] makefile: bugfix: filter out auto-generated files --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e35019e6..9774eff8 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c -FILES:=$(wildcard src/*.c) +FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c)) FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) From 85289636a6470c6070fd93cbdf68fcf645c693fa Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 19:41:15 +0200 Subject: [PATCH 732/867] config-wizard: correctly handle shift-only bindings --- i3-config-wizard/cfgparse.y | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/i3-config-wizard/cfgparse.y b/i3-config-wizard/cfgparse.y index f2e4a054..018b37b2 100644 --- a/i3-config-wizard/cfgparse.y +++ b/i3-config-wizard/cfgparse.y @@ -86,14 +86,16 @@ char *rewrite_binding(const char *bindingline) { static char *modifier_to_string(int modifiers) { //printf("should convert %d to string\n", modifiers); if (modifiers == (1 << 3)) - return strdup("Mod1"); + return strdup("$mod+"); else if (modifiers == ((1 << 3) | (1 << 0))) - return strdup("Mod1+shift"); + return strdup("$mod+Shift+"); else if (modifiers == (1 << 9)) - return strdup("$mod"); + return strdup("$mod+"); else if (modifiers == ((1 << 9) | (1 << 0))) - return strdup("$mod+shift"); - else return strdup("UNKNOWN"); + return strdup("$mod+Shift+"); + else if (modifiers == (1 << 0)) + return strdup("Shift+"); + else return strdup(""); } %} @@ -139,7 +141,7 @@ bindcode: char *str = XKeysymToString(sym); char *modifiers = modifier_to_string($3); // TODO: modifier to string - asprintf(&(context->result), "bindsym %s+%s %s\n", modifiers, str, $6); + asprintf(&(context->result), "bindsym %s%s %s\n", modifiers, str, $6); free(modifiers); } ; From 81340523be7cd33cf0f05aadb66304d28f990430 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 19:41:49 +0200 Subject: [PATCH 733/867] config-wizard: read config from SYSCONFDIR/i3/config.keycodes --- i3-config-wizard/main.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index a4dd070c..c8d148cb 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -35,6 +35,12 @@ #include #include +/* We need SYSCONFDIR for the path to the keycode config template, so raise an + * error if it’s not defined for whatever reason */ +#ifndef SYSCONFDIR +#error "SYSCONFDIR not defined" +#endif + #define FREE(pointer) do { \ if (pointer != NULL) { \ free(pointer); \ @@ -217,9 +223,9 @@ static void finish() { if (!(dpy = XOpenDisplay(NULL))) errx(1, "Could not connect to X11"); - FILE *kc_config = fopen("../i3.config.kc", "r"); + FILE *kc_config = fopen(SYSCONFDIR "/i3/config.keycodes", "r"); if (kc_config == NULL) - err(1, "Could not open input file \"%s\"", "../i3.config.kc"); + err(1, "Could not open input file \"%s\"", SYSCONFDIR "/i3/config.keycodes"); FILE *ks_config = fopen(config_path, "w"); if (ks_config == NULL) From e79e07104ca97a8d100578720682a058e242caa6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 19:42:10 +0200 Subject: [PATCH 734/867] config-wizard: write config to ~/.i3/config, create ~/.i3 if necessary --- i3-config-wizard/main.c | 47 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index c8d148cb..b54d9eb8 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -55,7 +56,7 @@ while (0) enum { STEP_WELCOME, STEP_GENERATE } current_step = STEP_WELCOME; enum { MOD_ALT, MOD_SUPER } modifier = MOD_SUPER; -static char *config_path = "/tmp/wizout/i3.config"; +static char *config_path; static xcb_connection_t *conn; static uint32_t font_id; static uint32_t font_bold_id; @@ -72,6 +73,38 @@ Display *dpy; char *rewrite_binding(const char *bindingline); static void finish(); +/* + * 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 ? 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; +} + /* * Try to get the socket path from X11 and return NULL if it doesn’t work. * As i3-msg is a short-running tool, we don’t bother with cleaning up the @@ -294,6 +327,7 @@ static void finish() { } int main(int argc, char *argv[]) { + config_path = resolve_tilde("~/.i3/config"); socket_path = getenv("I3SOCK"); char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; char *patternbold = "-misc-fixed-bold-r-normal--13-120-75-75-C-70-iso10646-1"; @@ -319,10 +353,10 @@ int main(int argc, char *argv[]) { socket_path = strdup(optarg); break; case 'v': - printf("i3-config-wizard " I3_VERSION); + printf("i3-config-wizard " I3_VERSION "\n"); return 0; case 'h': - printf("i3-config-wizard " I3_VERSION); + printf("i3-config-wizard " I3_VERSION "\n"); printf("i3-config-wizard [-s ] [-v]\n"); return 0; } @@ -336,6 +370,13 @@ int main(int argc, char *argv[]) { return 0; } + /* Create ~/.i3 if it does not yet exist */ + char *config_dir = resolve_tilde("~/.i3"); + if (stat(config_dir, &stbuf) != 0) + if (mkdir(config_dir, 0755) == -1) + err(1, "mkdir(%s) failed", config_dir); + free(config_dir); + int fd; if ((fd = open(config_path, O_CREAT | O_RDWR, 0644)) == -1) { printf("Cannot open file \"%s\" for writing: %s. Exiting.\n", config_path, strerror(errno)); From 4693d5f91ad0efdefd9a2e4e0d5d210787c0cefa Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 19:42:30 +0200 Subject: [PATCH 735/867] default config: put bindings in the right order (left/down/up/right) --- i3.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3.config b/i3.config index afd2fe5d..7899b033 100644 --- a/i3.config +++ b/i3.config @@ -44,9 +44,9 @@ bindsym Mod1+Shift+semicolon move right # alternatively, you can use the cursor keys: bindsym Mod1+Shift+Left move left -bindsym Mod1+Shift+Right move right bindsym Mod1+Shift+Down move down bindsym Mod1+Shift+Up move up +bindsym Mod1+Shift+Right move right # split in horizontal orientation bindsym Mod1+h split h From 868c804cb8b7b5b8408a540168fc269652b45514 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 19:45:22 +0200 Subject: [PATCH 736/867] makefile: install i3-config-wizard --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 9774eff8..e6063d45 100644 --- a/Makefile +++ b/Makefile @@ -77,6 +77,7 @@ install: all $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg install $(MAKE) TOPDIR=$(TOPDIR) -C i3-input install $(MAKE) TOPDIR=$(TOPDIR) -C i3-nagbar install + $(MAKE) TOPDIR=$(TOPDIR) -C i3-config-wizard install dist: distclean [ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION} From 3e3c6f85a122322e7e39af4af67f2004ca6fead2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 19:48:51 +0200 Subject: [PATCH 737/867] add v4 comment to i3.config.keycodes, remove userguide comment from i3-config-wizard --- i3-config-wizard/main.c | 1 - i3.config.keycodes | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index b54d9eb8..d32e6d56 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -276,7 +276,6 @@ static void finish() { fputs("# Should you change your keyboard layout somewhen, delete\n", ks_config); fputs("# this file and re-run i3-config-wizard(1).\n", ks_config); fputs("#\n", ks_config); - fputs("# See http://i3wm.org/docs/userguide.html\n", ks_config); while ((read = getline(&line, &len, kc_config)) != -1) { /* skip the warning block at the beginning of the input file */ diff --git a/i3.config.keycodes b/i3.config.keycodes index c22b0f2c..f0170459 100644 --- a/i3.config.keycodes +++ b/i3.config.keycodes @@ -4,6 +4,10 @@ # WARNING: not get loaded by i3. Please do not change it. # WARNING +# i3 config file (v4) +# +# Please see http://i3wm.org/docs/userguide.html for a complete reference! + set $mod Mod1 # font for window titles. ISO 10646 = Unicode From 7bb9949e23f04b593e08e571f0fb265cbb96aa30 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 20:02:25 +0200 Subject: [PATCH 738/867] default config: start i3-config-wizard --- i3.config | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/i3.config b/i3.config index 7899b033..4b72201f 100644 --- a/i3.config +++ b/i3.config @@ -144,3 +144,14 @@ mode "resize" { } bindsym Mod1+r mode "resize" + +####################################################################### +# automatically start i3-config-wizard to offer the user to create a +# keysym-based config which used his favorite modifier (alt or windows) +# +# i3-config-wizard will not launch if there already is a config file +# in ~/.i3/config. +# +# Please remove the following exec line: +####################################################################### +exec i3-config-wizard From ac4f14e13468465c0aad3ff77602eb70cdd7c14f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 20:14:17 +0200 Subject: [PATCH 739/867] Bugfix: linking error (Thanks smartass) --- i3-config-wizard/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3-config-wizard/Makefile b/i3-config-wizard/Makefile index 44715766..615e8787 100644 --- a/i3-config-wizard/Makefile +++ b/i3-config-wizard/Makefile @@ -14,7 +14,7 @@ HEADERS:=$(wildcard *.h) all: cfgparse.y.o cfgparse.yy.o ${FILES} echo "LINK i3-config-wizard" - $(CC) -o i3-config-wizard ${FILES} $(LDFLAGS) + $(CC) -o i3-config-wizard $^ $(LDFLAGS) cfgparse.yy.o: cfgparse.l cfgparse.y.o ${HEADERS} echo "LEX $<" From f9da0d5ded5121ae629f3cdbb4aa2024a9a4a61d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 20:14:25 +0200 Subject: [PATCH 740/867] Makefile: properly make 'clean' --- i3-config-wizard/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3-config-wizard/Makefile b/i3-config-wizard/Makefile index 615e8787..f0073455 100644 --- a/i3-config-wizard/Makefile +++ b/i3-config-wizard/Makefile @@ -33,7 +33,7 @@ install: all $(INSTALL) -m 0755 i3-config-wizard $(DESTDIR)$(PREFIX)/bin/ clean: - rm -f *.o + rm -f *.o cfgparse.tab.{c,h} cfgparse.output cfgparse.yy.c distclean: clean rm -f i3-config-wizard From 036ecba1d155fdf82d50a17a60727b2b17e458f2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 20:18:38 +0200 Subject: [PATCH 741/867] =?UTF-8?q?makefile:=20don=E2=80=99t=20pick=20up?= =?UTF-8?q?=20autogenerated=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- i3-config-wizard/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i3-config-wizard/Makefile b/i3-config-wizard/Makefile index f0073455..07d6484d 100644 --- a/i3-config-wizard/Makefile +++ b/i3-config-wizard/Makefile @@ -4,7 +4,8 @@ TOPDIR=.. include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files -FILES:=$(patsubst %.c,%.o,$(wildcard *.c)) +AUTOGENERATED:=cfgparse.tab.c cfgparse.yy.c +FILES:=$(patsubst %.c,%.o,$(filter-out $(AUTOGENERATED),$(wildcard *.c))) HEADERS:=$(wildcard *.h) # Depend on the specific file (.c for each .o) and on all headers From 5555c0fd3ba517427294b9f110880b7af3b7c264 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jul 2011 23:17:56 +0200 Subject: [PATCH 742/867] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20raise=20the?= =?UTF-8?q?=20stacked/tabbed=20decoration=20when=20border=20!=3D=20BS=5FNO?= =?UTF-8?q?NE=20and=20children=20=3D=3D=201=20(Thanks=20smartass)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise a black area would cover the top of the child window. --- src/render.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/render.c b/src/render.c index 17c1fde7..a7a9be56 100644 --- a/src/render.c +++ b/src/render.c @@ -345,11 +345,12 @@ void render_con(Con *con, bool render_fullscreen) { render_con(child, false); } - /* Raise the stack con itself. This will put the stack decoration on - * top of every stack window. That way, when a new window is opened in - * the stack, the old window will not obscure part of the decoration - * (it’s unmapped afterwards). */ - x_raise_con(con); + if (children != 1) + /* Raise the stack con itself. This will put the stack decoration on + * top of every stack window. That way, when a new window is opened in + * the stack, the old window will not obscure part of the decoration + * (it’s unmapped afterwards). */ + x_raise_con(con); } } From cc24a96e960acfefcbfd167d7dffb4c869ab60eb Mon Sep 17 00:00:00 2001 From: Claudio Marforio Date: Tue, 12 Jul 2011 12:24:01 +0200 Subject: [PATCH 743/867] patch to allow exec_always in configure file fixed indentation, updated docs --- docs/userguide | 14 +++++++++----- include/data.h | 6 +++++- include/i3.h | 1 + src/cfgparse.l | 1 + src/cfgparse.y | 11 +++++++++++ src/main.c | 10 ++++++++++ 6 files changed, 37 insertions(+), 6 deletions(-) diff --git a/docs/userguide b/docs/userguide index 60d8d63d..eb916a86 100644 --- a/docs/userguide +++ b/docs/userguide @@ -469,18 +469,22 @@ use it, it has to be a UTF-8 encoded arrow, not `->` or something like that. === Automatically starting applications on i3 startup -By using the +exec+ keyword outside a keybinding, you can configure which -commands will be performed by i3 on initial startup (not when restarting i3 -in-place however). These commands will be run in order. +By using the +exec+ keyword outside a keybinding, you can configure +which commands will be performed by i3 on initial startup. +exec+ +commands will not run when restarting i3, if you need a command to run +also when restarting i3 you should use the +exec_always+ +keyword. These commands will be run in order. *Syntax*: ------------- +------------------- exec command ------------- +exec_always command +------------------- *Examples*: -------------------------------- exec i3status | dzen2 -dock +exec_always ~/my_script.sh -------------------------------- [[workspace_screen]] diff --git a/include/data.h b/include/data.h index fed4420c..4dc379c2 100644 --- a/include/data.h +++ b/include/data.h @@ -174,13 +174,17 @@ struct Binding { }; /** - * Holds a command specified by an exec-line in the config (see src/config.c) + * Holds a command specified by either an: + * - exec-line + * - exec_always-line + * in the config (see src/config.c) * */ struct Autostart { /** Command, like in command mode */ char *command; TAILQ_ENTRY(Autostart) autostarts; + TAILQ_ENTRY(Autostart) autostarts_always; }; /** diff --git a/include/i3.h b/include/i3.h index 73b61178..c54e3c33 100644 --- a/include/i3.h +++ b/include/i3.h @@ -26,6 +26,7 @@ extern Display *xlibdpy, *xkbdpy; extern int xkb_current_group; extern TAILQ_HEAD(bindings_head, Binding) *bindings; extern TAILQ_HEAD(autostarts_head, Autostart) autostarts; +extern TAILQ_HEAD(autostarts_always_head, Autostart) autostarts_always; extern TAILQ_HEAD(ws_assignments_head, Workspace_Assignment) ws_assignments; extern TAILQ_HEAD(assignments_head, Assignment) assignments; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; diff --git a/src/cfgparse.l b/src/cfgparse.l index 9194fbe6..2e1d240a 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -147,6 +147,7 @@ stack-limit { return TOKSTACKLIMIT; } cols { /* yylval.number = STACK_LIMIT_COLS; */return TOKSTACKLIMIT; } rows { /* yylval.number = STACK_LIMIT_ROWS; */return TOKSTACKLIMIT; } exec { WS_STRING; return TOKEXEC; } +exec_always { WS_STRING; return TOKEXEC_ALWAYS; } client.background { BEGIN(COLOR_COND); yylval.single_color = &config.client.background; return TOKSINGLECOLOR; } client.focused { BEGIN(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; } client.focused_inactive { BEGIN(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; } diff --git a/src/cfgparse.y b/src/cfgparse.y index 09201c71..a3aea46f 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -518,6 +518,7 @@ void parse_file(const char *f) { %token TOKIPCSOCKET "ipc_socket" %token TOKRESTARTSTATE "restart_state" %token TOKEXEC "exec" +%token TOKEXEC_ALWAYS "exec_always" %token TOKSINGLECOLOR %token TOKCOLOR %token TOKARROW "→" @@ -590,6 +591,7 @@ line: | ipcsocket | restart_state | exec + | exec_always | single_color | color | terminal @@ -1036,6 +1038,15 @@ exec: } ; +exec_always: + TOKEXEC_ALWAYS STR + { + struct Autostart *new = smalloc(sizeof(struct Autostart)); + new->command = $2; + TAILQ_INSERT_TAIL(&autostarts_always, new, autostarts_always); + } + ; + terminal: TOKTERMINAL STR { diff --git a/src/main.c b/src/main.c index 7910c8b1..3ef394a2 100644 --- a/src/main.c +++ b/src/main.c @@ -32,6 +32,9 @@ struct bindings_head *bindings; /* The list of exec-lines */ struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts); +/* The list of exec_always lines */ +struct autostarts_always_head autostarts_always = TAILQ_HEAD_INITIALIZER(autostarts_always); + /* The list of assignments */ struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments); @@ -465,5 +468,12 @@ int main(int argc, char *argv[]) { } } + /* Autostarting exec_always-lines */ + struct Autostart *exec_always; + TAILQ_FOREACH(exec_always, &autostarts_always, autostarts_always) { + LOG("auto-starting (always!) %s\n", exec_always->command); + start_application(exec_always->command); + } + ev_loop(main_loop, 0); } From f1190bef4478eb4bb0a74b7dc41c1a984fff84e3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 12 Jul 2011 21:29:30 +0200 Subject: [PATCH 744/867] =?UTF-8?q?migrate-config:=20t=20=E2=86=92=20float?= =?UTF-8?q?ing=20toggle=20(Thanks=20eeemsi)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- i3-migrate-config-to-v4.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3-migrate-config-to-v4.pl b/i3-migrate-config-to-v4.pl index f4f970df..0d1d0183 100755 --- a/i3-migrate-config-to-v4.pl +++ b/i3-migrate-config-to-v4.pl @@ -207,7 +207,7 @@ sub convert_command { qr/^T/ => 'layout tabbed', qr/^f($|[^go])/ => 'fullscreen', qr/^fg/ => 'fullscreen global', - qr/^t/ => 'focus mode_toggle', + qr/^t/ => 'floating toggle', qr/^h/ => 'focus left', qr/^j($|[^u])/ => 'focus down', qr/^k/ => 'focus up', From 93f906308dd2338a593bbf3ee5411aa418dcfff0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 13 Jul 2011 13:22:15 +0200 Subject: [PATCH 745/867] makefiles: respect and use the CPPFLAGS variable (Thanks Kacper) See also: http://stackoverflow.com/questions/2754966/cflags-vs-cppflags --- Makefile | 10 +++++----- common.mk | 10 +++++----- i3-config-wizard/Makefile | 6 +++--- i3-input/Makefile | 2 +- i3-msg/Makefile | 2 +- i3-nagbar/Makefile | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index e6063d45..d628eaf7 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ endif # Depend on the specific file (.c for each .o) and on all headers src/%.o: src/%.c ${HEADERS} echo "CC $<" - $(CC) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -c -o $@ $< + $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -c -o $@ $< all: src/cfgparse.y.o src/cfgparse.yy.o src/cmdparse.y.o src/cmdparse.yy.o ${FILES} echo "LINK i3" @@ -42,23 +42,23 @@ loglevels.h: src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS} echo "LEX $<" flex -i -o$(@:.o=.c) $< - $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) + $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) src/cmdparse.yy.o: src/cmdparse.l src/cmdparse.y.o ${HEADERS} echo "LEX $<" flex -Pcmdyy -i -o$(@:.o=.c) $< - $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) + $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) src/cfgparse.y.o: src/cfgparse.y ${HEADERS} echo "YACC $<" bison --debug --verbose -b $(basename $< .y) -d $< - $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) + $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) src/cmdparse.y.o: src/cmdparse.y ${HEADERS} echo "YACC $<" bison -p cmdyy --debug --verbose -b $(basename $< .y) -d $< - $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) + $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) install: all diff --git a/common.mk b/common.mk index ff12bdb0..c7131f22 100644 --- a/common.mk +++ b/common.mk @@ -31,7 +31,7 @@ CFLAGS += -Wunused-value CFLAGS += -Iinclude CFLAGS += $(call cflags_for_lib, xcb-keysyms) ifeq ($(shell pkg-config --exists xcb-util || echo 1),1) -CFLAGS += -DXCB_COMPAT +CPPFLAGS += -DXCB_COMPAT CFLAGS += $(call cflags_for_lib, xcb-atom) CFLAGS += $(call cflags_for_lib, xcb-aux) else @@ -45,9 +45,9 @@ CFLAGS += $(call cflags_for_lib, xcursor) CFLAGS += $(call cflags_for_lib, x11) CFLAGS += $(call cflags_for_lib, yajl) CFLAGS += $(call cflags_for_lib, libev) -CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" -CFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" -CFLAGS += -DTERM_EMU=\"$(TERM_EMU)\" +CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" +CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" +CPPFLAGS += -DTERM_EMU=\"$(TERM_EMU)\" LDFLAGS += -lm LDFLAGS += $(call ldflags_for_lib, xcb-event, xcb-event) @@ -88,7 +88,7 @@ endif CFLAGS += -idirafter yajl-fallback ifneq (,$(filter Linux GNU GNU/%, $(UNAME))) -CFLAGS += -D_GNU_SOURCE +CPPFLAGS += -D_GNU_SOURCE endif ifeq ($(DEBUG),1) diff --git a/i3-config-wizard/Makefile b/i3-config-wizard/Makefile index 07d6484d..51b030f2 100644 --- a/i3-config-wizard/Makefile +++ b/i3-config-wizard/Makefile @@ -11,7 +11,7 @@ HEADERS:=$(wildcard *.h) # Depend on the specific file (.c for each .o) and on all headers %.o: %.c ${HEADERS} echo "CC $<" - $(CC) $(CFLAGS) -c -o $@ $< + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< all: cfgparse.y.o cfgparse.yy.o ${FILES} echo "LINK i3-config-wizard" @@ -20,12 +20,12 @@ all: cfgparse.y.o cfgparse.yy.o ${FILES} cfgparse.yy.o: cfgparse.l cfgparse.y.o ${HEADERS} echo "LEX $<" flex -i -o$(@:.o=.c) $< - $(CC) $(CFLAGS) -c -o $@ $(@:.o=.c) + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(@:.o=.c) cfgparse.y.o: cfgparse.y ${HEADERS} echo "YACC $<" bison --debug --verbose -b $(basename $< .y) -d $< - $(CC) $(CFLAGS) -c -o $@ $(<:.y=.tab.c) + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(<:.y=.tab.c) install: all diff --git a/i3-input/Makefile b/i3-input/Makefile index 74f3f8da..12658dcd 100644 --- a/i3-input/Makefile +++ b/i3-input/Makefile @@ -10,7 +10,7 @@ HEADERS=$(wildcard *.h) # Depend on the specific file (.c for each .o) and on all headers %.o: %.c ${HEADERS} echo "CC $<" - $(CC) $(CFLAGS) -c -o $@ $< + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< all: ${FILES} echo "LINK i3-input" diff --git a/i3-msg/Makefile b/i3-msg/Makefile index d75d807c..29de9263 100644 --- a/i3-msg/Makefile +++ b/i3-msg/Makefile @@ -12,7 +12,7 @@ HEADERS=$(wildcard *.h) # Depend on the specific file (.c for each .o) and on all headers %.o: %.c ${HEADERS} echo "CC $<" - $(CC) $(CFLAGS) -c -o $@ $< + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< all: ${FILES} echo "LINK i3-msg" diff --git a/i3-nagbar/Makefile b/i3-nagbar/Makefile index 8d9283bc..76b1ca4c 100644 --- a/i3-nagbar/Makefile +++ b/i3-nagbar/Makefile @@ -10,7 +10,7 @@ HEADERS=$(wildcard *.h) # Depend on the specific file (.c for each .o) and on all headers %.o: %.c ${HEADERS} echo "CC $<" - $(CC) $(CFLAGS) -c -o $@ $< + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< all: ${FILES} echo "LINK i3-nagbar" From 3749ed2fbe3115fb2409567a338763dd1b53d0b7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 13 Jul 2011 13:28:31 +0200 Subject: [PATCH 746/867] Makefile: respect and use LIBS (Thanks Kacper) --- Makefile | 2 +- common.mk | 34 +++++++++++++++++----------------- i3-config-wizard/Makefile | 2 +- i3-input/Makefile | 2 +- i3-msg/Makefile | 2 +- i3-nagbar/Makefile | 2 +- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index d628eaf7..fccb0690 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ src/%.o: src/%.c ${HEADERS} all: src/cfgparse.y.o src/cfgparse.yy.o src/cmdparse.y.o src/cmdparse.yy.o ${FILES} echo "LINK i3" - $(CC) -o i3 $^ $(LDFLAGS) + $(CC) $(LDFLAGS) -o i3 $^ $(LIBS) loglevels.h: echo "LOGLEVELS" diff --git a/common.mk b/common.mk index c7131f22..76813264 100644 --- a/common.mk +++ b/common.mk @@ -49,23 +49,23 @@ CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" CPPFLAGS += -DTERM_EMU=\"$(TERM_EMU)\" -LDFLAGS += -lm -LDFLAGS += $(call ldflags_for_lib, xcb-event, xcb-event) -LDFLAGS += $(call ldflags_for_lib, xcb-keysyms, xcb-keysyms) +LIBS += -lm +LIBS += $(call ldflags_for_lib, xcb-event, xcb-event) +LIBS += $(call ldflags_for_lib, xcb-keysyms, xcb-keysyms) ifeq ($(shell pkg-config --exists xcb-util || echo 1),1) -LDFLAGS += $(call ldflags_for_lib, xcb-atom, xcb-atom) -LDFLAGS += $(call ldflags_for_lib, xcb-aux, xcb-aux) +LIBS += $(call ldflags_for_lib, xcb-atom, xcb-atom) +LIBS += $(call ldflags_for_lib, xcb-aux, xcb-aux) else -LDFLAGS += $(call ldflags_for_lib, xcb-util) +LIBS += $(call ldflags_for_lib, xcb-util) endif -LDFLAGS += $(call ldflags_for_lib, xcb-icccm, xcb-icccm) -LDFLAGS += $(call ldflags_for_lib, xcb-xinerama, xcb-xinerama) -LDFLAGS += $(call ldflags_for_lib, xcb-randr, xcb-randr) -LDFLAGS += $(call ldflags_for_lib, xcb, xcb) -LDFLAGS += $(call ldflags_for_lib, xcursor, Xcursor) -LDFLAGS += $(call ldflags_for_lib, x11, X11) -LDFLAGS += $(call ldflags_for_lib, yajl, yajl) -LDFLAGS += $(call ldflags_for_lib, libev, ev) +LIBS += $(call ldflags_for_lib, xcb-icccm, xcb-icccm) +LIBS += $(call ldflags_for_lib, xcb-xinerama, xcb-xinerama) +LIBS += $(call ldflags_for_lib, xcb-randr, xcb-randr) +LIBS += $(call ldflags_for_lib, xcb, xcb) +LIBS += $(call ldflags_for_lib, xcursor, Xcursor) +LIBS += $(call ldflags_for_lib, x11, X11) +LIBS += $(call ldflags_for_lib, yajl, yajl) +LIBS += $(call ldflags_for_lib, libev, ev) ifeq ($(UNAME),NetBSD) # We need -idirafter instead of -I to prefer the system’s iconv over GNU libiconv @@ -75,12 +75,12 @@ endif ifeq ($(UNAME),OpenBSD) CFLAGS += -I${X11BASE}/include -LDFLAGS += -liconv +LIBS += -liconv LDFLAGS += -L${X11BASE}/lib endif ifeq ($(UNAME),FreeBSD) -LDFLAGS += -liconv +LIBS += -liconv endif # Fallback for libyajl 1 which did not include yajl_version.h. We need @@ -102,7 +102,7 @@ endif ifeq ($(COVERAGE),1) CFLAGS += -fprofile-arcs -ftest-coverage -LDFLAGS += -lgcov +LIBS += -lgcov endif # Don’t print command lines which are run diff --git a/i3-config-wizard/Makefile b/i3-config-wizard/Makefile index 51b030f2..688df11a 100644 --- a/i3-config-wizard/Makefile +++ b/i3-config-wizard/Makefile @@ -15,7 +15,7 @@ HEADERS:=$(wildcard *.h) all: cfgparse.y.o cfgparse.yy.o ${FILES} echo "LINK i3-config-wizard" - $(CC) -o i3-config-wizard $^ $(LDFLAGS) + $(CC) $(LDFLAGS) -o i3-config-wizard $^ $(LIBS) cfgparse.yy.o: cfgparse.l cfgparse.y.o ${HEADERS} echo "LEX $<" diff --git a/i3-input/Makefile b/i3-input/Makefile index 12658dcd..498cfb50 100644 --- a/i3-input/Makefile +++ b/i3-input/Makefile @@ -14,7 +14,7 @@ HEADERS=$(wildcard *.h) all: ${FILES} echo "LINK i3-input" - $(CC) -o i3-input ${FILES} $(LDFLAGS) + $(CC) $(LDFLAGS) -o i3-input ${FILES} $(LIBS) install: all echo "INSTALL" diff --git a/i3-msg/Makefile b/i3-msg/Makefile index 29de9263..7ea19e60 100644 --- a/i3-msg/Makefile +++ b/i3-msg/Makefile @@ -16,7 +16,7 @@ HEADERS=$(wildcard *.h) all: ${FILES} echo "LINK i3-msg" - $(CC) -o i3-msg ${FILES} $(LDFLAGS) + $(CC) $(LDFLAGS) -o i3-msg ${FILES} $(LIBS) install: all echo "INSTALL" diff --git a/i3-nagbar/Makefile b/i3-nagbar/Makefile index 76b1ca4c..c9eec446 100644 --- a/i3-nagbar/Makefile +++ b/i3-nagbar/Makefile @@ -14,7 +14,7 @@ HEADERS=$(wildcard *.h) all: ${FILES} echo "LINK i3-nagbar" - $(CC) -o i3-nagbar ${FILES} $(LDFLAGS) + $(CC) $(LDFLAGS) -o i3-nagbar ${FILES} $(LIBS) install: all echo "INSTALL" From 806ad77399abe0e17c230789348da21da94e8876 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 13 Jul 2011 13:29:39 +0200 Subject: [PATCH 747/867] Makefile: use $(MAKE) instead of make --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index fccb0690..10af73b8 100644 --- a/Makefile +++ b/Makefile @@ -93,7 +93,7 @@ dist: distclean find i3-input -maxdepth 1 -type f \( -name "*.c" -or -name "*.h" -or -name "Makefile" \) -exec cp '{}' i3-${VERSION}/i3-input \; sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION:=$(shell echo '${GIT_VERSION}' | sed 's/\\/\\\\/g')/g;s/^VERSION:=\(.*\)/VERSION:=${VERSION}/g' common.mk > i3-${VERSION}/common.mk # Pre-generate a manpage to allow distributors to skip this step and save some dependencies - make -C man + $(MAKE) -C man cp man/*.1 i3-${VERSION}/man/ tar cfj i3-${VERSION}.tar.bz2 i3-${VERSION} rm -rf i3-${VERSION} From ba7e82fc944e53b4e51a1dc901ad2b04b86fec54 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 13 Jul 2011 17:53:07 +0200 Subject: [PATCH 748/867] i3-msg: re-indent main.c --- i3-msg/main.c | 298 +++++++++++++++++++++++++------------------------- 1 file changed, 149 insertions(+), 149 deletions(-) diff --git a/i3-msg/main.c b/i3-msg/main.c index ee4de078..06beccad 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * @@ -43,33 +43,33 @@ static char *socket_path; * */ static char *socket_path_from_x11() { - xcb_connection_t *conn; - int screen; - if ((conn = xcb_connect(NULL, &screen)) == NULL || - xcb_connection_has_error(conn)) - return NULL; - xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen); - xcb_window_t root = root_screen->root; + xcb_connection_t *conn; + int screen; + if ((conn = xcb_connect(NULL, &screen)) == NULL || + xcb_connection_has_error(conn)) + return NULL; + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen); + xcb_window_t root = root_screen->root; - xcb_intern_atom_cookie_t atom_cookie; - xcb_intern_atom_reply_t *atom_reply; + xcb_intern_atom_cookie_t atom_cookie; + xcb_intern_atom_reply_t *atom_reply; - atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH"); - atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL); - if (atom_reply == NULL) - return NULL; + atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH"); + atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL); + if (atom_reply == NULL) + return NULL; - xcb_get_property_cookie_t prop_cookie; - xcb_get_property_reply_t *prop_reply; - prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, - XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX); - prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); - if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0) - return NULL; - if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply), - (char*)xcb_get_property_value(prop_reply)) == -1) - return NULL; - return socket_path; + xcb_get_property_cookie_t prop_cookie; + xcb_get_property_reply_t *prop_reply; + prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX); + prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0) + return NULL; + if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply), + (char*)xcb_get_property_value(prop_reply)) == -1) + return NULL; + return socket_path; } /* @@ -79,157 +79,157 @@ static char *socket_path_from_x11() { */ static void ipc_send_message(int sockfd, uint32_t message_size, uint32_t message_type, uint8_t *payload) { - int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size; - char msg[buffer_size]; - char *walk = msg; + int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size; + char msg[buffer_size]; + char *walk = msg; - strcpy(walk, I3_IPC_MAGIC); - walk += strlen(I3_IPC_MAGIC); - memcpy(walk, &message_size, sizeof(uint32_t)); - walk += sizeof(uint32_t); - memcpy(walk, &message_type, sizeof(uint32_t)); - walk += sizeof(uint32_t); - memcpy(walk, payload, message_size); + strcpy(walk, I3_IPC_MAGIC); + walk += strlen(I3_IPC_MAGIC); + memcpy(walk, &message_size, sizeof(uint32_t)); + walk += sizeof(uint32_t); + memcpy(walk, &message_type, sizeof(uint32_t)); + walk += sizeof(uint32_t); + memcpy(walk, payload, message_size); - int sent_bytes = 0; - int bytes_to_go = buffer_size; - while (sent_bytes < bytes_to_go) { - int n = write(sockfd, msg + sent_bytes, bytes_to_go); - if (n == -1) - err(EXIT_FAILURE, "write() failed"); + int sent_bytes = 0; + int bytes_to_go = buffer_size; + while (sent_bytes < bytes_to_go) { + int n = write(sockfd, msg + sent_bytes, bytes_to_go); + if (n == -1) + err(EXIT_FAILURE, "write() failed"); - sent_bytes += n; - bytes_to_go -= n; - } + sent_bytes += n; + bytes_to_go -= n; + } } static void ipc_recv_message(int sockfd, uint32_t message_type, uint32_t *reply_length, uint8_t **reply) { - /* Read the message header first */ - uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t); - char msg[to_read]; - char *walk = msg; + /* Read the message header first */ + uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t); + char msg[to_read]; + char *walk = msg; - uint32_t read_bytes = 0; - while (read_bytes < to_read) { - int n = read(sockfd, msg + read_bytes, to_read); - if (n == -1) - err(EXIT_FAILURE, "read() failed"); - if (n == 0) - errx(EXIT_FAILURE, "received EOF instead of reply"); + uint32_t read_bytes = 0; + while (read_bytes < to_read) { + int n = read(sockfd, msg + read_bytes, to_read); + if (n == -1) + err(EXIT_FAILURE, "read() failed"); + if (n == 0) + errx(EXIT_FAILURE, "received EOF instead of reply"); - read_bytes += n; - to_read -= n; - } + read_bytes += n; + to_read -= n; + } - if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) - errx(EXIT_FAILURE, "invalid magic in reply"); + if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) + errx(EXIT_FAILURE, "invalid magic in reply"); - walk += strlen(I3_IPC_MAGIC); - *reply_length = *((uint32_t*)walk); - walk += sizeof(uint32_t); - if (*((uint32_t*)walk) != message_type) - errx(EXIT_FAILURE, "unexpected reply type (got %d, expected %d)", *((uint32_t*)walk), message_type); - walk += sizeof(uint32_t); + walk += strlen(I3_IPC_MAGIC); + *reply_length = *((uint32_t*)walk); + walk += sizeof(uint32_t); + if (*((uint32_t*)walk) != message_type) + errx(EXIT_FAILURE, "unexpected reply type (got %d, expected %d)", *((uint32_t*)walk), message_type); + walk += sizeof(uint32_t); - *reply = malloc(*reply_length); - if ((*reply) == NULL) - err(EXIT_FAILURE, "malloc() failed"); + *reply = malloc(*reply_length); + if ((*reply) == NULL) + err(EXIT_FAILURE, "malloc() failed"); - to_read = *reply_length; - read_bytes = 0; - while (read_bytes < to_read) { - int n = read(sockfd, *reply + read_bytes, to_read); - if (n == -1) - err(EXIT_FAILURE, "read() failed"); + to_read = *reply_length; + read_bytes = 0; + while (read_bytes < to_read) { + int n = read(sockfd, *reply + read_bytes, to_read); + if (n == -1) + err(EXIT_FAILURE, "read() failed"); - read_bytes += n; - to_read -= n; - } + read_bytes += n; + to_read -= n; + } } int main(int argc, char *argv[]) { - socket_path = getenv("I3SOCK"); - int o, option_index = 0; - int message_type = I3_IPC_MESSAGE_TYPE_COMMAND; - char *payload = ""; - bool quiet = false; + socket_path = getenv("I3SOCK"); + int o, option_index = 0; + int message_type = I3_IPC_MESSAGE_TYPE_COMMAND; + char *payload = ""; + bool quiet = false; - static struct option long_options[] = { - {"socket", required_argument, 0, 's'}, - {"type", required_argument, 0, 't'}, - {"version", no_argument, 0, 'v'}, - {"quiet", no_argument, 0, 'q'}, - {"help", no_argument, 0, 'h'}, - {0, 0, 0, 0} - }; + static struct option long_options[] = { + {"socket", required_argument, 0, 's'}, + {"type", required_argument, 0, 't'}, + {"version", no_argument, 0, 'v'}, + {"quiet", no_argument, 0, 'q'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; - char *options_string = "s:t:vhq"; + char *options_string = "s:t:vhq"; - while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { - if (o == 's') { - if (socket_path != NULL) - free(socket_path); - socket_path = strdup(optarg); - } else if (o == 't') { - if (strcasecmp(optarg, "command") == 0) - message_type = I3_IPC_MESSAGE_TYPE_COMMAND; - else if (strcasecmp(optarg, "get_workspaces") == 0) - message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES; - else if (strcasecmp(optarg, "get_outputs") == 0) - message_type = I3_IPC_MESSAGE_TYPE_GET_OUTPUTS; - else if (strcasecmp(optarg, "get_tree") == 0) - message_type = I3_IPC_MESSAGE_TYPE_GET_TREE; - else { - printf("Unknown message type\n"); - printf("Known types: command, get_workspaces, get_outputs, get_tree\n"); - exit(EXIT_FAILURE); - } - } else if (o == 'q') { - quiet = true; - } else if (o == 'v') { - printf("i3-msg " I3_VERSION "\n"); - return 0; - } else if (o == 'h') { - printf("i3-msg " I3_VERSION "\n"); - printf("i3-msg [-s ] [-t ] \n"); - return 0; - } + while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { + if (o == 's') { + if (socket_path != NULL) + free(socket_path); + socket_path = strdup(optarg); + } else if (o == 't') { + if (strcasecmp(optarg, "command") == 0) + message_type = I3_IPC_MESSAGE_TYPE_COMMAND; + else if (strcasecmp(optarg, "get_workspaces") == 0) + message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES; + else if (strcasecmp(optarg, "get_outputs") == 0) + message_type = I3_IPC_MESSAGE_TYPE_GET_OUTPUTS; + else if (strcasecmp(optarg, "get_tree") == 0) + message_type = I3_IPC_MESSAGE_TYPE_GET_TREE; + else { + printf("Unknown message type\n"); + printf("Known types: command, get_workspaces, get_outputs, get_tree\n"); + exit(EXIT_FAILURE); + } + } else if (o == 'q') { + quiet = true; + } else if (o == 'v') { + printf("i3-msg " I3_VERSION "\n"); + return 0; + } else if (o == 'h') { + printf("i3-msg " I3_VERSION "\n"); + printf("i3-msg [-s ] [-t ] \n"); + return 0; } + } - if (socket_path == NULL) - socket_path = socket_path_from_x11(); + if (socket_path == NULL) + socket_path = socket_path_from_x11(); - /* Fall back to the default socket path */ - if (socket_path == NULL) - socket_path = strdup("/tmp/i3-ipc.sock"); + /* Fall back to the default socket path */ + if (socket_path == NULL) + socket_path = strdup("/tmp/i3-ipc.sock"); - if (optind < argc) - payload = argv[optind]; + if (optind < argc) + payload = argv[optind]; - int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); - if (sockfd == -1) - err(EXIT_FAILURE, "Could not create socket"); + int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (sockfd == -1) + err(EXIT_FAILURE, "Could not create socket"); - struct sockaddr_un addr; - memset(&addr, 0, sizeof(struct sockaddr_un)); - addr.sun_family = AF_LOCAL; - strcpy(addr.sun_path, socket_path); - if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) - err(EXIT_FAILURE, "Could not connect to i3"); + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_LOCAL; + strcpy(addr.sun_path, socket_path); + if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) + err(EXIT_FAILURE, "Could not connect to i3"); - ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload); - - if (quiet) - return 0; - - uint32_t reply_length; - uint8_t *reply; - ipc_recv_message(sockfd, message_type, &reply_length, &reply); - printf("%.*s", reply_length, reply); - free(reply); - - close(sockfd); + ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload); + if (quiet) return 0; + + uint32_t reply_length; + uint8_t *reply; + ipc_recv_message(sockfd, message_type, &reply_length, &reply); + printf("%.*s", reply_length, reply); + free(reply); + + close(sockfd); + + return 0; } From 5c276be3d55919e992584ff2034c82341209d73f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 13 Jul 2011 18:01:24 +0200 Subject: [PATCH 749/867] i3-msg: concatenate all arguments. now you can use i3-msg mark foo --- i3-msg/main.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/i3-msg/main.c b/i3-msg/main.c index 06beccad..ac08419f 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -152,7 +152,7 @@ int main(int argc, char *argv[]) { socket_path = getenv("I3SOCK"); int o, option_index = 0; int message_type = I3_IPC_MESSAGE_TYPE_COMMAND; - char *payload = ""; + char *payload = NULL; bool quiet = false; static struct option long_options[] = { @@ -204,8 +204,25 @@ int main(int argc, char *argv[]) { if (socket_path == NULL) socket_path = strdup("/tmp/i3-ipc.sock"); - if (optind < argc) - payload = argv[optind]; + /* Use all arguments, separated by whitespace, as payload. + * This way, you don’t have to do i3-msg 'mark foo', you can use + * i3-msg mark foo */ + while (optind < argc) { + if (!payload) { + if (!(payload = strdup(argv[optind]))) + err(EXIT_FAILURE, "strdup(argv[optind])"); + } else { + char *both; + if (asprintf(&both, "%s %s", payload, argv[optind]) == -1) + err(EXIT_FAILURE, "asprintf"); + free(payload); + payload = both; + } + optind++; + } + + if (!payload) + payload = ""; int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); if (sockfd == -1) From 32af9d41064466b4346e29d19705be4641d5473d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 13 Jul 2011 18:36:21 +0200 Subject: [PATCH 750/867] i3-nagbar: implement -m, set default prompt to "Pleaso do not run this program" It gets run by i3 automatically. --- i3-nagbar/main.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 73d6f63b..757a6cac 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -44,7 +44,7 @@ static xcb_rectangle_t rect = { 0, 0, 600, 20 }; static char *glyphs_ucs[512]; static int input_position; static int font_height; -static char *prompt = "You have an error in your i3 config file!"; +static char *prompt = "Please do not run this program."; static button_t *buttons; static int buttoncnt; xcb_window_t root; @@ -224,10 +224,11 @@ int main(int argc, char *argv[]) { {"font", required_argument, 0, 'f'}, {"button", required_argument, 0, 'b'}, {"help", no_argument, 0, 'h'}, + {"message", no_argument, 0, 'm'}, {0, 0, 0, 0} }; - char *options_string = "b:f:vh"; + char *options_string = "b:f:m:vh"; while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { switch (o) { @@ -238,6 +239,9 @@ int main(int argc, char *argv[]) { FREE(pattern); pattern = strdup(optarg); break; + case 'm': + prompt = strdup(optarg); + break; case 'h': printf("i3-nagbar " I3_VERSION); printf("i3-nagbar [-s ] [-p ] [-l ] [-P ] [-f ] [-v]\n"); From 8c1a242f5f253f513b0b528ac0493307457b61c7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 13 Jul 2011 18:47:08 +0200 Subject: [PATCH 751/867] reload the config after editing it through i3-nagbar --- src/cfgparse.y | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index a3aea46f..dac63b95 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -258,7 +258,7 @@ static void start_configerror_nagbar(const char *config_path) { if (configerror_pid == 0) { char *editaction, *pageraction; - if (asprintf(&editaction, TERM_EMU " -e sh -c \"${EDITOR:-vi} \"%s\"\"", config_path) == -1) + if (asprintf(&editaction, TERM_EMU " -e sh -c \"${EDITOR:-vi} \"%s\" && i3-msg reload\"", config_path) == -1) exit(1); if (asprintf(&pageraction, TERM_EMU " -e sh -c \"${PAGER:-less} \"%s\"\"", errorfilename) == -1) exit(1); From 6b4c65e04d236132b4276005c454b1f7ec12a3fd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 14 Jul 2011 13:26:59 +0200 Subject: [PATCH 752/867] i3-nagbar: Fix -h / --help output (Thanks ktosiek) --- i3-nagbar/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 757a6cac..fc2cc858 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -243,8 +243,8 @@ int main(int argc, char *argv[]) { prompt = strdup(optarg); break; case 'h': - printf("i3-nagbar " I3_VERSION); - printf("i3-nagbar [-s ] [-p ] [-l ] [-P ] [-f ] [-v]\n"); + printf("i3-nagbar " I3_VERSION "\n"); + printf("i3-nagbar [-m ] [-b