diff --git a/Makefile b/Makefile index a42099d4..850693e3 100644 --- a/Makefile +++ b/Makefile @@ -1,72 +1,6 @@ -UNAME=$(shell uname) -DEBUG=1 -INSTALL=install -GIT_VERSION=$(shell git describe --tags --always) -VERSION=$(shell git describe --tags --abbrev=0) +TOPDIR=$(shell pwd) -CFLAGS += -std=c99 -CFLAGS += -pipe -CFLAGS += -Wall -CFLAGS += -Wunused -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 -LDFLAGS += -lxcb-keysyms -LDFLAGS += -lxcb-atom -LDFLAGS += -lxcb-aux -LDFLAGS += -lxcb-icccm -LDFLAGS += -lxcb-xinerama -LDFLAGS += -lX11 -LDFLAGS += -lev -LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib - -ifeq ($(UNAME),NetBSD) -# We need -idirafter instead of -I to prefer the system’s iconv over GNU libiconv -CFLAGS += -idirafter /usr/pkg/include -LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib -endif - -ifeq ($(UNAME),FreeBSD) -LDFLAGS += -liconv -endif - -ifeq ($(UNAME),Linux) -CFLAGS += -D_GNU_SOURCE -endif - -ifeq ($(DEBUG),1) -# Extended debugging flags, macros shall be available in gcc -CFLAGS += -gdwarf-2 -CFLAGS += -g3 -else -CFLAGS += -O2 -endif - -# Don’t print command lines which are run -.SILENT: - -# Always remake the following targets -.PHONY: install clean dist distclean +include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files FILES=$(patsubst %.c,%.o,$(wildcard src/*.c)) @@ -80,6 +14,9 @@ src/%.o: src/%.c ${HEADERS} all: ${FILES} echo "LINK i3" $(CC) -o i3 ${FILES} $(LDFLAGS) + echo "" + echo "SUBDIR i3-msg" + $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg install: all echo "INSTALL" @@ -89,6 +26,7 @@ install: all $(INSTALL) -m 0755 i3 $(DESTDIR)/usr/bin/ test -e $(DESTDIR)/etc/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)/etc/i3/config $(INSTALL) -m 0644 i3.desktop $(DESTDIR)/usr/share/xsessions/ + $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg install dist: clean [ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION} @@ -108,6 +46,7 @@ clean: rm -f src/*.o $(MAKE) -C docs clean $(MAKE) -C man clean + $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg clean distclean: clean rm -f i3 diff --git a/common.mk b/common.mk new file mode 100644 index 00000000..7944196c --- /dev/null +++ b/common.mk @@ -0,0 +1,70 @@ +UNAME=$(shell uname) +DEBUG=1 +INSTALL=install +GIT_VERSION=$(shell git describe --tags --always) +VERSION=$(shell git describe --tags --abbrev=0) + +CFLAGS += -std=c99 +CFLAGS += -pipe +CFLAGS += -Wall +CFLAGS += -Wunused +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 +LDFLAGS += -lxcb-keysyms +LDFLAGS += -lxcb-atom +LDFLAGS += -lxcb-aux +LDFLAGS += -lxcb-icccm +LDFLAGS += -lxcb-xinerama +LDFLAGS += -lX11 +LDFLAGS += -lev +LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib + +ifeq ($(UNAME),NetBSD) +# We need -idirafter instead of -I to prefer the system’s iconv over GNU libiconv +CFLAGS += -idirafter /usr/pkg/include +LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib +endif + +ifeq ($(UNAME),FreeBSD) +LDFLAGS += -liconv +endif + +ifeq ($(UNAME),Linux) +CFLAGS += -D_GNU_SOURCE +endif + +ifeq ($(DEBUG),1) +# Extended debugging flags, macros shall be available in gcc +CFLAGS += -gdwarf-2 +CFLAGS += -g3 +else +CFLAGS += -O2 +endif + +# Don’t print command lines which are run +.SILENT: + +# Always remake the following targets +.PHONY: install clean dist distclean + diff --git a/debian/changelog b/debian/changelog index 17ece2ba..8bb2c846 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +i3-wm (3.c-0) unstable; urgency=low + + * Implement a reload command + * Implement IPC via unix sockets + * Optimization: Render stack windows on pixmaps to reduce flickering + * Optimization: Directly position new windows to their final position + * Bugfix: Repeatedly try to find screens if none are available + * Bugfix: Correctly redecorate clients when changing focus + * Bugfix: Don’t crash when clients reconfigure themselves + + -- Michael Stapelberg Sun, 02 Aug 2009 20:05:58 +0200 + i3-wm (3.b-1) unstable; urgency=low * Bugfix: Correctly handle col-/rowspanned containers when setting focus. diff --git a/docs/userguide b/docs/userguide index 36c30d62..05593cd5 100644 --- a/docs/userguide +++ b/docs/userguide @@ -259,13 +259,14 @@ i3 will get the title as soon as the application maps the window (mapping means actually displaying it on the screen), you’d need to have to match on Firefox in this case. -You can use the special workspace +~+ to specify that matching clients should -be put into floating mode. +You can prefix or suffix workspaces with a +~+ to specify that matching clients +should be put into floating mode. If you specify only a +~+, the client will +not be put onto any workspace, but will be set floating on the current one. *Syntax*: ----------------------------------------------------- -assign ["]window class[/window title]["] [→] workspace ----------------------------------------------------- +------------------------------------------------------------ +assign ["]window class[/window title]["] [→] [~ | workspace] +------------------------------------------------------------ *Examples*: ---------------------- @@ -273,7 +274,8 @@ assign urxvt 2 assign urxvt → 2 assign "urxvt" → 2 assign "urxvt/VIM" → 3 -assign "gecko" → ~ +assign "gecko" → ~4 +assign "xv/MPlayer" → ~ ---------------------- === Automatically starting applications on startup diff --git a/i3-msg/Makefile b/i3-msg/Makefile new file mode 100644 index 00000000..a5e15b6e --- /dev/null +++ b/i3-msg/Makefile @@ -0,0 +1,28 @@ +# Default value so one can compile i3-msg standalone +TOPDIR=.. + +include $(TOPDIR)/common.mk + +# Depend on the object files of all source-files in src/*.c and on all header files +FILES=$(patsubst %.c,%.o,$(wildcard *.c)) +HEADERS=$(wildcard *.h) + +# Depend on the specific file (.c for each .o) and on all headers +%.o: %.c ${HEADERS} + echo "CC $<" + $(CC) $(CFLAGS) -c -o $@ $< + +all: ${FILES} + echo "LINK i3-msg" + $(CC) -o i3-msg ${FILES} $(LDFLAGS) + +install: all + echo "INSTALL" + $(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin + $(INSTALL) -m 0755 i3-msg $(DESTDIR)/usr/bin/ + +clean: + rm -f *.o + +distclean: clean + rm -f i3-msg diff --git a/i3-msg/main.c b/i3-msg/main.c new file mode 100644 index 00000000..197cceb6 --- /dev/null +++ b/i3-msg/main.c @@ -0,0 +1,113 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * i3-msg/main.c: Utility which sends messages to a running i3-instance using + * IPC via UNIX domain sockets. + * + * This serves as an example for how to send your own messages to i3. + * Additionally, it’s even useful sometimes :-). + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Formats a message (payload) of the given size and type and sends it to i3 via + * the given socket file descriptor. + * + */ +static void ipc_send_message(int sockfd, uint32_t message_size, + uint32_t message_type, uint8_t *payload) { + int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + sizeof(uint32_t) + message_size; + char msg[buffer_size]; + char *walk = msg; + + strcpy(walk, "i3-ipc"); + walk += strlen("i3-ipc"); + memcpy(walk, &message_size, sizeof(uint32_t)); + walk += sizeof(uint32_t); + memcpy(walk, &message_type, sizeof(uint32_t)); + walk += sizeof(uint32_t); + memcpy(walk, payload, message_size); + + int sent_bytes = 0; + int bytes_to_go = buffer_size; + while (sent_bytes < bytes_to_go) { + int n = write(sockfd, msg + sent_bytes, bytes_to_go); + if (n == -1) + err(EXIT_FAILURE, "write() failed"); + + sent_bytes += n; + bytes_to_go -= n; + } +} + +int main(int argc, char *argv[]) { + char *socket_path = "/tmp/i3-ipc.sock"; + int o, option_index = 0; + + static struct option long_options[] = { + {"socket", required_argument, 0, 's'}, + {"type", required_argument, 0, 't'}, + {"version", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + char *options_string = "s:t:vh"; + + 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') { + printf("currently only commands are implemented\n"); + } else if (o == 'v') { + printf("i3-msg " I3_VERSION); + return 0; + } else if (o == 'h') { + printf("i3-msg " I3_VERSION); + printf("i3-msg [-s ] [-t ] \n"); + return 0; + } + } + + if (optind >= argc) { + fprintf(stderr, "Error: missing message\n"); + fprintf(stderr, "i3-msg [-s ] [-t ] \n"); + return 1; + } + + int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (sockfd == -1) + err(EXIT_FAILURE, "Could not create socket"); + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_LOCAL; + strcpy(addr.sun_path, socket_path); + if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) + err(EXIT_FAILURE, "Could not connect to i3"); + + ipc_send_message(sockfd, strlen(argv[optind]), 0, (uint8_t*)argv[optind]); + + close(sockfd); + + return 0; +} diff --git a/include/client.h b/include/client.h index 8fbd4434..5d87b2ee 100644 --- a/include/client.h +++ b/include/client.h @@ -78,4 +78,11 @@ void client_set_below_floating(xcb_connection_t *conn, Client *client); */ bool client_is_floating(Client *client); +/** + * Change the border type for the given client to normal (n), 1px border (p) or + * completely borderless (b). + * + */ +void client_change_border(xcb_connection_t *conn, Client *client, char border_type); + #endif diff --git a/include/config.h b/include/config.h index 140db5ab..cfedfdb8 100644 --- a/include/config.h +++ b/include/config.h @@ -15,6 +15,7 @@ #ifndef _CONFIG_H #define _CONFIG_H +#include #include "queue.h" typedef struct Config Config; @@ -52,6 +53,8 @@ struct Config { const char *terminal; const char *font; + const char *ipc_socket_path; + /** The modifier which needs to be pressed in combination with your mouse * buttons to do things with floating windows (move, resize) */ uint32_t floating_modifier; @@ -75,6 +78,7 @@ struct Config { * configuration file. * */ -void load_configuration(xcb_connection_t *conn, const char *override_configfile); +void load_configuration(xcb_connection_t *conn, const char *override_configfile, bool reload); +void grab_all_keys(xcb_connection_t *conn); #endif diff --git a/include/data.h b/include/data.h index 2776d506..c2d83783 100644 --- a/include/data.h +++ b/include/data.h @@ -101,6 +101,22 @@ struct Colorpixel { SLIST_ENTRY(Colorpixel) colorpixels; }; +struct Cached_Pixmap { + xcb_pixmap_t id; + + /* We’re going to paint on it, so a graphics context will be needed */ + xcb_gcontext_t gc; + + /* The rect with which the pixmap was created */ + Rect rect; + + /* The rect of the object to which this pixmap belongs. Necessary to + * find out when we need to re-create the pixmap. */ + Rect *referred_rect; + + xcb_drawable_t referred_drawable; +}; + /** * Contains data for the windows needed to draw the titlebars on in stacking * mode @@ -108,7 +124,7 @@ struct Colorpixel { */ struct Stack_Window { xcb_window_t window; - xcb_gcontext_t gc; + struct Cached_Pixmap pixmap; Rect rect; /** Backpointer to the container this stack window is in */ @@ -149,6 +165,15 @@ struct Workspace { /** Number of this workspace, starting from 0 */ int num; + /** 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; @@ -234,7 +259,12 @@ struct Assignment { /** floating is true if this was an assignment to the special * workspace "~". Matching clients will be put into floating mode * automatically. */ - bool floating; + enum { + ASSIGN_FLOATING_NO, /* don’t float, but put on a workspace */ + ASSIGN_FLOATING_ONLY, /* float, but don’t assign on a workspace */ + ASSIGN_FLOATING /* float and put on a workspace */ + } floating; + /** The number of the workspace to assign to. */ int workspace; TAILQ_ENTRY(Assignment) assignments; @@ -311,6 +341,10 @@ struct Client { /** Holds the WM_CLASS, useful for matching the client in commands */ char *window_class; + /** 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; @@ -326,6 +360,10 @@ struct Client { * 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; diff --git a/include/handlers.h b/include/handlers.h index 74e29e4b..83b70894 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -154,4 +154,13 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *reply); +/** + * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a + * toolwindow (or similar) and to which window it belongs (logical parent). + * + */ +int handle_clientleader_change(void *data, xcb_connection_t *conn, + uint8_t state, xcb_window_t window, + xcb_atom_t name, xcb_get_property_reply_t *prop); + #endif diff --git a/include/i3.h b/include/i3.h index ccf7a495..46caa415 100644 --- a/include/i3.h +++ b/include/i3.h @@ -20,8 +20,9 @@ #ifndef _I3_H #define _I3_H -#define NUM_ATOMS 17 +#define NUM_ATOMS 18 +extern xcb_connection_t *global_conn; extern char **start_argv; extern Display *xkbdpy; extern TAILQ_HEAD(bindings_head, Binding) bindings; @@ -30,6 +31,8 @@ extern TAILQ_HEAD(assignments_head, Assignment) assignments; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; extern xcb_event_handlers_t evenths; extern int num_screens; +extern uint8_t root_depth; extern xcb_atom_t atoms[NUM_ATOMS]; +extern xcb_window_t root; #endif diff --git a/include/i3/ipc.h b/include/i3/ipc.h new file mode 100644 index 00000000..40e01158 --- /dev/null +++ b/include/i3/ipc.h @@ -0,0 +1,24 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * This public header defines the different constants and message types to use + * for the IPC interface to i3 (see docs/ipc for more information). + * + */ + +#ifndef _I3_IPC_H +#define _I3_IPC_H + +/** Never change this, only on major IPC breakage (don’t do that) */ +#define I3_IPC_MAGIC "i3-ipc" + +/** The payload of the message will be interpreted as a command */ +#define I3_IPC_MESSAGE_TYPE_COMMAND 0 + +#endif diff --git a/include/ipc.h b/include/ipc.h new file mode 100644 index 00000000..de4e2264 --- /dev/null +++ b/include/ipc.h @@ -0,0 +1,35 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ + +#ifndef _IPC_H +#define _IPC_H + +#include + +#include "i3/ipc.h" + +/** + * Handler for activity on the listening socket, meaning that a new client + * has just connected and we should accept() him. Sets up the event handler + * for activity on the new connection and inserts the file descriptor into + * the list of clients. + * + */ +void ipc_new_client(EV_P_ struct ev_io *w, int revents); + +/** + * Creates the UNIX domain socket at the given path, sets it to non-blocking + * mode, bind()s and listen()s on it. + * + */ +int ipc_create_socket(const char *filename); + +#endif diff --git a/include/util.h b/include/util.h index bc2dc5e0..f4c9d539 100644 --- a/include/util.h +++ b/include/util.h @@ -9,12 +9,14 @@ * */ #include +#include #include "data.h" #ifndef _UTIL_H #define _UTIL_H +#define die(...) errx(EXIT_FAILURE, __VA_ARGS__); #define exit_if_null(pointer, ...) { if (pointer == NULL) die(__VA_ARGS__); } #define STARTS_WITH(string, needle) (strncasecmp(string, needle, strlen(needle)) == 0) #define CIRCLEQ_NEXT_OR_NULL(head, elm, field) (CIRCLEQ_NEXT(elm, field) != CIRCLEQ_END(head) ? \ @@ -50,12 +52,6 @@ int max(int a, int b); */ void slog(char *fmt, ...); -/** - * Prints the message (see printf()) to stderr, then exits the program. - * - */ -void die(char *fmt, ...) __attribute__((__noreturn__)); - /** * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that * there is no more memory available) diff --git a/include/workspace.h b/include/workspace.h new file mode 100644 index 00000000..9c69e55b --- /dev/null +++ b/include/workspace.h @@ -0,0 +1,35 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include + +#include "data.h" + +#ifndef _WORKSPACE_H +#define _WORKSPACE_H + +/** + * Sets the name (or just its number) for the given workspace. This has to + * be called for every workspace as the rendering function + * (render_internal_bar) relies on workspace->name and workspace->name_len + * being ready-to-use. + * + */ +void workspace_set_name(Workspace *ws, const char *name); + +/** + * Returns true if the workspace is currently visible. Especially important for + * multi-monitor environments, as they can have multiple currenlty active + * workspaces. + * + */ +bool workspace_is_visible(Workspace *ws); + +#endif diff --git a/include/xcb.h b/include/xcb.h index de74bab6..d01f6da1 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -60,7 +60,8 @@ enum { _NET_SUPPORTED = 0, WM_PROTOCOLS, WM_DELETE_WINDOW, UTF8_STRING, - WM_STATE + WM_STATE, + WM_CLIENT_LEADER }; extern unsigned int xcb_numlock_mask; @@ -89,7 +90,7 @@ uint32_t get_colorpixel(xcb_connection_t *conn, char *hex); * */ xcb_window_t create_window(xcb_connection_t *conn, Rect r, uint16_t window_class, - int cursor, uint32_t mask, uint32_t *values); + int cursor, bool map, uint32_t mask, uint32_t *values); /** * Changes a single value in the graphic context (so one doesn’t have to @@ -143,4 +144,21 @@ void xcb_get_numlock_mask(xcb_connection_t *conn); */ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window); +/** + * + * Prepares the given Cached_Pixmap for usage (checks whether the size of the + * object this pixmap is related to (e.g. a window) has changed and re-creates + * the pixmap if so). + * + */ +void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap); + +/** + * Calculate the width of the given text (16-bit characters, UCS) with given + * real length (amount of glyphs) using the given font. + * + */ +int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *text, + int length); + #endif diff --git a/logo.svg b/logo.svg new file mode 100644 index 00000000..11ec4648 --- /dev/null +++ b/logo.svg @@ -0,0 +1,555 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Logo I3 + + + yellowiscool, farvardin + + + + + steckdenis + + + Logo for I3, an improved dynamic tiling window manager: http://i3.zekjur.net/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/client.c b/src/client.c index c3a80c36..81430b2f 100644 --- a/src/client.c +++ b/src/client.c @@ -248,3 +248,42 @@ void client_set_below_floating(xcb_connection_t *conn, Client *client) { bool client_is_floating(Client *client) { return (client->floating >= FLOATING_AUTO_ON); } + +/* + * Change the border type for the given client to normal (n), 1px border (p) or + * completely borderless (b). + * + */ +void client_change_border(xcb_connection_t *conn, Client *client, char border_type) { + switch (border_type) { + case 'n': + LOG("Changing to normal border\n"); + client->titlebar_position = TITLEBAR_TOP; + client->borderless = false; + break; + case 'p': + LOG("Changing to 1px border\n"); + client->titlebar_position = TITLEBAR_OFF; + client->borderless = false; + break; + case 'b': + LOG("Changing to borderless\n"); + client->titlebar_position = TITLEBAR_OFF; + client->borderless = true; + break; + default: + LOG("Unknown border mode\n"); + return; + } + + /* Ensure that the child’s position inside our window gets updated */ + client->force_reconfigure = true; + + /* For clients inside a container, we can simply render the container. + * If the client is floating, we need to render the whole layout */ + if (client->container != NULL) + render_container(conn, client->container); + else render_layout(conn); + + redecorate_window(conn, client); +} diff --git a/src/commands.c b/src/commands.c index 275f8f96..fce3ae65 100644 --- a/src/commands.c +++ b/src/commands.c @@ -26,6 +26,9 @@ #include "client.h" #include "floating.h" #include "xcb.h" +#include "config.h" +#include "workspace.h" +#include "commands.h" bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { /* If this container is empty, we’re done */ @@ -53,7 +56,7 @@ bool focus_window_in_container(xcb_connection_t *conn, Container *container, dir return true; } -typedef enum { THING_WINDOW, THING_CONTAINER } thing_t; +typedef enum { THING_WINDOW, THING_CONTAINER, THING_SCREEN } thing_t; static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) { LOG("focusing direction %d\n", direction); @@ -79,6 +82,41 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t return; } + /* For focusing screens, situation is different: we get the rect + * of the current screen, then get the screen which is on its + * right/left/bottom/top and just switch to the workspace on + * the target screen. */ + if (thing == THING_SCREEN) { + i3Screen *cs = c_ws->screen; + assert(cs != NULL); + Rect bounds = cs->rect; + + if (direction == D_RIGHT) + bounds.x += bounds.width; + else if (direction == D_LEFT) + bounds.x -= bounds.width; + else if (direction == D_UP) + bounds.y -= bounds.height; + else bounds.y += bounds.height; + + i3Screen *target = get_screen_containing(bounds.x, bounds.y); + if (target == NULL) { + LOG("Target screen NULL\n"); + /* Wrap around if the target screen is out of bounds */ + if (direction == D_RIGHT) + target = get_screen_most(D_LEFT); + else if (direction == D_LEFT) + target = get_screen_most(D_RIGHT); + else if (direction == D_UP) + target = get_screen_most(D_DOWN); + else target = get_screen_most(D_UP); + } + + LOG("Switching to ws %d\n", target->current_workspace + 1); + show_workspace(conn, target->current_workspace + 1); + return; + } + /* TODO: for horizontal default layout, this has to be expanded to LEFT/RIGHT */ if (direction == D_UP || direction == D_DOWN) { if (thing == THING_WINDOW) @@ -489,10 +527,8 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl floating_assign_to_workspace(client, t_ws); - bool target_invisible = t_ws->screen->current_workspace != t_ws->num; - /* If we’re moving it to an invisible screen, we need to unmap it */ - if (target_invisible) { + if (!workspace_is_visible(t_ws)) { LOG("This workspace is not visible, unmapping\n"); xcb_unmap_window(conn, client->frame); } else { @@ -514,7 +550,7 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl render_layout(conn); - if (!target_invisible) + if (workspace_is_visible(t_ws)) set_focus(conn, client, true); } @@ -574,10 +610,8 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa container->currently_focused = to_focus; to_container->currently_focused = current_client; - bool target_invisible = (to_container->workspace->screen->current_workspace != to_container->workspace->num); - /* If we’re moving it to an invisible screen, we need to unmap it */ - if (target_invisible) { + if (!workspace_is_visible(to_container->workspace)) { LOG("This workspace is not visible, unmapping\n"); xcb_unmap_window(conn, current_client->frame); } else { @@ -592,7 +626,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa render_layout(conn); - if (!target_invisible) + if (workspace_is_visible(to_container->workspace)) set_focus(conn, current_client, true); } @@ -846,6 +880,38 @@ static char **append_argument(char **original, char *argument) { return result; } +/* + * Switch to next or previous existing workspace + * + */ +static void next_previous_workspace(xcb_connection_t *conn, int direction) { + Workspace *t_ws; + int i; + + if (direction == 'n') { + /* If we are on the last workspace, we cannot go any further */ + if (c_ws->num == 9) + return; + + for (i = c_ws->num + 1; i <= 9; i++) { + t_ws = &(workspaces[i]); + if (t_ws->screen != NULL) + break; + } + } else if (direction == 'p') { + if (c_ws->num == 0) + return; + for (i = c_ws->num - 1; i >= 0 ; i--) { + t_ws = &(workspaces[i]); + if (t_ws->screen != NULL) + break; + } + } + + if (t_ws->screen != NULL) + show_workspace(conn, i+1); +} + /* * Parses a command, see file CMDMODE for more information * @@ -875,6 +941,12 @@ void parse_command(xcb_connection_t *conn, const char *command) { exit(EXIT_SUCCESS); } + /* Is it a ? */ + if (STARTS_WITH(command, "reload")) { + load_configuration(conn, NULL, true); + return; + } + /* Is it ? Then restart in place. */ if (STARTS_WITH(command, "restart")) { LOG("restarting \"%s\"...\n", start_argv[0]); @@ -931,13 +1003,23 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } + /* Is it 'bn' (border normal), 'bp' (border 1pixel) or 'bb' (border borderless)? */ + if (command[0] == 'b') { + if (last_focused == NULL) { + LOG("No window focused, cannot change border type\n"); + return; + } + client_change_border(conn, last_focused, command[1]); + return; + } + if (command[0] == 'H') { LOG("Hiding all floating windows\n"); floating_toggle_hide(conn, c_ws); return; } - enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE } with = WITH_WINDOW; + enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE, WITH_SCREEN } with = WITH_WINDOW; /* Is it a ? */ if (command[0] == 'w') { @@ -949,6 +1031,9 @@ void parse_command(xcb_connection_t *conn, const char *command) { } else if (command[0] == 'w') { with = WITH_WORKSPACE; command++; + } else if (command[0] == 's') { + with = WITH_SCREEN; + command++; } else { LOG("not yet implemented.\n"); return; @@ -983,6 +1068,12 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } + /* Is it 'n' or 'p' for next/previous workspace? (nw) */ + if ((command[0] == 'n' || command[0] == 'p') && command[1] == 'w') { + next_previous_workspace(conn, command[0]); + return; + } + /* It’s a normal */ char *rest = NULL; enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS; @@ -1046,6 +1137,10 @@ void parse_command(xcb_connection_t *conn, const char *command) { rest++; if (action == ACTION_FOCUS) { + if (with == WITH_SCREEN) { + focus_thing(conn, direction, THING_SCREEN); + continue; + } if (client_is_floating(last_focused)) { floating_focus_direction(conn, last_focused, direction); continue; @@ -1055,6 +1150,13 @@ void parse_command(xcb_connection_t *conn, const char *command) { } if (action == ACTION_MOVE) { + if (with == WITH_SCREEN) { + /* TODO: this should swap the screen’s contents + * (e.g. all workspaces) with the next/previous/… + * screen */ + LOG("Not yet implemented\n"); + continue; + } if (client_is_floating(last_focused)) { floating_move(conn, last_focused, direction); continue; @@ -1066,6 +1168,10 @@ void parse_command(xcb_connection_t *conn, const char *command) { } if (action == ACTION_SNAP) { + if (with == WITH_SCREEN) { + LOG("You cannot snap a screen (it makes no sense).\n"); + continue; + } snap_current_container(conn, direction); continue; } diff --git a/src/config.c b/src/config.c index d55c2a75..2f78dec9 100644 --- a/src/config.c +++ b/src/config.c @@ -18,6 +18,8 @@ #include "util.h" #include "config.h" #include "xcb.h" +#include "table.h" +#include "workspace.h" Config config; @@ -57,6 +59,40 @@ static void replace_variable(char *buffer, const char *key, const char *value) { } } +/* + * Ungrab the bound keys + * + */ +void ungrab_all_keys(xcb_connection_t *conn) { + Binding *bind; + TAILQ_FOREACH(bind, &bindings, bindings) { + LOG("Ungrabbing %d\n", bind->keycode); + xcb_ungrab_key(conn, bind->keycode, root, bind->keycode); + } +} + +/* + * Grab the bound keys (tell X to send us keypress events for those keycodes) + * + */ +void grab_all_keys(xcb_connection_t *conn) { + Binding *bind; + TAILQ_FOREACH(bind, &bindings, bindings) { + LOG("Grabbing %d\n", bind->keycode); + if ((bind->mods & BIND_MODE_SWITCH) != 0) + xcb_grab_key(conn, 0, root, 0, bind->keycode, + XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC); + else { + /* Grab the key in all combinations */ + #define GRAB_KEY(modifier) xcb_grab_key(conn, 0, root, modifier, bind->keycode, \ + XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC) + GRAB_KEY(bind->mods); + GRAB_KEY(bind->mods | xcb_numlock_mask); + GRAB_KEY(bind->mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK); + } + } +} + /* * Reads the configuration from ~/.i3/config or /etc/i3/config if not found. * @@ -64,7 +100,29 @@ static void replace_variable(char *buffer, const char *key, const char *value) { * configuration file. * */ -void load_configuration(xcb_connection_t *conn, const char *override_configpath) { +void load_configuration(xcb_connection_t *conn, const char *override_configpath, bool reload) { + if (reload) { + /* First ungrab the keys */ + ungrab_all_keys(conn); + + /* Clear the old binding and assignment lists */ + Binding *bind; + while (!TAILQ_EMPTY(&bindings)) { + bind = TAILQ_FIRST(&bindings); + TAILQ_REMOVE(&bindings, bind, bindings); + FREE(bind->command); + FREE(bind); + } + + struct Assignment *assign; + while (!TAILQ_EMPTY(&assignments)) { + assign = TAILQ_FIRST(&assignments); + FREE(assign->windowclass_title); + TAILQ_REMOVE(&assignments, assign, assignments); + FREE(assign); + } + } + SLIST_HEAD(variables_head, Variable) variables; #define OPTION_STRING(name) \ @@ -239,43 +297,94 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath) continue; } + /* name "workspace number" "name of the workspace" */ + if (strcasecmp(key, "name") == 0) { + LOG("name workspace: %s\n",value); + char *ws_str = sstrdup(value); + char *end = strchr(ws_str, ' '); + if (end == NULL) + die("Malformed name, couln't find terminating space\n"); + *end = '\0'; + + /* Strip trailing whitespace */ + while (strlen(value) > 0 && value[strlen(value)-1] == ' ') + value[strlen(value)-1] = '\0'; + + int ws_num = atoi(ws_str); + + if (ws_num < 1 || ws_num > 10) + die("Malformed name, invalid workspace number\n"); + + /* find the name */ + char *name = value; + name += strlen(ws_str) + 1; + + if (name == '\0') { + free(ws_str); + continue; + } + + workspace_set_name(&(workspaces[ws_num - 1]), name); + free(ws_str); + continue; + } + /* assign window class[/window title] → workspace */ if (strcasecmp(key, "assign") == 0) { LOG("assign: \"%s\"\n", value); - char *class_title = sstrdup(value); + char *class_title; char *target; + char *end; /* If the window class/title is quoted we skip quotes */ - if (class_title[0] == '"') { - class_title++; - char *end = strchr(class_title, '"'); - if (end == NULL) - die("Malformed assignment, couldn't find terminating quote\n"); - *end = '\0'; + if (value[0] == '"') { + class_title = sstrdup(value+1); + end = strchr(class_title, '"'); } else { + class_title = sstrdup(value); /* If it is not quoted, we terminate it at the first space */ - char *end = strchr(class_title, ' '); - if (end == NULL) - die("Malformed assignment, couldn't find terminating space\n"); - *end = '\0'; + end = strchr(class_title, ' '); } + if (end == NULL) + die("Malformed assignment, couldn't find terminating quote\n"); + *end = '\0'; + + /* Strip trailing whitespace */ + while (strlen(value) > 0 && value[strlen(value)-1] == ' ') + value[strlen(value)-1] = '\0'; /* The target is the last argument separated by a space */ if ((target = strrchr(value, ' ')) == NULL) - die("Malformed assignment, couldn't find target\n"); + die("Malformed assignment, couldn't find target (\"%s\")\n", value); target++; - if (*target != '~' && (atoi(target) < 1 || atoi(target) > 10)) + if (strchr(target, '~') == NULL && (atoi(target) < 1 || atoi(target) > 10)) die("Malformed assignment, invalid workspace number\n"); LOG("assignment parsed: \"%s\" to \"%s\"\n", class_title, target); struct Assignment *new = scalloc(sizeof(struct Assignment)); new->windowclass_title = class_title; - if (*target == '~') - new->floating = true; - else new->workspace = atoi(target); + if (strchr(target, '~') != NULL) + new->floating = ASSIGN_FLOATING_ONLY; + + while (*target == '~') + target++; + + if (atoi(target) >= 1 && atoi(target) <= 10) { + if (new->floating == ASSIGN_FLOATING_ONLY) + new->floating = ASSIGN_FLOATING; + new->workspace = atoi(target); + } TAILQ_INSERT_TAIL(&assignments, new, assignments); + + LOG("Assignment loaded: \"%s\":\n", class_title); + if (new->floating != ASSIGN_FLOATING_ONLY) + LOG(" to workspace %d\n", new->workspace); + + if (new->floating != ASSIGN_FLOATING_NO) + LOG(" will be floating\n"); + continue; } @@ -299,14 +408,21 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath) continue; } + if (strcasecmp(key, "ipc-socket") == 0) { + config.ipc_socket_path = sstrdup(value); + continue; + } + die("Unknown configfile option: %s\n", key); } + /* now grab all keys again */ + if (reload) + grab_all_keys(conn); fclose(handle); REQUIRED_OPTION(terminal); REQUIRED_OPTION(font); - while (!SLIST_EMPTY(&variables)) { struct Variable *v = SLIST_FIRST(&variables); SLIST_REMOVE_HEAD(&variables, variables); @@ -314,6 +430,14 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath) free(v->value); free(v); } + + /* Set an empty name for every workspace which got no name */ + for (int i = 0; i < 10; i++) { + if (workspaces[i].name != NULL) + continue; + + workspace_set_name(&(workspaces[i]), NULL); + } return; } diff --git a/src/handlers.c b/src/handlers.c index ec02180d..9a402629 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -276,16 +276,22 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e } return true; } - i3Font *font = load_font(conn, config.font); - int workspace = event->event_x / (font->height + 6), - c = 0; + int drawn = 0; /* Because workspaces can be on different screens, we need to loop through all of them and decide to count it based on its ->screen */ - for (int i = 0; i < 10; i++) - if ((workspaces[i].screen == screen) && (c++ == workspace)) { + for (int i = 0; i < 10; i++) { + if (workspaces[i].screen != screen) + continue; + LOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n", + i, drawn, workspaces[i].text_width); + if (event->event_x > (drawn + 1) && + event->event_x <= (drawn + 1 + workspaces[i].text_width + 5 + 5)) { show_workspace(conn, i+1); return true; } + + drawn += workspaces[i].text_width + 5 + 5 + 2; + } return true; } @@ -308,7 +314,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ if (config.floating_modifier != 0 && (event->state & config.floating_modifier) != 0) { if (client == NULL) { - LOG("Not handling, Mod1 was pressed and no client found\n"); + LOG("Not handling, floating_modifier was pressed and no client found\n"); return 1; } if (client_is_floating(client)) { @@ -348,6 +354,18 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ LOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width); + /* Some clients (xfontsel for example) seem to pass clicks on their + * window to the parent window, thus we receive an event here which in + * reality is a border_click. Check for the position and fix state. */ + if (border_click && + event->event_x >= client->child_rect.x && + event->event_x <= (client->child_rect.x + client->child_rect.width) && + event->event_y >= client->child_rect.y && + event->event_y <= (client->child_rect.y + client->child_rect.height)) { + LOG("Fixing border_click = false because of click in child\n"); + border_click = false; + } + if (!border_click) { LOG("client. done.\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); @@ -1046,3 +1064,31 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_ return 1; } + +/* + * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a + * toolwindow (or similar) and to which window it belongs (logical parent). + * + */ +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) { + LOG("client leader changed\n"); + if (prop == NULL) { + prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, + false, window, WM_CLIENT_LEADER, WINDOW, 0, 32), NULL); + } + + Client *client = table_get(&by_child, window); + if (client == NULL) + return 1; + + xcb_window_t *leader = xcb_get_property_value(prop); + if (leader == NULL) + return 1; + + LOG("changed to %08x\n", *leader); + + client->leader = *leader; + + return 1; +} diff --git a/src/ipc.c b/src/ipc.c new file mode 100644 index 00000000..34675943 --- /dev/null +++ b/src/ipc.c @@ -0,0 +1,232 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * ipc.c: Everything about the UNIX domain sockets for IPC + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "queue.h" +#include "i3/ipc.h" +#include "i3.h" +#include "util.h" +#include "commands.h" + +typedef struct ipc_client { + int fd; + + TAILQ_ENTRY(ipc_client) clients; +} ipc_client; + +TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients); + +/* + * Puts the given socket file descriptor into non-blocking mode or dies if + * setting O_NONBLOCK failed. Non-blocking sockets are a good idea for our + * IPC model because we should by no means block the window manager. + * + */ +static void set_nonblock(int sockfd) { + int flags = fcntl(sockfd, F_GETFL, 0); + flags |= O_NONBLOCK; + if (fcntl(sockfd, F_SETFL, flags) < 0) + err(-1, "Could not set O_NONBLOCK"); +} + +#if 0 +void broadcast(EV_P_ struct ev_timer *t, int revents) { + ipc_client *current; + TAILQ_FOREACH(current, &all_clients, clients) { + write(current->fd, "hi there!\n", strlen("hi there!\n")); + } +} +#endif + +/* + * Decides what to do with the received message. + * + * message is the raw packet, as received from the UNIX domain socket. size + * is the remaining size of bytes for this packet. + * + * message_size is the size of the message as the sender specified it. + * message_type is the type of the message as the sender specified it. + * + */ +static void ipc_handle_message(uint8_t *message, int size, + uint32_t message_size, uint32_t message_type) { + LOG("handling message of size %d\n", size); + LOG("sender specified size %d\n", message_size); + LOG("sender specified type %d\n", message_type); + LOG("payload as a string = %s\n", message); + + switch (message_type) { + case I3_IPC_MESSAGE_TYPE_COMMAND: + parse_command(global_conn, (const char*)message); + + break; + default: + LOG("unhandled ipc message\n"); + break; + } +} + +/* + * Handler for activity on a client connection, receives a message from a + * client. + * + * For now, the maximum message size is 2048. I’m not sure for what the + * IPC interface will be used in the future, thus I’m not implementing a + * mechanism for arbitrarily long messages, as it seems like overkill + * at the moment. + * + */ +static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { + char buf[2048]; + int n = read(w->fd, buf, sizeof(buf)); + + /* On error or an empty message, we close the connection */ + if (n <= 0) { +#if 0 + /* FIXME: I get these when closing a client socket, + * therefore we just treat them as an error. Is this + * correct? */ + if (errno == EAGAIN || errno == EWOULDBLOCK) + return; +#endif + + /* If not, there was some kind of error. We don’t bother + * and close the connection */ + close(w->fd); + + /* Delete the client from the list of clients */ + struct ipc_client *current; + TAILQ_FOREACH(current, &all_clients, clients) { + if (current->fd != w->fd) + continue; + + /* We can call TAILQ_REMOVE because we break out of the + * TAILQ_FOREACH afterwards */ + TAILQ_REMOVE(&all_clients, current, clients); + break; + } + + ev_io_stop(EV_A_ w); + + LOG("IPC: client disconnected\n"); + return; + } + + /* Terminate the message correctly */ + buf[n] = '\0'; + + /* Check if the message starts with the i3 IPC magic code */ + if (n < strlen(I3_IPC_MAGIC)) { + LOG("IPC: message too short, ignoring\n"); + return; + } + + if (strncmp(buf, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) { + LOG("IPC: message does not start with the IPC magic\n"); + return; + } + + uint8_t *message = (uint8_t*)buf; + message += strlen(I3_IPC_MAGIC); + n -= strlen(I3_IPC_MAGIC); + + /* The next 32 bit after the magic are the message size */ + uint32_t message_size = *((uint32_t*)message); + message += sizeof(uint32_t); + n -= sizeof(uint32_t); + + /* The last 32 bits of the header are the message type */ + uint32_t message_type = *((uint32_t*)message); + message += sizeof(uint32_t); + n -= sizeof(uint32_t); + + ipc_handle_message(message, n, message_size, message_type); +} + +/* + * Handler for activity on the listening socket, meaning that a new client + * has just connected and we should accept() him. Sets up the event handler + * for activity on the new connection and inserts the file descriptor into + * the list of clients. + * + */ +void ipc_new_client(EV_P_ struct ev_io *w, int revents) { + struct sockaddr_un peer; + socklen_t len = sizeof(struct sockaddr_un); + int client; + if ((client = accept(w->fd, (struct sockaddr*)&peer, &len)) < 0) { + if (errno == EINTR) + return; + else perror("accept()"); + return; + } + + set_nonblock(client); + + struct ev_io *package = calloc(sizeof(struct ev_io), 1); + ev_io_init(package, ipc_receive_message, client, EV_READ); + ev_io_start(EV_A_ package); + + LOG("IPC: new client connected\n"); + + struct ipc_client *new = calloc(sizeof(struct ipc_client), 1); + new->fd = client; + + TAILQ_INSERT_TAIL(&all_clients, new, clients); +} + +/* + * Creates the UNIX domain socket at the given path, sets it to non-blocking + * mode, bind()s and listen()s on it. + * + */ +int ipc_create_socket(const char *filename) { + int sockfd; + + /* Unlink the unix domain socket before */ + unlink(filename); + + if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { + perror("socket()"); + return -1; + } + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_LOCAL; + strcpy(addr.sun_path, filename); + if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) { + perror("bind()"); + return -1; + } + + set_nonblock(sockfd); + + if (listen(sockfd, 5) < 0) { + perror("listen()"); + return -1; + } + + return sockfd; +} diff --git a/src/layout.c b/src/layout.c index 4f132aea..9cc41a0c 100644 --- a/src/layout.c +++ b/src/layout.c @@ -103,19 +103,27 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw i3Font *font = load_font(conn, config.font); int decoration_height = font->height + 2 + 2; struct Colortriple *color; + Client *last_focused; /* Clients without a container (docks) won’t get decorated */ if (client->dock) return; LOG("redecorating child %08x\n", client->child); - if (client_is_floating(client) || client->container->currently_focused == client) { - /* Distinguish if the window is currently focused… */ - if (client_is_floating(client) || CUR_CELL->currently_focused == client) + last_focused = SLIST_FIRST(&(client->workspace->focus_stack)); + if (client_is_floating(client)) { + if (last_focused == client) color = &(config.client.focused); - /* …or if it is the focused window in a not focused container */ - else color = &(config.client.focused_inactive); - } else color = &(config.client.unfocused); + else color = &(config.client.unfocused); + } else { + if (client->container->currently_focused == client) { + /* Distinguish if the window is currently focused… */ + if (last_focused == client) + color = &(config.client.focused); + /* …or if it is the focused window in a not focused container */ + else color = &(config.client.focused_inactive); + } else color = &(config.client.unfocused); + } /* Our plan is the following: - Draw a rect around the whole client in color->background @@ -145,10 +153,12 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); } - /* Draw the lines */ - xcb_draw_line(conn, drawable, gc, color->border, 0, offset, client->rect.width, offset); - xcb_draw_line(conn, drawable, gc, color->border, 2, offset + font->height + 3, - client->rect.width - 3, offset + font->height + 3); + if (client->titlebar_position != TITLEBAR_OFF) { + /* Draw the lines */ + xcb_draw_line(conn, drawable, gc, color->border, 0, offset, client->rect.width, offset); + xcb_draw_line(conn, drawable, gc, color->border, 2, offset + font->height + 3, + client->rect.width - 3, offset + font->height + 3); + } /* If the client has a title, we draw it */ if (client->name != NULL) { @@ -227,11 +237,16 @@ void resize_client(xcb_connection_t *conn, Client *client) { rect->height = client->rect.height - 2; break; default: - if (client->titlebar_position == TITLEBAR_OFF) { + if (client->titlebar_position == TITLEBAR_OFF && client->borderless) { rect->x = 0; rect->y = 0; rect->width = client->rect.width; rect->height = client->rect.height; + } else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) { + rect->x = 1; + rect->y = 1; + rect->width = client->rect.width - 1 - 1; + rect->height = client->rect.height - 1 - 1; } else { rect->x = 2; rect->y = font->height + 2 + 2; @@ -282,9 +297,6 @@ void render_container(xcb_connection_t *conn, Container *container) { Client *client; int num_clients = 0, current_client = 0; - if (container->currently_focused == NULL) - return; - CIRCLEQ_FOREACH(client, &(container->clients), clients) num_clients++; @@ -362,6 +374,9 @@ void render_container(xcb_connection_t *conn, Container *container) { xcb_configure_window(conn, stack_win->window, mask, values); } + /* Prepare the pixmap for usage */ + cached_pixmap_prepare(conn, &(stack_win->pixmap)); + /* Render the decorations of all clients */ CIRCLEQ_FOREACH(client, &(container->clients), clients) { /* If the client is in fullscreen mode, it does not get reconfigured */ @@ -384,9 +399,12 @@ void render_container(xcb_connection_t *conn, Container *container) { client->force_reconfigure = false; - decorate_window(conn, client, stack_win->window, stack_win->gc, + decorate_window(conn, client, stack_win->pixmap.id, stack_win->pixmap.gc, current_client++ * decoration_height); } + + xcb_copy_area(conn, stack_win->pixmap.id, stack_win->window, stack_win->pixmap.gc, + 0, 0, 0, 0, stack_win->rect.width, stack_win->rect.height); } } @@ -415,7 +433,6 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid i3Font *font = load_font(conn, config.font); i3Screen *screen = r_ws->screen; enum { SET_NORMAL = 0, SET_FOCUSED = 1 }; - char label[3]; /* Fill the whole bar in black */ xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); @@ -432,18 +449,28 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid struct Colortriple *color = (screen->current_workspace == c ? &(config.bar.focused) : &(config.bar.unfocused)); + Workspace *ws = &workspaces[c]; + /* Draw the outer rect */ xcb_draw_rect(conn, screen->bar, screen->bargc, color->border, - drawn * height, 1, height - 2, height - 2); - xcb_draw_rect(conn, screen->bar, screen->bargc, color->background, - drawn * height + 1, 2, height - 4, height - 4); + drawn, /* x */ + 1, /* y */ + ws->text_width + 5 + 5, /* width = text width + 5 px left + 5px right */ + height - 2 /* height = max. height - 1 px upper and 1 px bottom border */); + + /* Draw the background of this rect */ + xcb_draw_rect(conn, screen->bar, screen->bargc, color->background, + drawn + 1, + 2, + ws->text_width + 4 + 4, + height - 4); - snprintf(label, sizeof(label), "%d", c+1); xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, color->text); xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, color->background); - xcb_image_text_8(conn, strlen(label), screen->bar, screen->bargc, drawn * height + 5 /* X */, - font->height + 1 /* Y = baseline of font */, label); - drawn++; + xcb_image_text_16(conn, ws->name_len, screen->bar, screen->bargc, drawn + 5 /* X */, + font->height + 1 /* Y = baseline of font */, + (xcb_char2b_t*)ws->name); + drawn += ws->text_width + 12; } LOG("done rendering internal\n"); diff --git a/src/mainx.c b/src/mainx.c index d95ca465..27e026b5 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -45,6 +45,9 @@ #include "xcb.h" #include "xinerama.h" #include "manage.h" +#include "ipc.h" + +xcb_connection_t *global_conn; /* This is the path to i3, copied from argv[0] when starting up */ char **start_argv; @@ -69,8 +72,12 @@ struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins); xcb_event_handlers_t evenths; xcb_atom_t atoms[NUM_ATOMS]; +xcb_window_t root; int num_screens = 0; +/* The depth of the root screen (used e.g. for creating new pixmaps later) */ +uint8_t root_depth; + /* * 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 @@ -108,7 +115,6 @@ int main(int argc, char *argv[], char *env[]) { bool autostart = true; xcb_connection_t *conn; xcb_property_handlers_t prophs; - xcb_window_t root; xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS]; setlocale(LC_ALL, ""); @@ -145,12 +151,12 @@ int main(int argc, char *argv[], char *env[]) { memset(&evenths, 0, sizeof(xcb_event_handlers_t)); memset(&prophs, 0, sizeof(xcb_property_handlers_t)); - conn = xcb_connect(NULL, &screens); + conn = global_conn = xcb_connect(NULL, &screens); if (xcb_connection_has_error(conn)) die("Cannot open display\n"); - load_configuration(conn, override_configpath); + load_configuration(conn, override_configpath, false); /* 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); @@ -172,6 +178,7 @@ int main(int argc, char *argv[], char *env[]) { REQUEST_ATOM(WM_DELETE_WINDOW); REQUEST_ATOM(UTF8_STRING); REQUEST_ATOM(WM_STATE); + REQUEST_ATOM(WM_CLIENT_LEADER); /* TODO: this has to be more beautiful somewhen */ int major, minor, error; @@ -261,7 +268,9 @@ int main(int argc, char *argv[], char *env[]) { xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL); /* Get the root window and set the event mask */ - root = xcb_aux_get_screen(conn, screens)->root; + 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 | @@ -300,6 +309,7 @@ int main(int argc, char *argv[], char *env[]) { GET_ATOM(WM_DELETE_WINDOW); GET_ATOM(UTF8_STRING); GET_ATOM(WM_STATE); + GET_ATOM(WM_CLIENT_LEADER); xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, handle_window_type, NULL); /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */ @@ -316,6 +326,9 @@ int main(int argc, char *argv[], char *env[]) { /* Watch WM_CLASS (= class of the window) */ xcb_property_set_handler(&prophs, WM_CLASS, 128, handle_windowclass_change, NULL); + /* Watch WM_CLIENT_LEADER (= logical parent window for toolbars etc.) */ + xcb_property_set_handler(&prophs, atoms[WM_CLIENT_LEADER], UINT_MAX, handle_clientleader_change, NULL); + /* Set up the atoms we support */ check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED"); @@ -325,21 +338,7 @@ int main(int argc, char *argv[], char *env[]) { xcb_get_numlock_mask(conn); - /* Grab the bound keys */ - Binding *bind; - TAILQ_FOREACH(bind, &bindings, bindings) { - LOG("Grabbing %d\n", bind->keycode); - if (bind->mods & BIND_MODE_SWITCH) - xcb_grab_key(conn, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC); - else { - /* Grab the key in all combinations */ - #define GRAB_KEY(modifier) xcb_grab_key(conn, 0, root, modifier, bind->keycode, \ - XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC) - GRAB_KEY(bind->mods); - GRAB_KEY(bind->mods | xcb_numlock_mask); - GRAB_KEY(bind->mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK); - } - } + grab_all_keys(conn); /* Autostarting exec-lines */ struct Autostart *exec; @@ -375,6 +374,18 @@ int main(int argc, char *argv[], char *env[]) { c_ws = &workspaces[screen->current_workspace]; } + /* 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) { + LOG("Could not create the IPC socket, IPC disabled\n"); + } else { + struct ev_io *ipc_io = scalloc(sizeof(struct ev_io)); + ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ); + ev_io_start(loop, ipc_io); + } + } + /* Handle the events which arrived until now */ xcb_check_cb(NULL, NULL, 0); diff --git a/src/manage.c b/src/manage.c index c1a79e7f..70d12488 100644 --- a/src/manage.c +++ b/src/manage.c @@ -29,6 +29,7 @@ #include "manage.h" #include "floating.h" #include "client.h" +#include "workspace.h" /* * Go through all existing windows (if the window manager is restarted) and manage them @@ -110,6 +111,7 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, 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_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); @@ -130,26 +132,26 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, int16_t x, int16_t y, uint16_t width, uint16_t height) { xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie, - utf8_title_cookie, title_cookie, class_cookie; + utf8_title_cookie, title_cookie, + class_cookie, leader_cookie; uint32_t mask = 0; uint32_t values[3]; uint16_t original_height = height; + bool map_frame = true; /* We are interested in property changes */ mask = XCB_CW_EVENT_MASK; values[0] = CHILD_EVENT_MASK; xcb_change_window_attributes(conn, child, mask, values); - /* Map the window first to avoid flickering */ - xcb_map_window(conn, child); - /* Place requests for properties ASAP */ wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX); strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX); state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX); utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128); + leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX); title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128); - class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128); + class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128); Client *new = table_get(&by_child, child); @@ -201,7 +203,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, height + 2 + 2 + font->height}; /* 2 px border plus font’s height */ /* Yo dawg, I heard you like windows, so I create a window around your window… */ - new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values); + new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values); /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t. * Also, xprop(1) needs that to work. */ @@ -249,11 +251,13 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) { LOG("Window is a dock.\n"); new->dock = true; + new->borderless = true; new->titlebar_position = TITLEBAR_OFF; new->force_reconfigure = true; new->container = NULL; SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients); /* If it’s a dock we can’t make it float, so we break */ + new->floating = FLOATING_AUTO_OFF; break; } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] || atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] || @@ -265,6 +269,12 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } } + /* All clients which have a leader should be floating */ + if (!new->dock && !client_is_floating(new) && new->leader != 0) { + LOG("Client has WM_CLIENT_LEADER hint set, setting floating\n"); + new->floating = FLOATING_AUTO_ON; + } + if (new->workspace->auto_float) { new->floating = FLOATING_AUTO_ON; LOG("workspace is in autofloat mode, setting floating\n"); @@ -305,16 +315,21 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, preply = xcb_get_property_reply(conn, class_cookie, NULL); handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply); + preply = xcb_get_property_reply(conn, leader_cookie, NULL); + handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply); + LOG("DEBUG: should have all infos now\n"); struct Assignment *assign; TAILQ_FOREACH(assign, &assignments, assignments) { if (get_matching_client(conn, assign->windowclass_title, new) == NULL) continue; - if (assign->floating) { + if (assign->floating == ASSIGN_FLOATING_ONLY || + assign->floating == ASSIGN_FLOATING) { new->floating = FLOATING_AUTO_ON; LOG("Assignment matches, putting client into floating mode\n"); - break; + if (assign->floating == ASSIGN_FLOATING_ONLY) + break; } LOG("Assignment \"%s\" matches, so putting it on workspace %d\n", @@ -325,7 +340,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, break; } - LOG("Changin container/workspace and unmapping the client\n"); + LOG("Changing container/workspace and unmapping the client\n"); Workspace *t_ws = &(workspaces[assign->workspace-1]); if (t_ws->screen == NULL) { LOG("initializing new workspace, setting num to %d\n", assign->workspace); @@ -338,7 +353,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, new->workspace = t_ws; old_focused = new->container->currently_focused; - xcb_unmap_window(conn, new->frame); + map_frame = workspace_is_visible(t_ws); break; } } @@ -349,14 +364,6 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, uint32_t values[] = { XCB_STACK_MODE_BELOW }; xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); } - } else if (!new->dock) { - /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */ - if (new->container->workspace->fullscreen_client == NULL) { - if (!client_is_floating(new)) - new->container->currently_focused = new; - if (new->container == CUR_CELL) - xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); - } } /* Insert into the currently active container, if it’s not a dock window */ @@ -382,10 +389,27 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, new->container = NULL; - new->floating_rect.x = new->rect.x = x; - new->floating_rect.y = new->rect.y = y; new->rect.width = new->floating_rect.width + 2 + 2; new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2; + + /* Some clients (like GIMP’s color picker window) get mapped + * to (0, 0), so we push them to a reasonable position + * (centered over their leader) */ + if (new->leader != 0 && x == 0 && y == 0) { + LOG("Floating client wants to (0x0), moving it over its leader instead\n"); + Client *leader = table_get(&by_child, new->leader); + if (leader == NULL) { + LOG("leader is NULL, centering it over current workspace\n"); + + x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2); + y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2); + } else { + x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2); + y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2); + } + } + new->floating_rect.x = new->rect.x = x; + new->floating_rect.y = new->rect.y = y; LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n", new->floating_rect.x, new->floating_rect.y, new->floating_rect.width, new->floating_rect.height); @@ -418,4 +442,20 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } render_layout(conn); + + /* Map the window first to avoid flickering */ + xcb_map_window(conn, child); + if (map_frame) + xcb_map_window(conn, new->frame); + if (CUR_CELL->workspace->fullscreen_client == NULL && !new->dock) { + /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */ + if (new->workspace->fullscreen_client == NULL) { + if (!client_is_floating(new)) + new->container->currently_focused = new; + if (new->container == CUR_CELL || client_is_floating(new)) + xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); + } + } + + xcb_flush(conn); } diff --git a/src/resize.c b/src/resize.c index 3d634420..09a8d083 100644 --- a/src/resize.c +++ b/src/resize.c @@ -43,6 +43,8 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i return 1; } + LOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x); + LOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); /* FIXME: horizontal resizing causes empty spaces to exist */ @@ -61,7 +63,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i /* Open a new window, the resizebar. Grab the pointer and move the window around as the user moves the pointer. */ Rect grabrect = {0, 0, root_screen->width_in_pixels, root_screen->height_in_pixels}; - xcb_window_t grabwin = create_window(conn, grabrect, XCB_WINDOW_CLASS_INPUT_ONLY, -1, mask, values); + xcb_window_t grabwin = create_window(conn, grabrect, XCB_WINDOW_CLASS_INPUT_ONLY, -1, true, mask, values); Rect helprect; if (orientation == O_VERTICAL) { @@ -87,7 +89,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i xcb_window_t helpwin = create_window(conn, helprect, XCB_WINDOW_CLASS_INPUT_OUTPUT, (orientation == O_VERTICAL ? XCB_CURSOR_SB_V_DOUBLE_ARROW : - XCB_CURSOR_SB_H_DOUBLE_ARROW), mask, values); + XCB_CURSOR_SB_H_DOUBLE_ARROW), true, mask, values); xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin); diff --git a/src/util.c b/src/util.c index 3a3890f1..d09dafe2 100644 --- a/src/util.c +++ b/src/util.c @@ -62,20 +62,6 @@ void slog(char *fmt, ...) { va_end(args); } -/* - * Prints the message (see printf()) to stderr, then exits the program. - * - */ -void die(char *fmt, ...) { - va_list args; - - va_start(args, fmt); - vfprintf(stderr, fmt, args); - va_end(args); - - exit(EXIT_FAILURE); -} - /* * The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of * the called functions returns NULL, meaning that there is no more memory available @@ -83,13 +69,13 @@ void die(char *fmt, ...) { */ void *smalloc(size_t size) { void *result = malloc(size); - exit_if_null(result, "Error: out of memory (malloc(%d))\n", size); + exit_if_null(result, "Error: out of memory (malloc(%zd))\n", size); return result; } void *scalloc(size_t size) { void *result = calloc(size, 1); - exit_if_null(result, "Error: out of memory (calloc(%d))\n", size); + exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size); return result; } @@ -362,14 +348,30 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { redecorate_window(conn, last_focused); } + /* If the last client was a floating client, we need to go to the next + * tiling client in stack and re-decorate it. */ + if (old_client != NULL && client_is_floating(old_client)) { + LOG("Coming from floating client, searching next tiling...\n"); + Client *current; + SLIST_FOREACH(current, &(client->workspace->focus_stack), focus_clients) { + if (client_is_floating(current)) + continue; + + LOG("Found window: %p / child %p\n", current->frame, current->child); + redecorate_window(conn, current); + break; + } + + } + + SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); + SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients); + /* If we’re in stacking mode, this renders the container to update changes in the title bars and to raise the focused client */ if ((old_client != NULL) && (old_client != client) && !old_client->dock) redecorate_window(conn, old_client); - SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); - SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients); - /* redecorate_window flushes, so we don’t need to */ redecorate_window(conn, client); } @@ -385,7 +387,8 @@ void leave_stack_mode(xcb_connection_t *conn, Container *container) { SLIST_REMOVE(&stack_wins, stack_win, Stack_Window, stack_windows); - xcb_free_gc(conn, stack_win->gc); + xcb_free_gc(conn, stack_win->pixmap.gc); + xcb_free_pixmap(conn, stack_win->pixmap.id); xcb_destroy_window(conn, stack_win->window); stack_win->rect.width = -1; @@ -421,11 +424,13 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) XCB_EVENT_MASK_EXPOSURE; /* …our window needs to be redrawn */ struct Stack_Window *stack_win = &(container->stack_win); - stack_win->window = create_window(conn, rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values); + stack_win->window = create_window(conn, rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values); - /* Generate a graphics context for the titlebar */ - stack_win->gc = xcb_generate_id(conn); - xcb_create_gc(conn, stack_win->gc, stack_win->window, 0, 0); + /* Initialize the entry for our cached pixmap. It will be + * created as soon as it’s needed (see cached_pixmap_prepare). */ + memset(&(stack_win->pixmap), 0, sizeof(struct Cached_Pixmap)); + stack_win->pixmap.referred_rect = &stack_win->rect; + stack_win->pixmap.referred_drawable = stack_win->window; stack_win->container = container; diff --git a/src/workspace.c b/src/workspace.c new file mode 100644 index 00000000..1e2aaf4f --- /dev/null +++ b/src/workspace.c @@ -0,0 +1,57 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * workspace.c: Functions for modifying workspaces + * + */ +#include +#include +#include + +#include "util.h" +#include "data.h" +#include "i3.h" +#include "config.h" +#include "xcb.h" + +/* + * Sets the name (or just its number) for the given workspace. This has to + * be called for every workspace as the rendering function + * (render_internal_bar) relies on workspace->name and workspace->name_len + * being ready-to-use. + * + */ +void workspace_set_name(Workspace *ws, const char *name) { + char *label; + int ret; + + if (name != NULL) + ret = asprintf(&label, "%d: %s", ws->num + 1, name); + else ret = asprintf(&label, "%d", ws->num + 1); + + if (ret == -1) + errx(1, "asprintf() failed"); + + FREE(ws->name); + + ws->name = convert_utf8_to_ucs2(label, &(ws->name_len)); + ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len); + + free(label); +} + +/* + * Returns true if the workspace is currently visible. Especially important for + * multi-monitor environments, as they can have multiple currenlty active + * workspaces. + * + */ +bool workspace_is_visible(Workspace *ws) { + return (ws->screen->current_workspace == ws->num); +} diff --git a/src/xcb.c b/src/xcb.c index 4062f648..3f5d4280 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -18,6 +18,7 @@ #include #include +#include "i3.h" #include "util.h" #include "xcb.h" @@ -89,7 +90,7 @@ uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) { * */ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_class, int cursor, - uint32_t mask, uint32_t *values) { + bool map, uint32_t mask, uint32_t *values) { xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; xcb_window_t result = xcb_generate_id(conn); xcb_cursor_t cursor_id = xcb_generate_id(conn); @@ -120,7 +121,8 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id); /* Map the window (= make it visible) */ - xcb_map_window(conn, result); + if (map) + xcb_map_window(conn, result); return result; } @@ -259,3 +261,103 @@ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) { uint32_t values[] = { XCB_STACK_MODE_ABOVE }; xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values); } + +/* + * + * Prepares the given Cached_Pixmap for usage (checks whether the size of the + * object this pixmap is related to (e.g. a window) has changed and re-creates + * the pixmap if so). + * + */ +void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) { + LOG("preparing pixmap\n"); + + /* If the Rect did not change, the pixmap does not need to be recreated */ + if (memcmp(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)) == 0) + return; + + memcpy(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)); + + if (pixmap->id == 0 || pixmap->gc == 0) { + LOG("Creating new pixmap...\n"); + pixmap->id = xcb_generate_id(conn); + pixmap->gc = xcb_generate_id(conn); + } else { + LOG("Re-creating this pixmap...\n"); + xcb_free_gc(conn, pixmap->gc); + xcb_free_pixmap(conn, pixmap->id); + } + + xcb_create_pixmap(conn, root_depth, pixmap->id, + pixmap->referred_drawable, pixmap->rect.width, pixmap->rect.height); + + xcb_create_gc(conn, pixmap->gc, pixmap->id, 0, 0); +} + +/* + * Returns the xcb_charinfo_t for the given character (specified by row and + * column in the lookup table) if existing, otherwise the minimum bounds. + * + */ +static xcb_charinfo_t *get_charinfo(int col, int row, xcb_query_font_reply_t *font_info, + xcb_charinfo_t *table, bool dont_fallback) { + xcb_charinfo_t *result; + + /* Bounds checking */ + if (row < font_info->min_byte1 || row > font_info->max_byte1 || + col < font_info->min_char_or_byte2 || col > font_info->max_char_or_byte2) + return NULL; + + /* If we don’t have a table to lookup the infos per character, return the + * minimum bounds */ + if (table == NULL) + return &font_info->min_bounds; + + result = &table[((row - font_info->min_byte1) * + (font_info->max_char_or_byte2 - font_info->min_char_or_byte2 + 1)) + + (col - font_info->min_char_or_byte2)]; + + /* If the character has an entry in the table, return it */ + if (result->character_width != 0 || + (result->right_side_bearing | + result->left_side_bearing | + result->ascent | + result->descent) != 0) + return result; + + /* Otherwise, get the default character and return its charinfo */ + if (dont_fallback) + return NULL; + + return get_charinfo((font_info->default_char >> 8), + (font_info->default_char & 0xFF), + font_info, + table, + true); +} + +/* + * Calculate the width of the given text (16-bit characters, UCS) with given + * real length (amount of glyphs) using the given font. + * + */ +int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *text, int length) { + xcb_query_font_reply_t *font_info; + xcb_charinfo_t *table; + int i, width = 0; + i3Font *font = load_font(conn, font_pattern); + + font_info = xcb_query_font_reply(conn, xcb_query_font_unchecked(conn, font->id), NULL); + table = xcb_query_font_char_infos(font_info); + + for (i = 0; i < 2 * length; i += 2) { + xcb_charinfo_t *info = get_charinfo(text[i+1], text[i], font_info, table, false); + if (info == NULL) + continue; + width += info->character_width; + } + + free(font_info); + + return width; +} diff --git a/src/xinerama.c b/src/xinerama.c index f1becf8c..1b410e03 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -113,7 +114,7 @@ static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspac font->height + 6}; uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS}; - screen->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values); + screen->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values); screen->bargc = xcb_generate_id(conn); xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0); @@ -156,46 +157,55 @@ static void disable_xinerama(xcb_connection_t *conn) { static void query_screens(xcb_connection_t *conn, struct screens_head *screenlist) { xcb_xinerama_query_screens_reply_t *reply; xcb_xinerama_screen_info_t *screen_info; + time_t before_trying = time(NULL); - reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL); - if (!reply) { - LOG("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); - num_screens = 0; + /* Try repeatedly to find screens (there might be short timeframes in + * which the X server does not return any screens, such as when rotating + * screens), but not longer than 5 seconds (strictly speaking, only four + * seconds of trying are guaranteed due to the 1-second-resolution) */ + while ((time(NULL) - before_trying) < 5) { + reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL); + if (!reply) { + LOG("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); + num_screens = 0; - for (int screen = 0; screen < screens; screen++) { - i3Screen *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org, screenlist); - if (s!= NULL) { - /* 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 = calloc(sizeof(i3Screen), 1); - 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(screenlist, s, screens); - else TAILQ_INSERT_TAIL(screenlist, s, screens); - num_screens++; + for (int screen = 0; screen < screens; screen++) { + i3Screen *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org, screenlist); + if (s!= NULL) { + /* 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 = calloc(sizeof(i3Screen), 1); + 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(screenlist, s, screens); + else TAILQ_INSERT_TAIL(screenlist, s, screens); + num_screens++; + } + + LOG("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); } - LOG("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); - } + free(reply); - free(reply); + if (num_screens == 0) { + LOG("No screens found. This is weird. Trying again...\n"); + continue; + } - if (num_screens == 0) { - LOG("No screens found. This is weird.\n"); - exit(1); + break; } }