From c145f7e5297ef06aaf84689762a736d5bc8cbb83 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 15:25:51 +0100 Subject: [PATCH] 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 | 27 +-- src/click.c | 69 +++--- src/con.c | 248 +++++++++++++++++++++ src/config.c | 55 +++-- src/floating.c | 125 +++++++---- src/handlers.c | 95 ++++----- src/ipc.c | 108 ++++++++-- src/load_layout.c | 160 ++++++++++++++ src/log.c | 2 +- src/manage.c | 142 +++++++++---- src/nc.c | 414 ++++++++++++++++++++++++++++++++++++ src/randr.c | 47 ++-- src/render.c | 137 ++++++++++++ src/table.c | 406 ----------------------------------- src/tree.c | 335 +++++++++++++++++++++++++++++ src/util.c | 90 +++++--- src/workspace.c | 96 ++++----- src/x.c | 299 ++++++++++++++++++++++++++ src/xcb.c | 16 +- src/xinerama.c | 150 +++++++------ testcases/Makefile | 2 +- testcases/t/16-nestedcons.t | 84 ++++++++ 48 files changed, 2598 insertions(+), 1285 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); - - /* 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; - - DLOG("(checked mode_switch, state %d)\n", state_filtered); - - /* 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; - } + 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); + + if (xkb_current_group == XkbGroup2Index) + state_filtered |= BIND_MODE_SWITCH; + + DLOG("(checked mode_switch, state %d)\n", state_filtered); + + /* 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; } + } - 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 "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" + +#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_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"); - - 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); +void restore_geometry() { + 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); + } /* 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 "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" - -static iconv_t conversion_descriptor = 0; +#include + +#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); @@ -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; - - /* If we are still there, we could not find the requested workspace. */ - int last_ws = TAILQ_LAST(workspaces, workspaces_head)->num; - - DLOG("We need to initialize that one, last ws = %d\n", last_ws); - - for (int c = last_ws; c < number; c++) { - DLOG("Creating new ws\n"); +Con *workspace_get(const char *num) { + 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; - 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); + workspace = current; + break; + } + } - 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); - - 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++; - } + 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); - 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" ); -- 2.39.5