Merge branch 'next' into testcases

This commit is contained in:
Michael Stapelberg 2009-08-05 20:22:24 +02:00
commit 8d2cb53794
30 changed files with 1934 additions and 245 deletions

View File

@ -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 systems 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
# Dont 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

70
common.mk Normal file
View File

@ -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 systems 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
# Dont print command lines which are run
.SILENT:
# Always remake the following targets
.PHONY: install clean dist distclean

12
debian/changelog vendored
View File

@ -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: Dont crash when clients reconfigure themselves
-- Michael Stapelberg <michael@stapelberg.de> 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.

View File

@ -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), youd 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

28
i3-msg/Makefile Normal file
View File

@ -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

113
i3-msg/main.c Normal file
View File

@ -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, its even useful sometimes :-).
*
*/
#include <ev.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <err.h>
#include <stdint.h>
#include <getopt.h>
/*
* 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 <socket>] [-t <type>] <message>\n");
return 0;
}
}
if (optind >= argc) {
fprintf(stderr, "Error: missing message\n");
fprintf(stderr, "i3-msg [-s <socket>] [-t <type>] <message>\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;
}

View File

@ -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

View File

@ -15,6 +15,7 @@
#ifndef _CONFIG_H
#define _CONFIG_H
#include <stdbool.h>
#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

View File

@ -101,6 +101,22 @@ struct Colorpixel {
SLIST_ENTRY(Colorpixel) colorpixels;
};
struct Cached_Pixmap {
xcb_pixmap_t id;
/* Were 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 workspaces name (in glyphs) */
int name_len;
/** Width of the workspaces 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, /* dont float, but put on a workspace */
ASSIGN_FLOATING_ONLY, /* float, but dont 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;

View File

@ -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

View File

@ -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

24
include/i3/ipc.h Normal file
View File

@ -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 (dont 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

35
include/ipc.h Normal file
View File

@ -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 <ev.h>
#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

View File

@ -9,12 +9,14 @@
*
*/
#include <xcb/xcb.h>
#include <err.h>
#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)

35
include/workspace.h Normal file
View File

@ -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 <xcb/xcb.h>
#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

View File

@ -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 doesnt 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

555
logo.svg Normal file
View File

@ -0,0 +1,555 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="210mm"
height="297mm"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.46"
sodipodi:docname="logo_i3_linuxfr_bapt_v2.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs4">
<linearGradient
id="linearGradient3750">
<stop
style="stop-color:#17273b;stop-opacity:1;"
offset="0"
id="stop3752" />
<stop
id="stop4356"
offset="0.41666666"
style="stop-color:#008cd4;stop-opacity:0.78039217;" />
<stop
style="stop-color:#6eb5de;stop-opacity:0.69803923;"
offset="0.87847221"
id="stop4358" />
<stop
style="stop-color:#88bfe5;stop-opacity:0.61960787;"
offset="1"
id="stop3754" />
</linearGradient>
<inkscape:perspective
id="perspective3661"
inkscape:persp3d-origin="750.50629 : 505.26732 : 1"
inkscape:vp_z="683.5728 : 1230.5721 : 1"
inkscape:vp_y="0 : 1946.8917 : 0"
inkscape:vp_x="-526.84957 : 2.2065866e-13 : 0"
sodipodi:type="inkscape:persp3d" />
<linearGradient
id="linearGradient3284">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop3286" />
<stop
style="stop-color:#ffffff;stop-opacity:0.7518248;"
offset="1"
id="stop3288" />
</linearGradient>
<linearGradient
id="linearGradient3278">
<stop
id="stop3280"
offset="0"
style="stop-color:#33bff7;stop-opacity:0.38039216;" />
<stop
id="stop3282"
offset="1"
style="stop-color:#2d446b;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient3272">
<stop
id="stop3274"
offset="0"
style="stop-color:#2596f6;stop-opacity:0.38039216;" />
<stop
id="stop3276"
offset="1"
style="stop-color:#2d446b;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient3266">
<stop
id="stop3268"
offset="0"
style="stop-color:#189fff;stop-opacity:0.38039216;" />
<stop
id="stop3270"
offset="1"
style="stop-color:#010b2b;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient3244">
<stop
id="stop3246"
offset="0"
style="stop-color:#419bff;stop-opacity:1;" />
<stop
id="stop3248"
offset="1"
style="stop-color:#002359;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient3159">
<stop
style="stop-color:#33bff7;stop-opacity:0.38039216;"
offset="0"
id="stop3161" />
<stop
style="stop-color:#2d446b;stop-opacity:1;"
offset="1"
id="stop3163" />
</linearGradient>
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="-526.84957 : 2.9848654e-13 : 0"
inkscape:vp_y="1.192088e-13 : 1946.8917 : 0"
inkscape:vp_z="680.54236 : 1232.3792 : 1"
inkscape:persp3d-origin="730.30325 : 937.39936 : 1"
id="perspective10" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3244"
id="radialGradient3256"
cx="344.73471"
cy="77.263504"
fx="344.73471"
fy="77.263504"
r="196.15704"
gradientTransform="matrix(-1.39796,-5.3360318e-2,8.3551106e-2,-2.1889079,820.20461,272.08319)"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3159"
id="radialGradient3264"
cx="140.3363"
cy="300.27451"
fx="140.3363"
fy="300.27451"
r="206.15704"
gradientTransform="matrix(0.6771172,-0.6830119,1.1802789,1.1700925,-161.83993,77.639162)"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3284"
id="radialGradient3290"
cx="267.50388"
cy="337.12692"
fx="267.50388"
fy="337.12692"
r="77.845424"
gradientTransform="matrix(1,0,0,0.7430849,0,86.613009)"
gradientUnits="userSpaceOnUse" />
<inkscape:perspective
id="perspective3373"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 526.18109 : 1"
sodipodi:type="inkscape:persp3d" />
<linearGradient
id="linearGradient3211">
<stop
id="stop3213"
offset="0"
style="stop-color: rgb(0, 160, 255); stop-opacity: 1;" />
<stop
id="stop3215"
offset="1"
style="stop-color: rgb(0, 37, 255); stop-opacity: 1;" />
</linearGradient>
<filter
inkscape:collect="always"
id="filter3406">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="13.396228"
id="feGaussianBlur3408" />
</filter>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3284"
id="radialGradient3416"
cx="119.96373"
cy="229.28981"
fx="119.96373"
fy="229.28981"
r="203.19508"
gradientTransform="matrix(0.7210805,2.1168143,-1.4722239,0.5015047,347.53034,-320.94088)"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3266"
id="radialGradient3423"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.1454302,-1.1687051,2.0295327,1.9193266,-889.0626,57.219022)"
cx="351.15485"
cy="372.06332"
fx="351.15485"
fy="372.06332"
r="163.57143" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3284"
id="radialGradient3734"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.7210805,2.1168143,-1.4722239,0.5015047,347.53034,-320.94088)"
cx="119.96373"
cy="229.28981"
fx="119.96373"
fy="229.28981"
r="203.19508" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3284"
id="radialGradient3736"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.7210805,2.1168143,-1.4722239,0.5015047,347.53034,-320.94088)"
cx="119.96373"
cy="229.28981"
fx="119.96373"
fy="229.28981"
r="203.19508" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3750"
id="radialGradient3758"
cx="390.69662"
cy="258.92429"
fx="390.69662"
fy="258.92429"
r="32.03125"
gradientTransform="matrix(4.0292425,-5.5974184,5.1695404,3.721239,-2510.9809,1613.1551)"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3750"
id="radialGradient3760"
cx="59.046589"
cy="248.2272"
fx="59.046589"
fy="248.2272"
r="197.15625"
gradientTransform="matrix(1.7105919,-4.3705195e-2,3.3347151e-2,1.3051856,-58.593126,-250.18318)"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3750"
id="radialGradient3762"
cx="192.64008"
cy="282.40387"
fx="192.64008"
fy="282.40387"
r="133.09375"
gradientTransform="matrix(1.0081228,0.4493093,-1.0529321,2.3624813,273.13721,-455.12138)"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3750"
id="radialGradient4344"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(5.7478532,-0.5889281,0.4834745,4.7186431,-260.98332,-919.446)"
cx="24"
cy="280.45392"
fx="24"
fy="280.45392"
r="65.0625" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3750"
id="linearGradient4354"
x1="-125.59599"
y1="-100.47679"
x2="-5.2882538"
y2="-100.47679"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="239.17981"
inkscape:cy="807.75327"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1272"
inkscape:window-height="950"
inkscape:window-x="24"
inkscape:window-y="24" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Logo I3</dc:title>
<dc:contributor>
<cc:Agent>
<dc:title>yellowiscool, farvardin</dc:title>
</cc:Agent>
</dc:contributor>
<dc:creator>
<cc:Agent>
<dc:title>steckdenis</dc:title>
</cc:Agent>
</dc:creator>
<dc:description>Logo for I3, an improved dynamic tiling window manager: http://i3.zekjur.net/</dc:description>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/3.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="fond"
style="display:none">
<rect
style="opacity:0.87000002;fill:#5599ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:60;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="rect3241"
width="330"
height="313.5"
x="39"
y="103.86218" />
</g>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
style="display:inline">
<rect
style="opacity:1;fill:url(#radialGradient3423);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect2383"
width="322.85715"
height="308.57144"
x="42.85714"
y="106.6479" />
<path
style="opacity:0.09583333;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:60;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 43.707165,106.65625 C 41.782753,110.85167 43.413409,116.56832 42.911106,121.37227 C 43.128238,169.37318 43.345369,217.37409 43.5625,265.375 C 55.407545,275.84522 72.489757,274.25524 87.152921,276.2874 C 140.64585,279.73053 194.4359,279.89819 247.75899,273.83826 C 281.78509,270.40324 315.81178,265.10681 348.64903,255.42536 C 357.00872,252.61832 367.50989,246.20234 365.71875,236.08333 C 365.71875,192.94097 365.71875,149.79861 365.71875,106.65625 C 258.38155,106.65625 151.04436,106.65625 43.707165,106.65625 z"
id="path3221" />
<g
transform="matrix(0.3387513,-0.3401668,0.3401668,0.3387513,59.228831,277.49214)"
style="opacity:0.5;fill:#000000;fill-opacity:1;filter:url(#filter3406)"
id="g3394">
<path
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:60;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 412.75,98.59375 C 396.19379,98.835333 382.96319,112.44354 383.1875,129 C 383.1875,129 383.18749,216.25107 383.1875,325.3125 C 383.1875,348.61986 371.07095,364.44442 344.96875,378.78125 C 318.86655,393.11808 279.99599,401.65625 241.09375,401.65625 C 202.19151,401.65625 163.32095,393.11808 137.21875,378.78125 C 111.11655,364.44442 99,348.61986 99,325.3125 C 99,270.93917 99.441249,221.95695 99.65625,186.4375 C 99.76375,168.67777 99.833258,154.32173 99.75,144.09375 C 99.708371,138.97976 99.620501,134.98608 99.46875,131.625 C 99.392874,129.94446 99.331848,128.56919 99.0625,126.375 C 98.927826,125.27791 98.894428,124.1628 98.0625,121.1875 C 97.646536,119.69985 97.275733,117.79087 94.875,113.75 C 92.474267,109.70913 84.912085,98.999943 69,99 C 59.653467,98.879464 50.78473,103.12192 45.013008,110.47443 C 39.241286,117.82694 37.226064,127.44941 39.5625,136.5 C 39.625678,138.53091 39.71934,140.82729 39.75,144.59375 C 39.827653,154.13315 39.763299,168.40852 39.65625,186.09375 C 39.442151,221.46421 39,270.62442 39,325.3125 C 38.999999,372.89506 69.913445,410.26687 108.34375,431.375 C 146.77406,452.48313 193.95265,461.65625 241.09375,461.65625 C 288.23485,461.65625 335.41344,452.48313 373.84375,431.375 C 412.27406,410.26687 443.1875,372.89506 443.1875,325.3125 C 443.18749,216.25108 443.1875,129 443.1875,129 C 443.29728,120.89717 440.12471,113.0945 434.39168,107.36735 C 428.65864,101.6402 420.85272,98.475651 412.75,98.59375 L 412.75,98.59375 z"
id="path3396" />
<path
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:60;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 239.5625,99.5 C 223.00629,99.741583 209.77569,113.34979 210,129.90625 L 210,350 C 209.84699,360.81908 215.53126,370.88244 224.87619,376.33663 C 234.22111,381.79082 245.77889,381.79082 255.12381,376.33663 C 264.46874,370.88244 270.15301,360.81908 270,350 L 270,129.90625 C 270.10978,121.80342 266.93721,114.00075 261.20418,108.2736 C 255.47114,102.54645 247.66522,99.381901 239.5625,99.5 L 239.5625,99.5 z"
id="path3398" />
<path
sodipodi:type="arc"
style="fill:#000000;fill-opacity:1;stroke-width:60;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="path3400"
sodipodi:cx="70"
sodipodi:cy="40"
sodipodi:rx="30"
sodipodi:ry="30"
d="M 100,40 A 30,30 0 1 1 40,40 A 30,30 0 1 1 100,40 z"
transform="translate(0,-10)" />
<path
transform="translate(170,-10)"
d="M 100,40 A 30,30 0 1 1 40,40 A 30,30 0 1 1 100,40 z"
sodipodi:ry="30"
sodipodi:rx="30"
sodipodi:cy="40"
sodipodi:cx="70"
id="path3402"
style="fill:#000000;fill-opacity:1;stroke-width:60;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
sodipodi:type="arc" />
<path
sodipodi:type="arc"
style="fill:#000000;fill-opacity:1;stroke-width:60;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="path3404"
sodipodi:cx="70"
sodipodi:cy="40"
sodipodi:rx="30"
sodipodi:ry="30"
d="M 100,40 A 30,30 0 1 1 40,40 A 30,30 0 1 1 100,40 z"
transform="translate(345,-10)" />
</g>
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="tiling"
style="display:none">
<rect
y="106.6479"
x="42.85714"
height="308.57144"
width="322.85715"
id="rect3247"
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<rect
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect3253"
width="212.07706"
height="98.216858"
x="42.85096"
y="218.0385" />
<rect
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect3257"
width="110.75323"
height="70.943619"
x="254.94135"
y="274.26196" />
<rect
y="218.02481"
x="254.93958"
height="56.235466"
width="110.76914"
id="rect3259"
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<rect
y="345.25272"
x="254.91608"
height="69.955116"
width="110.78036"
id="rect3255"
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<rect
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect3249"
width="106.87583"
height="111.69163"
x="42.849949"
y="106.32091" />
<rect
y="106.45182"
x="149.72185"
height="111.57816"
width="105.18911"
id="rect3261"
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<rect
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect3263"
width="110.786"
height="111.57211"
x="254.91632"
y="106.45485" />
<rect
y="316.22876"
x="42.847878"
height="98.979958"
width="212.0791"
id="rect3251"
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" />
</g>
<g
inkscape:groupmode="layer"
id="layer5"
inkscape:label="tiling2"
style="display:inline">
<rect
y="106.6479"
x="42.85714"
height="308.57144"
width="322.85715"
id="rect3244"
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" />
<rect
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline"
id="rect3246"
width="322.85715"
height="308.57144"
x="42.85714"
y="106.6479" />
<path
style="fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;display:inline"
d="M 182.85714,106.6479 L 182.85714,415.21935 L 182.85714,106.6479 z"
id="rect3246"
sodipodi:nodetypes="ccc" />
<path
sodipodi:nodetypes="ccc"
id="path3254"
d="M 182.41744,207.43363 L 43.296842,207.43363 L 182.41744,207.43363 z"
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
<path
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
d="M 182.41744,313.43363 L 43.296842,313.43363 L 182.41744,313.43363 z"
id="path3256"
sodipodi:nodetypes="ccc" />
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="I3"
style="display:inline">
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:60;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline;opacity:0.87;color:#000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-dashoffset:0;visibility:visible;overflow:visible;enable-background:accumulate"
d="M 232.58675,170.48705 C 227.06049,176.20076 227.20767,185.31117 232.91561,190.84339 C 232.91561,190.84339 262.59553,220.39981 299.69461,257.34451 C 307.623,265.23991 308.90149,274.72214 304.93625,288.45787 C 300.97101,302.19359 290.70796,318.30838 277.52977,331.54163 C 264.35159,344.77488 248.27973,355.10504 234.56067,359.12752 C 220.8416,363.15 211.35411,361.91106 203.42572,354.01566 C 184.92972,335.59662 168.41707,318.85373 156.40736,306.74834 C 150.40251,300.69564 145.54261,295.80887 142.03518,292.37245 C 140.28147,290.65424 138.89319,289.33126 137.69845,288.24431 C 137.10109,287.70084 136.61259,287.25572 135.77496,286.60406 C 135.35615,286.27823 134.96551,285.91185 133.67159,285.18696 C 133.02464,284.82451 132.24966,284.30398 130.06183,283.75178 C 127.87401,283.19957 121.6694,282.14423 116.27918,287.55701 C 113.07203,290.69556 111.51088,295.14955 112.05678,299.60357 C 112.60268,304.05759 115.19327,308.00272 119.06345,310.27384 C 119.7757,310.94033 120.58858,311.68637 121.88019,312.95183 C 125.15148,316.1569 129.98569,321.01459 135.96535,327.0419 C 147.92468,339.09652 164.49757,355.90001 183.10064,374.42567 C 199.28665,390.54432 222.47127,392.68834 242.66987,386.76604 C 262.86847,380.84373 281.97067,367.90254 297.93978,351.86671 C 313.90889,335.83087 326.77031,316.67487 332.60834,296.45175 C 338.44637,276.22863 336.20569,253.05315 320.01968,236.9345 C 282.92061,199.9898 253.24069,170.43338 253.24069,170.43338 C 250.52156,167.6512 246.79264,166.08723 242.90239,166.09734 C 239.01212,166.10745 235.29138,167.69077 232.58675,170.48705 L 232.58675,170.48705 z"
id="path2405" />
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:60;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline;opacity:0.87"
d="M 174.22754,229.70669 C 168.70128,235.42039 168.84845,244.5308 174.5564,250.06302 L 249.42498,324.62007 C 253.05344,328.33709 258.40222,329.81247 263.42316,328.48125 C 268.4441,327.15003 272.35931,323.21846 273.66958,318.19201 C 274.97985,313.16556 273.48219,307.82299 269.75006,304.21006 L 194.88148,229.65302 C 192.16235,226.87083 188.43343,225.30687 184.54317,225.31697 C 180.65291,225.32708 176.93217,226.9104 174.22754,229.70669 L 174.22754,229.70669 z"
id="path3179" />
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;stroke-width:124.98212864999999283;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;display:inline;opacity:0.87;color:#000000;fill-rule:evenodd;stroke:none;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;overflow:visible;enable-background:accumulate"
id="path3181"
sodipodi:cx="70"
sodipodi:cy="40"
sodipodi:rx="30"
sodipodi:ry="30"
d="M 100,40 A 30,30 0 1 1 40,40 A 30,30 0 1 1 100,40 z"
transform="matrix(0.3387513,-0.3401668,0.3401668,0.3387513,55.827163,274.10463)" />
<path
transform="matrix(0.3387513,-0.3401668,0.3401668,0.3387513,113.41488,216.27627)"
d="M 100,40 A 30,30 0 1 1 40,40 A 30,30 0 1 1 100,40 z"
sodipodi:ry="30"
sodipodi:rx="30"
sodipodi:cy="40"
sodipodi:cx="70"
id="path3183"
style="fill:#ffffff;fill-opacity:1;stroke-width:124.98212864999999283;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;display:inline;opacity:0.87;color:#000000;fill-rule:evenodd;stroke:none;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;overflow:visible;enable-background:accumulate"
sodipodi:type="arc" />
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;stroke-width:124.98212864999999283;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;display:inline;opacity:0.87;color:#000000;fill-rule:evenodd;stroke:none;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;overflow:visible;enable-background:accumulate"
id="path3185"
sodipodi:cx="70"
sodipodi:cy="40"
sodipodi:rx="30"
sodipodi:ry="30"
d="M 100,40 A 30,30 0 1 1 40,40 A 30,30 0 1 1 100,40 z"
transform="matrix(0.3387513,-0.3401668,0.3401668,0.3387513,172.69636,156.74708)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -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 childs 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);
}

View File

@ -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, were 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 were 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 were 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 <reload>? */
if (STARTS_WITH(command, "reload")) {
load_configuration(conn, NULL, true);
return;
}
/* Is it <restart>? 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 <with>? */
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;
}
/* Its a normal <cmd> */
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 screens 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;
}

View File

@ -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 (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 */
end = strchr(class_title, ' ');
}
if (end == NULL)
die("Malformed assignment, couldn't find terminating quote\n");
*end = '\0';
} else {
/* 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';
}
/* 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);
@ -315,5 +431,13 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath)
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;
}

View File

@ -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;
}

232
src/ipc.c Normal file
View File

@ -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 <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <err.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <ev.h>
#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. Im not sure for what the
* IPC interface will be used in the future, thus Im 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 dont 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;
}

View File

@ -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) wont get decorated */
if (client->dock)
return;
LOG("redecorating child %08x\n", client->child);
if (client_is_floating(client) || client->container->currently_focused == client) {
last_focused = SLIST_FIRST(&(client->workspace->focus_stack));
if (client_is_floating(client)) {
if (last_focused == client)
color = &(config.client.focused);
else color = &(config.client.unfocused);
} else {
if (client->container->currently_focused == client) {
/* Distinguish if the window is currently focused… */
if (client_is_floating(client) || CUR_CELL->currently_focused == 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);
}
/* 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);
}
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");

View File

@ -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);

View File

@ -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,24 +132,24 @@ 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);
@ -201,7 +203,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
height + 2 + 2 + font->height}; /* 2 px border plus fonts 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 dont want to drag & drop if we dont.
* 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 its a dock we cant 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,15 +315,20 @@ 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");
if (assign->floating == ASSIGN_FLOATING_ONLY)
break;
}
@ -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 were 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 its 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 GIMPs 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 were 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);
}

View File

@ -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);

View File

@ -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 were 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 dont 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 its 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;

57
src/workspace.c Normal file
View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <err.h>
#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);
}

104
src/xcb.c
View File

@ -18,6 +18,7 @@
#include <xcb/xcb.h>
#include <xcb/xcb_keysyms.h>
#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,6 +121,7 @@ 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) */
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 dont 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;
}

View File

@ -13,6 +13,7 @@
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <time.h>
#include <xcb/xcb.h>
#include <xcb/xinerama.h>
@ -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,7 +157,13 @@ 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);
/* 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");
@ -194,8 +201,11 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis
free(reply);
if (num_screens == 0) {
LOG("No screens found. This is weird.\n");
exit(1);
LOG("No screens found. This is weird. Trying again...\n");
continue;
}
break;
}
}