Merge branch 'next'

Conflicts:
	src/manage.c
	src/util.c
	src/xinerama.c
This commit is contained in:
Michael Stapelberg 2009-08-19 15:13:27 +02:00
commit ec0113f631
52 changed files with 16997 additions and 986 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,17 +26,21 @@ 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
dist: distclean
[ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION}
[ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2
mkdir i3-${VERSION}
cp DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-* i3.config i3.desktop pseudo-doc.doxygen i3-${VERSION}
cp -r src include man i3-${VERSION}
cp DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop pseudo-doc.doxygen Makefile i3-${VERSION}
cp -r src i3-msg include man i3-${VERSION}
# Only copy toplevel documentation (important stuff)
mkdir i3-${VERSION}/docs
find docs -maxdepth 1 -type f ! -name "*.xcf" -exec cp '{}' i3-${VERSION}/docs \;
sed -e 's/^GIT_VERSION=\(.*\)/GIT_VERSION=${GIT_VERSION}/g;s/^VERSION=\(.*\)/VERSION=${VERSION}/g' Makefile > i3-${VERSION}/Makefile
sed -e 's/^GIT_VERSION=\(.*\)/GIT_VERSION=${GIT_VERSION}/g;s/^VERSION=\(.*\)/VERSION=${VERSION}/g' common.mk > i3-${VERSION}/common.mk
# Pre-generate a manpage to allow distributors to skip this step and save some dependencies
make -C man
cp man/i3.1 i3-${VERSION}/man/i3.1
tar cf i3-${VERSION}.tar i3-${VERSION}
bzip2 -9 i3-${VERSION}.tar
rm -rf i3-${VERSION}
@ -108,6 +49,8 @@ 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
$(MAKE) TOPDIR=$(TOPDIR) -C i3-msg distclean

41
RELEASE-NOTES-3.c Normal file
View File

@ -0,0 +1,41 @@
Release notes for i3 v3.γ
-----------------------------
This is the third version (3.γ, transcribed 3.c) of i3. It is considered stable.
This release contains many small improvements like using keysymbols in the
configuration file, named workspaces, borderless windows, an IPC interface
etc. (see below for a complete list of changes)
Thanks for this release go out to bapt, badboy, Atsutane, tsdh, xeen, mxf,
and all other people who reported bugs/made suggestions.
Special thanks go to steckdenis, yellowiscool and farvardin who designed a logo
for i3.
A list of changes follows:
* Implement a reload command
* Implement keysymbols in configuration file
* Implement assignments of workspaces to screens
* Implement named workspaces
* Implement borderless/1-px-border windows
* Implement command to focus screens
* Implement IPC via unix sockets
* Correctly render decoration of floating windows
* Map floating windows requesting (0x0) to center of their leader/workspace
* 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
* Bugfix: Fix screen wrapping
* Bugfix: Fix selecting a different screen with your mouse when not having
any windows on the current workspace
* Bugfix: Correctly unmap stack windows and dont re-map them too early
* Bugfix: Allow switching layout if there are no clients in the this container
* Bugfix: Set WM_STATE_WITHDRAWN when unmapping, unmap windows when
destroying
* Bugfix: Dont hide assigned clients to inactive but visible workspaces
-- Michael Stapelberg, 2009-08-19

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

27
debian/changelog vendored
View File

@ -1,3 +1,30 @@
i3-wm (3.c-1) unstable; urgency=low
* Implement a reload command
* Implement keysymbols in configuration file
* Implement assignments of workspaces to screens
* Implement named workspaces
* Implement borderless/1-px-border windows
* Implement command to focus screens
* Implement IPC via unix sockets
* Correctly render decoration of floating windows
* Map floating windows requesting (0x0) to center of their leader/workspace
* 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
* Bugfix: Fix screen wrapping
* Bugfix: Fix selecting a different screen with your mouse when not having
any windows on the current workspace
* Bugfix: Correctly unmap stack windows and dont re-map them too early
* Bugfix: Allow switching layout if there are no clients in the this container
* Bugfix: Set WM_STATE_WITHDRAWN when unmapping, unmap windows when
destroying
* Bugfix: Dont hide assigned clients to inactive but visible workspaces
-- Michael Stapelberg <michael@stapelberg.de> Wed, 19 Aug 2009 13:07:58 +0200
i3-wm (3.b-1) unstable; urgency=low
* Bugfix: Correctly handle col-/rowspanned containers when setting focus.

2
debian/control vendored
View File

@ -3,7 +3,7 @@ Section: utils
Priority: extra
Maintainer: Michael Stapelberg <michael@stapelberg.de>
DM-Upload-Allowed: yes
Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4-1), xmlto, docbook-xml, pkg-config, libev-dev
Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev
Standards-Version: 3.8.2
Homepage: http://i3.zekjur.net/

510
docs/GPN-2009-06-27/i3.tex Normal file
View File

@ -0,0 +1,510 @@
%
% © 2009 Michael Stapelberg
%
% 2009-06-24
%
\documentclass[mode=print,paper=screen,style=jefka]{powerdot}
\usepackage[utf8]{inputenc}
\usepackage{graphicx}
\usepackage{float}
\usepackage{ngerman}
\usepackage{url}
\usepackage{listings}
\newcommand{\bs}{\textbackslash}
\pdsetup{palette=white}
\definecolor{darkblue}{rgb}{0,0,.6}
\definecolor{darkred}{rgb}{.6,0,0}
\definecolor{darkgreen}{rgb}{0,.6,0}
\definecolor{darkgray}{gray}{.3}
\definecolor{lightblue}{rgb}{0.97,0.99,1}
\lstloadlanguages{C}
\lstdefinestyle{colors}{keywordstyle={\bf\color{darkblue}}, commentstyle={\em\color{magenta}}, stringstyle={\color{darkred}},%
emphstyle={\color{darkgray}}}
\lstnewenvironment{code}{%
\lstset{frame=single, basicstyle=\footnotesize\ttfamily, language=C, showstringspaces=false,%
style=colors, numbers=left, morekeywords={xcb_get_window_attributes_cookie_t, xcb_map_request_event_t,%
xcb_connection_t, xcb_get_window_attributes_reply_t, window_attributes_t, xcb_intern_atom_cookie_t,%
xcb_intern_atom_reply_t, xcb_atom_t, uint32_t, uint16_t, foreach, UINT_MAX, NULL},%
moreemph={xcb_get_window_attributes_reply, xcb_get_window_attributes_unchecked, manage_window,%
add_ignore_event, xcb_intern_atom, xcb_intern_atom_reply, fprintf, printf, free, load_configuration,%
XInternAtom, exit, strlen, xcb_change_window_attributes, xcb_event_wait_for_event_loop,%
xcb_event_set_key_press_handler, xcb_property_set_handler}}
}{}
\newcommand{\isrc}[1]{\begin{center} \footnotesize\ttfamily Siehe auch: #1 \end{center}}
\title{Hacking your own window manager}
\author{sECuRE auf der GPN 8\\
~\\
powered by \LaTeX, of course}
\begin{document}
\maketitle
\begin{slide}{Dieser Vortrag}
\begin{list}{$\bullet$}{\itemsep=.5em}
\item Geschichte/Einführung in Window Manager
\item Merkmale von i3
\item Window Manager und X11
%
% zuerst: wie funktioniert ein client?
%
% WM ist nur ein weiterer Client
% Keine Rechteverwaltung, prinzipiell darf jeder Fenster schubsen
% Clients können Events abfangen, der WM macht das halt für das root-fenster
\item Arbeitsumgebung
\item XCB
\item Setup
\item Reparenting (Window Decorations)
%\item fake\_configure\_notify
%\item Colorpixel
%\item UTF-8
% irgendwo da erwähnen: fenster in eine hashtable aufnehmen
\item Events
% (die kriegt man natürlich nur wenn man redirectmask gesetzt hat:)
% MapRequest
% ConfigureRequest
\item Hints (Titel, Klassen, Größen, …)
% Atoms
% NetWM
% - NET_WM_WINDOW_TYPE
% - NET_WM_NAME
% - in kombination mit NET_SUPPORTING_WM_CHECK auf dem rootfenster
% - NET_WM_STRUT_PARTIAL
% ICCCM
% - Normal hints / size hints (warum zwei namen?)
% - Aspect ratio, wichtig z.B. für mplayer
% - min/max size, interessant primär für floating
% - WM_NAME
% - WM_TRANSIENT_FOR
% - WM_CLASS
\item Gotchas
% flush()
% WM_STATE_NORMAL und drag&drop in gtk-apps
\item Zusammenfassung
% TODO
\end{list}
\end{slide}
\begin{slide}{Geschichte/Einführung}
\begin{list}{$\bullet$}{\itemsep=1em}
\item<1-> „All window managers suck, this one just sucks less”?
\item<2-> Desktop environment vs. window manager (GNOME, KDE, Xfce, …)
\item<3-> Stacking (e17, fluxbox, IceWM, fvwm, …) vs Tiling (dwm, wmii, xmonad, …)
\item<4-> dwm, awesome, xmonad, …: statisches Layout
% gedanke: man braucht sich nicht mal mehr um das layout kümmern
\item<5-> wmii, ion: dynamisches layout
\item<6-> Probleme an ion: tuomov (Lizenz, Kommunikation), Config, Look and feel, Code
\item<7-> Probleme an wmii: Xinerama-support, Xlib, undokumentierter Code, nur Spalten, keine Reihen, Kleinigkeiten (titellose Fenster)
\end{list}
\end{slide}
\begin{slide}{Merkmale von i3}
\begin{list}{$\bullet$}{\itemsep=1em}
\item<1-> gut lesbarer, dokumentierter Code. Dokumentation.
\item<2-> XCB anstelle von Xlib
\item<3-> Xinerama done right™
\item<4-> Spalten und Zeilen, Tabelle als Basis
\item<5-> command-mode, wie in vim
\item<6-> UTF-8 clean
\item<7-> kein Antialiasing, schlank und schnell bleiben
\end{list}
\end{slide}
\begin{slide}{Typische Kommunikation mit X}
\begin{list}{$\bullet$}{\itemsep=1em}
\item<1-> Verbindung aufbauen
\item<2-> Requests über die Leitung schicken (Fenster erzeugen)
\begin{list}{$\bullet$}{\itemsep=1em}
\item Cookie für jeden Request
\item Antwort für spezifisches Cookie abholen
\item $\Rightarrow$ Asynchronität nutzbar
\end{list}
\item<3-> Eventloop starten, reagieren (Fenster zeichnen, Eingaben, …)
\end{list}
\end{slide}
\begin{slide}{Was genau macht ein WM?}
\begin{list}{$\bullet$}{\itemsep=1em}
\item<1-> Events umlenken
\item<2-> Neue Fenster anzeigen/positionieren (MapRequest)
\item<3-> Titelleisten malen (reparenting)
\item<4-> Den Fokus verwalten
\item<5-> Mit Hints umgehen (Fenstertitel, Fullscreen, Dock, …)
\item<6-> Auf Benutzereingaben reagieren
\end{list}
\end{slide}
\begin{slide}[method=direct]{Window Manager und X11 (1)}
\includegraphics[width=1\textwidth]{xserver_konzept.eps}
\end{slide}
\begin{slide}{Window Manager und X11 (2)}
\begin{list}{$\bullet$}{\itemsep=1em}
\item<1-> Keine Rechteaufteilung, prinzipiell kann jeder Fenster managen
\item<2-> Window Manager verantwortlich für alle Kinder das Root-Fensters
\item<3-> RedirectMask, lässt sich Events des Root-Fensters schicken
\item<4-> Setzt hints auf dem Root-Fenster
\end{list}
\end{slide}
\begin{slide}{Arbeitsumgebung}
\begin{list}{$\bullet$}{\itemsep=1em}
\item X sinnvoll beim Entwickeln $\Rightarrow$ anderen Computer verwenden oder Xephyr
\item xtrace dazwischenschalten (sowohl zwischen WM und X11 als auch zwischen Clients und X11 sinnvoll)\\
\texttt{DISPLAY=:1 xtrace -o /tmp/xtrace.log -n :9}
\item \texttt{xprop} zeigt Hints an, \texttt{xwininfo} gibt Struktur aus
\item als ersten Client ein Terminal starten $\Rightarrow$ wenn der WM crashed lebt
die X-Session noch\\
\texttt{DISPLAY=:1 urxvt \&}
\item Debugger, strace, logfiles, core-dumps aktivieren\\
(Siehe auch \url{http://i3.zekjur.net/docs/debugging.html})
\end{list}
\end{slide}
\begin{slide}{XCB}
\begin{list}{$\bullet$}{\itemsep=1em}
\item \url{http://xcb.freedesktop.org/}
\item<1-> „X-protocol C-language Binding”
\item<2-> Klein, wartbar (aus einer Protokollbeschreibung auto-generiert)
\item<3-> Sinnvoll benannte Funktionsnamen und Datentypen
\item<4-> Nutzt die Asynchronität von X aus
\item<5-> Allerdings: Sehr spärlich dokumentiert, man muss mit Xlib-Doku arbeiten
\item<6-> xcb-util: XCB noch mal ein bisschen gekapselt, nützliche Funktionen abstrahiert
\end{list}
\end{slide}
\begin{slide}[method=direct]{Xlib-Beispielcode}
\begin{code}
char *names[10] = {"_NET_SUPPORTED", "_NET_WM_STATE",
"_NET_WM_STATE_FULLSCREEN", "_NET_WM_NAME" /* ... */};
Atom atoms[10];
/* Get atoms */
for (int i = 0; i < 10; i++) {
atoms[i] = XInternAtom(display, names[i], 0);
}
\end{code}
\end{slide}
\begin{slide}[method=direct]{XCB-Beispielcode}
\begin{code}
char *names[10] = {"_NET_SUPPORTED", "_NET_WM_STATE",
"_NET_WM_STATE_FULLSCREEN", "_NET_WM_NAME" /* ... */};
xcb_intern_atom_cookie_t cookies[10];
/* Place requests for atoms as soon as possible */
for (int c = 0; c < 10; c++)
cookies[c] = xcb_intern_atom(connection, 0,
strlen(names[c]), names[c]);
/* Do other stuff here */
load_configuration();
/* Get atoms */
for (int c = 0; c < 10; c++) {
xcb_intern_atom_reply_t *reply =
xcb_intern_atom_reply(connection, cookies[c], NULL);
if (!reply) {
fprintf(stderr, "Could not get atom\n");
exit(-1);
}
printf("atom has ID %d\n", reply->atom);
free(reply);
}
\end{code}
\end{slide}
\begin{slide}[method=direct]{Setup}
\begin{code}
get_atoms();
xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
xcb_property_set_handler(&prophs, WM_TRANSIENT_FOR, UINT_MAX,
handle_transient_for, NULL);
xcb_grab_key(conn, 0, root, modifier, keycode,
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
xcb_grab_key(conn, 0, root, modifier | xcb_numlock_mask, keycode,
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
XCB_EVENT_MASK_STRUCTURE_NOTIFY |
XCB_EVENT_MASK_PROPERTY_CHANGE |
XCB_EVENT_MASK_ENTER_WINDOW };
xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, values);
manage_existing_windows();
xcb_event_wait_for_event_loop(&evenths);
\end{code}
\isrc{i3/src/mainx.c:370ff}
\end{slide}
\begin{slide}[method=direct]{Reparenting}
\includegraphics[width=1\textwidth]{reparenting.eps}
\begin{enumerate}
\item (App) Fenster wird konfiguriert (Position, Größe, …)
\item (App) MapRequest
\item (WM) Window Manager erstellt eigenes Fenster
\item (WM) Reparent = neues Fenster kriegt statt root das WM-Fenster als parent
\item (WM) Mappen des neuen Fensters
\end{enumerate}
\end{slide}
\begin{slide}[method=direct]{fake\_configure\_notify}
\begin{list}{$\bullet$}{\itemsep=.5em}
\item (Alte) Reparented clients kriegen nichts mit, denken relativ zum root-Fenster
\item $\Rightarrow$ Window Manager tut so, als würde das Fenster neu konfiguriert, sendet den Event mit absoluten statt relativen Koordinaten
\item Sieht man sehr gut an \texttt{xfontsel} und anderen Anwendungen, die Xaw (X Athena widget set) verwenden
\end{list}
\begin{code}
xcb_configure_notify_event_t generated_event;
generated_event.window = window;
generated_event.response_type = XCB_CONFIGURE_NOTIFY;
generated_event.x = r.x;
/* ... */
generated_event.override_redirect = false;
xcb_send_event(conn, false, window,
XCB_EVENT_MASK_STRUCTURE_NOTIFY,
(char*)&generated_event);
\end{code}
\isrc{i3/src/xcb.c:193ff}
\end{slide}
\begin{slide}[method=direct]{Events: button\_press}
\begin{list}{$\bullet$}{\itemsep=.5em}
\item Aktiv grabben, die Anwendung soll keinen Klick bekommen, wenn der Nutzer das Fenster verschiebt
\end{list}
\begin{code}
int handle_button_press(void *ignored, xcb_connection_t *conn,
xcb_button_press_event_t *event) {
/* ... */
if ((event->state & XCB_MOD_MASK_1) != 0)
floating_drag_window(conn, client, event);
/* ... */
if (event->detail == XCB_BUTTON_INDEX_4 ||
event->detail == XCB_BUTTON_INDEX_5) {
LOG("User scrolled\n");
return 1;
}
/* if unhandled, forward the click to the application */
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
return 1;
}
\end{code}
\isrc{i3/src/handlers.c:148ff}
\end{slide}
\begin{slide}[method=direct]{Events: enter\_notify}
\begin{list}{$\bullet$}{\itemsep=.5em}
\item Der Mauszeiger ist über dem Fenster gelandet
\item Auch unabsichtlich: Wenn das Fenster unter den Mauszeiger konfiguriert wird
\item $\Rightarrow$ Blacklist an Events, die man ignorieren muss
\end{list}
\begin{code}
int handle_enter_notify(void *ignored, xcb_connection_t *conn,
xcb_enter_notify_event_t *event) {
if (event_is_ignored(event->sequence))
return 1;
Client *client = table_get(&by_parent, event->event);
if (client == NULL) {
return 1; /* user moved cursor to another screen */
}
set_focus(conn, client, false);
return 1;
}
\end{code}
\isrc{i3/src/handlers.c:148ff}
\end{slide}
\begin{slide}[method=direct]{Events: key\_press }
\begin{list}{$\bullet$}{\itemsep=.5em}
\item Aktives key grabbing: WM entscheidet, ob Tastendruck weitergeht, also bei der Anwendung ankommt (kann abfangen)
\item Passives key grabbing: WM kriegt einen event
\end{list}
\begin{code}
uint16_t state_filtered =
event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
state_filtered &= 0xFF; /* filter mouse buttons */
foreach (binding) {
if (binding->keycode == event->detail &&
binding->mods == state_filtered) {
/* do fancy stuff here */
break;
}
}
\end{code}
\isrc{i3/src/handlers.c:100ff}
\end{slide}
\begin{slide}[method=direct]{Events: key\_press (2), Mode\_switch }
\begin{list}{$\bullet$}{\itemsep=.25em}
\item \texttt{event->state} enthält nie das Mode\_switch-Bit, Bug in X
\item XKB hilft, den korrekten state zu ermitteln
\item $\Rightarrow$ Mode\_switch nicht als modifier in \texttt{xcb\_grab\_key} verwendbar
\item $\Rightarrow$ wir grabben alle keys aktiv (!) und filtern selbst nach Mode\_switch
\end{list}
\begin{code}
/* ... state_filtered is already cleaned */
XkbStateRec state;
if (XkbGetState(xkbdpy, XkbUseCoreKbd, &state) == Success &&
(state.group+1) == 2)
state_filtered |= BIND_MODE_SWITCH;
foreach (binding)
if (binding->keycode == event->detail &&
binding->mods == state_filtered) {
xcb_allow_events(conn, SyncKeyboard, event->time);
return; /* after doing actual stuff, of course */
}
xcb_allow_events(conn, ReplayKeyboard, event->time);
\end{code}
\isrc{i3/src/handlers.c:100ff}
\end{slide}
\begin{slide}[method=direct]{Umlaute und Sonderzeichen}
\includegraphics[width=.5\textwidth]{xft.eps}
\begin{list}{$\bullet$}{\itemsep=.1em}
\item Verschiedene APIs fürs Rendern von Text: X Core Fonts und xft
\item xft = X FreeType, antialiased fonts, Pango, GTK
\item Problem mit X Core Fonts: keine Sonderzeichen
\item …oder? \texttt{misc-fixed-*-iso10646}, also X Core Fonts mit Universal Character Set (= Unicode-Zeichen). Nicht 100\% vollständig
\item urxvt: benutzt beide APIs, pro Glyph unterschiedlich
\item Trend geht leider zu fontconfig/xft :-(
\end{list}
\end{slide}
\begin{slide}[method=direct]{Umlaute und Sonderzeichen (2)}
\begin{list}{$\bullet$}{\itemsep=.5em}
\item X hat eigenes Encoding: Compound Text
\item Früher ICCCM (Compound text, z.B. Atom WM\_NAME)\\
ICCCM = Inter-Client Communication Conventions Manual
\item heute EWMH (UTF-8, z.B. Atom \_NET\_WM\_NAME)\\
EWMH = Extended Window Manager Hints (= NetWM)
\item XImageText16 (bzw xcb\_image\_text\_16) erwartet UCS-2\\
$\Rightarrow$ \texttt{iconv\_open(UCS2\_BE, UTF-8)}
\end{list}
\isrc{i3/src/util.c:191ff, i3/src/handlers.c:663ff}
\end{slide}
\begin{slide}[method=direct]{Colorpixel}
\begin{list}{$\bullet$}{\itemsep=.5em}
\item Heutzutage: TrueColor. Früher: 8 bit o.ä.
\item Colormaps: Geben an welche Farben die Hardware kann
\item Colorpixel: Ein Wert aus der Colormap, der der gewünschten Farbe am nähesten kommt
\item Bei TrueColor: \texttt{return (red << 16) + (green << 8) + blue;}
\item Alles andere: Round-Trip zum X-Server:
\end{list}
\begin{code}
#define RGB_8_TO_16(i) (65535 * ((i) & 0xFF) / 255)
xcb_alloc_color_reply_t *reply;
reply = xcb_alloc_color_reply(conn, xcb_alloc_color(conn,
root_screen->default_colormap, RGB_8_TO_16(red),
RGB_8_TO_16(green), RGB_8_TO_16(blue)), NULL);
if (!reply)
die("Could not allocate color\n");
return reply->pixel;
\end{code}
\isrc{i3/src/xcb.c:76ff}
\end{slide}
\begin{slide}[method=direct]{Hints}
\begin{list}{$\bullet$}{\itemsep=.5em}
\item NetWM
\begin{description}
\item[NET\_WM\_WINDOW\_TYPE] dock, dialog, utility, toolbar, splashscreen
\item[NET\_WM\_NAME] Fenstertitel (UTF-8), auch auf dem root-Fenster
\item[NET\_WM\_STRUT\_PARTIAL] Reservierter Bereich am Bildschirmrand (Docks), z.B. für dzen2
\end{description}
\item ICCCM
\begin{description}
\item[WM\_NAME] Fenstertitel (Compound Text)
\item[WM\_TRANSIENT\_FOR] Zugehöriges, "`temporäres"' Fenster für Anwendung X ($\Rightarrow$ floating)
\item[WM\_CLASS] Fensterklasse (z.B. "`urxvt"'), praktisch zum identifizieren
\item[WM\_NORMAL\_HINTS] (Size hints), beinhaltet Aspect Ratio (mplayer!), minimale und maximale Größe
\end{description}
\end{list}
\end{slide}
\begin{slide}[method=direct]{Hints (2)}
\begin{code}
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)
{
xcb_window_t transient_for;
if (reply != NULL) {
if (!xcb_get_wm_transient_for_from_reply(&transient_for, reply)) {
LOG("Not transient for any window\n");
return 1;
}
} else {
if (!xcb_get_wm_transient_for_reply(conn,
xcb_get_wm_transient_for_unchecked(conn, window),
&transient_for, NULL)) {
LOG("Not transient for any window\n");
return 1;
}
}
if (client->floating == FLOATING_AUTO_OFF)
toggle_floating_mode(conn, client, true);
return 1;
}
\end{code}
\end{slide}
\begin{slide}[method=direct]{Gotchas}
\begin{list}{$\bullet$}{\itemsep=.5em}
\item Flushing (\texttt{xcb\_flush(connection);})
\item \texttt{WM\_STATE} != \texttt{WM\_STATE\_NORMAL}
\item Eventloops / Caching von xcb (GIMP splash screen)
\end{list}
\end{slide}
\begin{slide}{Zusammenfassung}
\begin{list}{$\bullet$}{\itemsep=.5em}
\item Bindings aufsetzen, Eventmask konfigurieren
\item Events/Hints abarbeiten
\item Decorations zeichnen
\end{list}
\end{slide}
\begin{slide}{Lust bekommen?}
\begin{list}{$\bullet$}{\itemsep=1em}
\item git clone \url{git://code.stapelberg.de/i3}
\item development branch: \texttt{git checkout --track -b next origin/next}
\item Debian: \texttt{apt-get install i3-wm/unstable}
\item non-Debian: \texttt{cd i3; cat DEPENDS; make \&\& sudo make install}
\item in \~{}/.xsession: \texttt{exec /usr/bin/i3}
\item Siehe manpage \texttt{i3(1)}, users guide, how to hack
\end{list}
\end{slide}
\begin{slide}{exit(0);}
\begin{list}{$\bullet$}{\itemsep=1em}
\item git-webinterface: \url{http://code.stapelberg.de/git/i3}
\item Website: \url{http://i3.zekjur.net}
\item IRC: \#i3 auf irc.twice-irc.de
\item xcb: \url{http://xcb.freedesktop.org/}
\item 50-Zeilen-WM: \url{http://incise.org/tinywm.html}
\item „Why X is not our ideal window system”: \url{http://www.std.org/~msm/common/WhyX.pdf}
\item …noch Fragen?
\end{list}
\end{slide}
\end{document}

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

File diff suppressed because it is too large Load Diff

1708
docs/GPN-2009-06-27/xft.eps Normal file

File diff suppressed because it is too large Load Diff

BIN
docs/GPN-2009-06-27/xft.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -127,6 +127,9 @@ src/handlers.c::
Contains all handlers for all kind of X events (new window title, new hints,
unmapping, key presses, button presses, …).
src/ipc.c::
Contains code for the IPC interface.
src/layout.c::
Renders your layout (screens, workspaces, containers).
@ -149,6 +152,9 @@ Manages the most important internal data structure, the design table.
src/util.c::
Contains useful functions which are not really dependant on anything.
src/workspace.c::
Contains all functions related to workspaces (displaying, hiding, renaming…)
src/xcb.c::
Contains wrappers to use xcb more easily.

View File

@ -1,7 +1,7 @@
i3 Users Guide
===============
Michael Stapelberg <michael+i3@stapelberg.de>
June 2009
August 2009
This document contains all information you need to configuring and using the i3
window manager. If it does not, please contact me on IRC, Jabber or E-Mail and
@ -174,24 +174,40 @@ font::
=== Keyboard bindings
You can use each command (see below) using keyboard bindings. At the moment,
keyboard bindings require you to specify the keycode (38) of the key, not its key
symbol ("a"). This has some advantages (keybindings make sense regardless of
the layout you type) and some disadvantages (hard to remember, you have to look
them up every time).
A keyboard binding makes i3 execute a command (see below) upon pressing a
specific key. i3 allows you to bind either on keycodes or on keysyms (you can
also mix your bindings, though i3 will not protect you from overlapping ones).
* A keysym (key symbol) is a description for a specific symbol, like "a" or "b",
but also more strange ones like "underscore" instead of "_". These are the ones
you also use in Xmodmap to remap your keys. To get the current mapping of your
keys, use +xmodmap -pke+.
* Keycodes however do not need to have a symbol assigned (handy for some hotkeys
on some notebooks) and they will not change their meaning as you switch to a
different keyboard layout.
My recommendation is: If you often switch keyboard layouts because you try to
learn a different one, but you want to keep your bindings at the same place,
use keycodes. If you dont switch layouts and like a clean and simple config
file, use keysyms.
*Syntax*:
--------------------------------
----------------------------------
bindsym [Modifiers+]keysym command
bind [Modifiers+]keycode command
--------------------------------
----------------------------------
*Examples*:
--------------------------------
# Fullscreen
bind Mod1+41 f
bindsym Mod1+f f
# Restart
bind Mod1+Shift+27 restart
bindsym Mod1+Shift+r restart
# Notebook-specific hotkeys
bind 214 exec /home/michael/toggle_beamer.sh
--------------------------------
Available Modifiers:
@ -241,7 +257,7 @@ set name value
*Examples*:
------------------------
set $m Mod1
bind $m+Shift+27 restart
bindsym $m+Shift+r restart
------------------------
Variables are directly replaced in the file when parsing, there is no fancy
@ -259,13 +275,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 +290,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
@ -292,51 +310,56 @@ exec command
exec sudo i3status | dzen2 -dock
--------------------------------
=== Jumping to specific windows
=== Automatically putting workspaces on specific screens
Especially when in a multi-monitor environment, you want to quickly jump to a specific
window, for example while currently working on workspace 3 you may want to jump to
your mailclient to mail your boss that youve achieved some important goal. Instead
of figuring out how to navigate to your mailclient, it would be more convenient to
have a shortcut.
If you use the assigning of clients to workspaces and start some clients
automatically, it might be handy to put the workspaces on specific screens.
Also, the assignment of workspaces to screens will determine the workspace
which i3 uses for a new screen when adding screens or when starting (e.g., by
default it will use 1 for the first screen, 2 for the second screen and so on).
*Syntax*:
----------------------------------------------------
jump ["]window class[/window title]["]
jump workspace [ column row ]
----------------------------------------------------
----------------------------------
workspace <number> screen <screen>
----------------------------------
You can either use the same matching algorithm as in the +assign+ command (see above)
or you can specify the position of the client if you always use the same layout.
Screen can be either a number (starting at 0 for the first screen) or a
position. When using numbers, it is not guaranteed that your screens always
get the same number. Though, unless you upgrade your X server or drivers, the
order usually stays the same. When using positions, you have to specify the
exact pixel where the screen *starts*, not a pixel which is contained by the
screen. Thus, if your first screen has the dimensions 1280x800, you can match
the second screen right of it by specifying 1280. You cannot use 1281.
*Examples*:
--------------------------------------
# Get me to the next open VIM instance
bind Mod1+38 jump "urxvt/VIM"
--------------------------------------
---------------------------
workspace 1 screen 0
workspace 5 screen 1
=== Traveling the focus stack
workspace 1 screen 1280
workspace 2 screen x800
workspace 3 screen 1280x800
---------------------------
This mechanism can be thought of as the opposite of the +jump+ command. It travels
the focus stack and jumps to the window you focused before.
=== Named workspaces
If you always have a certain arrangement of workspaces, you might want to give
them names (of course UTF-8 is supported):
*Syntax*:
--------------
focus [number] | floating | tilling | ft
--------------
---------------------------------------
workspace <number> <name>
workspace <number> screen <screen> name
---------------------------------------
Where +number+ by default is 1 meaning that the next client in the focus stack will
be selected.
For more details about the screen-part of this command, see above.
The special values have the following meaning:
floating::
The next floating window is selected.
tiling::
The next tiling window is selected.
ft::
If the current window is floating, the next tiling window will be selected
and vice-versa.
*Examples*:
--------------------------
workspace 1 www
workspace 2 work
workspace 3 i ♥ workspaces
--------------------------
=== Changing colors
@ -369,3 +392,171 @@ Colors are in HTML hex format, see below.
# class border backgr. text
client.focused #2F343A #900000 #FFFFFF
--------------------------------------
=== Interprocess communication
i3 uses unix sockets to provide an IPC interface. At the moment, this interface
is only useful for sending commands. To enable it, you have to configure a path
where the unix socket will be stored. The default path is +/tmp/i3-ipc.sock+.
*Examples*:
----------------------------
ipc-socket /tmp/i3-ipc.sock
----------------------------
You can then use the i3-msg command to perform any command listed in the next
section.
== List of commands
=== Manipulating layout
To change the layout of the current container to stacking or back to default
layout, use +s+ or +d+. To make the current client (!) fullscreen, use +f+, to
make it floating (or tiling again) use +t+:
*Examples*:
--------------
bindsym Mod1+s s
bindsym Mod1+l d
# Toggle fullscreen
bindsym Mod1+f f
# Toggle floating/tiling
bindsym Mod1+space t
--------------
=== Focussing/Moving/Snapping clients/containers/screens
To change the focus, use one of the +h+, +j+, +k+ and +l+ commands, meaning
respectively left, down, up, right. To focus a container, prefix it with +wc+,
to focus a screen, prefix it with +ws+.
The same principle applies for moving and snapping, just prefix the command
with +m+ when moving and with +s+ when snapping:
*Examples*:
----------------------
# Focus clients on the left, bottom, top, right:
bindsym Mod1+j h
bindsym Mod1+k j
bindsym Mod1+l k
bindsym Mod1+semicolon l
# Move client to the left, bottom, top, right:
bindsym Mod1+j mh
bindsym Mod1+k mj
bindsym Mod1+l mk
bindsym Mod1+semicolon ml
# Snap client to the left, bottom, top, right:
bindsym Mod1+j sh
bindsym Mod1+k sj
bindsym Mod1+l sk
bindsym Mod1+semicolon sl
# Focus container on the left, bottom, top, right:
bindsym Mod3+j wch
----------------------
=== Changing workspaces/moving clients to workspaces
To change to a specific workspace, the command is just the number of the
workspace, e.g. +1+ or +3+. To move the current client to a specific workspace,
prefix the number with an +m+.
Furthermore, you can switch to the next and previous workspace with the
commands +nw+ and +pw+, which is handy for example if you have workspace
1, 3, 4 and 9 and you want to cycle through them with a single key combination.
*Examples*:
-------------------------
bindsym Mod1+1 1
bindsym Mod1+2 2
...
bindsym Mod1+Shift+1 m1
bindsym Mod1+Shift+2 m2
...
bindsym Mod1+o nw
bindsym Mod1+p pw
-------------------------
=== Jumping to specific windows
Especially when in a multi-monitor environment, you want to quickly jump to a specific
window, for example while currently working on workspace 3 you may want to jump to
your mailclient to mail your boss that youve achieved some important goal. Instead
of figuring out how to navigate to your mailclient, it would be more convenient to
have a shortcut.
*Syntax*:
----------------------------------------------------
jump ["]window class[/window title]["]
jump workspace [ column row ]
----------------------------------------------------
You can either use the same matching algorithm as in the +assign+ command (see above)
or you can specify the position of the client if you always use the same layout.
*Examples*:
--------------------------------------
# Get me to the next open VIM instance
bindsym Mod1+a jump "urxvt/VIM"
--------------------------------------
=== Traveling the focus stack
This mechanism can be thought of as the opposite of the +jump+ command. It travels
the focus stack and jumps to the window you focused before.
*Syntax*:
--------------
focus [number] | floating | tilling | ft
--------------
Where +number+ by default is 1 meaning that the next client in the focus stack will
be selected.
The special values have the following meaning:
floating::
The next floating window is selected.
tiling::
The next tiling window is selected.
ft::
If the current window is floating, the next tiling window will be selected
and vice-versa.
=== Changing border style
To change the border of the current client, you can use +bn+ to use the normal
border (including window title), +bp+ to use a 1-pixel border (no window title)
and +bb+ to make the client borderless.
*Examples*:
------------------
bindsym Mod1+t bn
bindsym Mod1+y bp
bindsym Mod1+u bb
------------------
=== Reloading/Restarting/Exiting
You can make i3 reload its configuration file with +reload+. You can also
restart i3 inplace with the +restart+ command to get it out of some weird state
(if that should ever happen) or to perform an upgrade without having to restart
your X session. However, your layout is not preserved at the moment, meaning
that all open windows will be in a single container in default layout. To exit
i3 properly, you can use the +exit+ command, however you dont need to (e.g.,
simply killing your X session is fine aswell).
*Examples*:
----------------------------
bindsym Mod1+Shift+r restart
bindsym Mod1+Shift+w reload
bindsym Mod1+Shift+e exit
----------------------------

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

@ -16,15 +16,17 @@
#define _CLIENT_H
/**
* Removes the given client from the container, either because it will be inserted into another
* one or because it was unmapped
* Removes the given client from the container, either because it will be
* inserted into another one or because it was unmapped
*
*/
void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container, bool remove_from_focusstack);
void client_remove_from_container(xcb_connection_t *conn, Client *client,
Container *container,
bool remove_from_focusstack);
/**
* Warps the pointer into the given client (in the middle of it, to be specific), therefore
* selecting it
* Warps the pointer into the given client (in the middle of it, to be
* specific), therefore selecting it
*
*/
void client_warp_pointer_into(xcb_connection_t *conn, Client *client);
@ -36,8 +38,8 @@ void client_warp_pointer_into(xcb_connection_t *conn, Client *client);
void client_kill(xcb_connection_t *conn, Client *window);
/**
* Checks if the given window class and title match the given client
* Window title is passed as "normal" string and as UCS-2 converted string for
* Checks if the given window class and title match the given client Window
* title is passed as "normal" string and as UCS-2 converted string for
* matching _NET_WM_NAME capable clients as well as those using legacy hints.
*
*/
@ -52,27 +54,55 @@ bool client_matches_class_name(Client *client, char *to_class, char *to_title,
void client_enter_fullscreen(xcb_connection_t *conn, Client *client);
/**
* Toggles fullscreen mode for the given client. It updates the data structures and
* reconfigures (= resizes/moves) the client and its frame to the full size of the
* screen. When leaving fullscreen, re-rendering the layout is forced.
* Toggles fullscreen mode for the given client. It updates the data
* structures and reconfigures (= resizes/moves) the client and its frame to
* the full size of the screen. When leaving fullscreen, re-rendering the
* layout is forced.
*
*/
void client_toggle_fullscreen(xcb_connection_t *conn, Client *client);
/**
* Sets the position of the given client in the X stack to the highest (tiling layer is always
* on the same position, so this doesnt matter) below the first floating client, so that
* floating windows are always on top.
* Sets the position of the given client in the X stack to the highest (tiling
* layer is always on the same position, so this doesnt matter) below the
* first floating client, so that floating windows are always on top.
*
*/
void client_set_below_floating(xcb_connection_t *conn, Client *client);
/**
* Returns true if the client is floating. Makes the code more beatiful, as floating
* is not simply a boolean, but also saves whether the user selected the current state
* or whether it was automatically set.
* Returns true if the client is floating. Makes the code more beatiful, as
* floating is not simply a boolean, but also saves whether the user selected
* the current state or whether it was automatically set.
*
*/
bool client_is_floating(Client *client);
/**
* Change the border type for the given client to normal (n), 1px border (p) or
* completely borderless (b).
*
*/
void client_change_border(xcb_connection_t *conn, Client *client, char border_type);
/**
* Unmap the client, correctly setting any state which is needed.
*
*/
void client_unmap(xcb_connection_t *conn, Client *client);
/**
* Map the client, correctly restoring any state needed.
*
*/
void client_map(xcb_connection_t *conn, Client *client);
/**
* Pretty-prints the clients information into the logfile.
*
*/
#define CLIENT_LOG(client) do { \
LOG("Window: frame 0x%08x, child 0x%08x\n", client->frame, client->child); \
} while (0)
#endif

View File

@ -13,10 +13,8 @@
#include <xcb/xcb.h>
bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction);
/** Switches to the given workspace */
void show_workspace(xcb_connection_t *conn, int workspace);
bool focus_window_in_container(xcb_connection_t *conn, Container *container,
direction_t direction);
/** Parses a command, see file CMDMODE for more information */
void parse_command(xcb_connection_t *conn, const char *command);

View File

@ -15,17 +15,28 @@
#ifndef _CONFIG_H
#define _CONFIG_H
#include <stdbool.h>
#include "queue.h"
typedef struct Config Config;
extern Config config;
/**
* Part of the struct Config. It makes sense to group colors for background,
* border and text as every element in i3 has them (window decorations, bar).
*
*/
struct Colortriple {
uint32_t border;
uint32_t background;
uint32_t text;
};
/**
* Holds a user-assigned variable for parsing the configuration file. The key
* is replaced by value in every following line of the file.
*
*/
struct Variable {
char *key;
char *value;
@ -33,10 +44,17 @@ struct Variable {
SLIST_ENTRY(Variable) variables;
};
/**
* Holds part of the configuration (the part which is not already in dedicated
* structures in include/data.h).
*
*/
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;
@ -60,6 +78,19 @@ 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);
/**
* Ungrabs all keys, to be called before re-grabbing the keys because of a
* mapping_notify event or a configuration file reload
*
*/
void ungrab_all_keys(xcb_connection_t *conn);
/**
* Grab the bound keys (tell X to send us keypress events for those keycodes)
*
*/
void grab_all_keys(xcb_connection_t *conn);
#endif

View File

@ -19,24 +19,28 @@
#include "queue.h"
/*
* To get the big concept: There are helper structures like struct Colorpixel or
* struct Stack_Window. Everything which is also defined as type (see forward definitions)
* is considered to be a major structure, thus important.
* To get the big concept: There are helper structures like struct Colorpixel
* or struct Stack_Window. Everything which is also defined as type (see
* forward definitions) is considered to be a major structure, thus important.
*
* Lets start from the biggest to the smallest:
* - An i3Screen is a virtual screen (Xinerama). This can be a single one, though two monitors
* might be connected, if youre running clone mode. There can also be multiple of them.
*
* - Each i3Screen contains Workspaces. The concept is known from various other window managers.
* Basically, a workspace is a specific set of windows, usually grouped thematically (irc,
* www, work, ). You can switch between these.
* - An i3Screen is a virtual screen (Xinerama). This can be a single one,
* though two monitors might be connected, if youre running clone
* mode. There can also be multiple of them.
*
* - Each Workspace has a table, which is our layout abstraction. You manage your windows
* by moving them around in your table. It grows as necessary.
* - Each i3Screen contains Workspaces. The concept is known from various
* other window managers. Basically, a workspace is a specific set of
* windows, usually grouped thematically (irc, www, work, ). You can switch
* between these.
*
* - Each cell of the table has a container, which can be in default or stacking mode. In default
* mode, each client is given equally much space in the container. In stacking mode, only one
* client is shown at a time, but all the titlebars are rendered at the top.
* - Each Workspace has a table, which is our layout abstraction. You manage
* your windows by moving them around in your table. It grows as necessary.
*
* - Each cell of the table has a container, which can be in default or
* stacking mode. In default mode, each client is given equally much space
* in the container. In stacking mode, only one client is shown at a time,
* but all the titlebars are rendered at the top.
*
* - Inside the container are clients, which is X11-speak for a window.
*
@ -69,12 +73,16 @@ enum {
BIND_MODE_SWITCH = (1 << 8)
};
/**
* Stores a rectangle, for example the size of a window, the child window etc.
*
*/
struct Rect {
uint32_t x, y;
uint32_t width, height;
};
/*
/**
* Defines a position in the table
*
*/
@ -83,28 +91,43 @@ struct Cell {
int column;
};
/*
/**
* Used for the cache of colorpixels.
*
*/
struct Colorpixel {
uint32_t pixel;
char *hex;
SLIST_ENTRY(Colorpixel) colorpixels;
};
/*
* Contains data for the windows needed to draw the titlebars on in stacking mode
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
*
*/
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 */
/** Backpointer to the container this stack window is in */
Container *container;
SLIST_ENTRY(Stack_Window) stack_windows;
@ -117,225 +140,292 @@ struct Ignore_Event {
SLIST_ENTRY(Ignore_Event) ignore_events;
};
/*
* Emulates the behaviour of tables of libxcb-wm, which in libxcb 0.3.4 suddenly vanished.
/**
* Emulates the behaviour of tables of libxcb-wm, which in libxcb 0.3.4
* suddenly vanished.
*
*/
struct keyvalue_element {
uint32_t key;
void *value;
TAILQ_ENTRY(keyvalue_element) elements;
};
typedef struct {
enum xcb_atom_fast_tag_t tag;
union {
xcb_get_window_attributes_cookie_t cookie;
uint8_t override_redirect;
} u;
} window_attributes_t;
/******************************************************************************
* Major types
*****************************************************************************/
/*
* The concept of Workspaces is known from various other window managers. Basically,
* a workspace is a specific set of windows, usually grouped thematically (irc,
* www, work, ). You can switch between these.
/**
* The concept of Workspaces is known from various other window
* managers. Basically, a workspace is a specific set of windows, usually
* grouped thematically (irc, www, work, ). You can switch between these.
*
*/
struct Workspace {
/* Number of this workspace, starting from 0 */
/** Number of this workspace, starting from 0 */
int num;
/* x, y, width, height */
/** 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;
/* table dimensions */
/** table dimensions */
int cols;
/** table dimensions */
int rows;
/* These are stored here only while this workspace is _not_ shown (see show_workspace()) */
/** These are stored here only while this workspace is _not_ shown
* (see show_workspace()) */
int current_row;
/** These are stored here only while this workspace is _not_ shown
* (see show_workspace()) */
int current_col;
/* Should clients on this workspace be automatically floating? */
/** Should clients on this workspace be automatically floating? */
bool auto_float;
/* Are the floating clients on this workspace currently hidden? */
/** Are the floating clients on this workspace currently hidden? */
bool floating_hidden;
/** A <screen> specifier on which this workspace would like to be (if
* the screen is available). screen := <number> | <position> */
char *preferred_screen;
/** Temporary flag needed for re-querying xinerama screens */
bool reassigned;
/** the client who is started in fullscreen mode on this workspace,
* NULL if there is none */
Client *fullscreen_client;
/* The focus stack contains the clients in the correct order of focus so that
the focus can be reverted correctly when a client is closed */
/** The focus stack contains the clients in the correct order of focus
so that the focus can be reverted correctly when a client is
closed */
SLIST_HEAD(focus_stack_head, Client) focus_stack;
/* This tail queue contains the floating clients in order of when they were first
* set to floating (new floating clients are just appended) */
/** This tail queue contains the floating clients in order of when
* they were first set to floating (new floating clients are just
* appended) */
TAILQ_HEAD(floating_clients_head, Client) floating_clients;
/* Backpointer to the screen this workspace is on */
/** Backpointer to the screen this workspace is on */
i3Screen *screen;
/* This is a two-dimensional dynamic array of Container-pointers. Ive always wanted
* to be a three-star programmer :) */
/** This is a two-dimensional dynamic array of
* Container-pointers. Ive always wanted to be a three-star
* programmer :) */
Container ***table;
/* width_factor and height_factor contain the amount of space (percentage) a column/row
has of all the space which is available for resized windows. This ensures that
non-resized windows (newly opened, for example) have the same size as always */
/** width_factor and height_factor contain the amount of space
* (percentage) a column/row has of all the space which is available
* for resized windows. This ensures that non-resized windows (newly
* opened, for example) have the same size as always */
float *width_factor;
float *height_factor;
};
/*
* Holds a keybinding, consisting of a keycode combined with modifiers and the command
* which is executed as soon as the key is pressed (see src/command.c)
/**
* Holds a keybinding, consisting of a keycode combined with modifiers and the
* command which is executed as soon as the key is pressed (see src/command.c)
*
*/
struct Binding {
/* Keycode to bind */
/** Symbol the user specified in configfile, if any. This needs to be
* stored with the binding to be able to re-convert it into a keycode
* if the keyboard mapping changes (using Xmodmap for example) */
char *symbol;
/** Only in use if symbol != NULL. Gets set to the value to which the
* symbol got translated when binding. Useful for unbinding and
* checking which binding was used when a key press event comes in.
*
* This is an array of number_keycodes size. */
xcb_keycode_t *translated_to;
uint32_t number_keycodes;
/** Keycode to bind */
uint32_t keycode;
/* Bitmask consisting of BIND_MOD_1, BIND_MODE_SWITCH, … */
/** Bitmask consisting of BIND_MOD_1, BIND_MODE_SWITCH, … */
uint32_t mods;
/* Command, like in command mode */
/** Command, like in command mode */
char *command;
TAILQ_ENTRY(Binding) bindings;
};
/*
/**
* Holds a command specified by an exec-line in the config (see src/config.c)
*
*/
struct Autostart {
/* Command, like in command mode */
/** Command, like in command mode */
char *command;
TAILQ_ENTRY(Autostart) autostarts;
};
/*
/**
* Holds an assignment for a given window class/title to a specific workspace
* (see src/config.c)
*
*/
struct Assignment {
char *windowclass_title;
/* floating is true if this was an assignment to the special workspace "~".
* Matching clients will be put into floating mode automatically. */
bool floating;
/** floating is true if this was an assignment to the special
* workspace "~". Matching clients will be put into floating mode
* automatically. */
enum {
ASSIGN_FLOATING_NO, /* 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;
};
/*
/**
* Data structure for cached font information:
* - font id in X11 (load it once)
* - font height (multiple calls needed to get it)
*
*/
struct Font {
/* The name of the font, that is what the pattern resolves to */
/** The name of the font, that is what the pattern resolves to */
char *name;
/* A copy of the pattern to build a cache */
/** A copy of the pattern to build a cache */
char *pattern;
/* The height of the font, built from font_ascent + font_descent */
/** The height of the font, built from font_ascent + font_descent */
int height;
/* The xcb-id for the font */
/** The xcb-id for the font */
xcb_font_t id;
TAILQ_ENTRY(Font) fonts;
};
/*
/**
* A client is X11-speak for a window.
*
*/
struct Client {
/* initialized will be set to true if the client was fully initialized by
* manage_window() and all functions can be used normally */
/** initialized will be set to true if the client was fully
* initialized by manage_window() and all functions can be used
* normally */
bool initialized;
/* if you set a client to floating and set it back to managed, it does remember its old
position and *tries* to get back there */
/** if you set a client to floating and set it back to managed, it
* does remember its old position and *tries* to get back there */
Cell old_position;
/* Backpointer. A client is inside a container */
/** Backpointer. A client is inside a container */
Container *container;
/* Because dock clients dont have a container, we have this workspace-backpointer */
/** Because dock clients dont have a container, we have this
* workspace-backpointer */
Workspace *workspace;
/* x, y, width, height of the frame */
/** x, y, width, height of the frame */
Rect rect;
/* Position in floating mode and in tiling mode are saved separately */
/** Position in floating mode and in tiling mode are saved
* separately */
Rect floating_rect;
/* x, y, width, height of the child (relative to its frame) */
/** x, y, width, height of the child (relative to its frame) */
Rect child_rect;
/* contains the size calculated from the hints set by the window or 0 if the client
did not send any hints */
/** contains the size calculated from the hints set by the window or 0
* if the client did not send any hints */
int proportional_height;
int proportional_width;
/* Height which was determined by reading the _NET_WM_STRUT_PARTIAL top/bottom of the screen
reservation */
/** contains the minimum increment size as specified for the window
* (in pixels). */
int width_increment;
int height_increment;
/** Height which was determined by reading the _NET_WM_STRUT_PARTIAL
* top/bottom of the screen reservation */
int desired_height;
/* Name (= window title) */
/** Name (= window title) */
char *name;
/* name_len stores the real string length (glyphs) of the window title if the client uses
_NET_WM_NAME. Otherwise, it is set to -1 to indicate that name should be just passed
to X as 8-bit string and therefore will not be rendered correctly. This behaviour is
to support legacy applications which do not set _NET_WM_NAME */
/** name_len stores the real string length (glyphs) of the window
* title if the client uses _NET_WM_NAME. Otherwise, it is set to -1
* to indicate that name should be just passed to X as 8-bit string
* and therefore will not be rendered correctly. This behaviour is to
* support legacy applications which do not set _NET_WM_NAME */
int name_len;
/* This will be set to true as soon as the first _NET_WM_NAME comes in. If set to true,
legacy window names are ignored. */
/** This will be set to true as soon as the first _NET_WM_NAME comes
* in. If set to true, legacy window names are ignored. */
bool uses_net_wm_name;
/* Holds the WM_CLASS, useful for matching the client in commands */
/** Holds the WM_CLASS, useful for matching the client in commands */
char *window_class;
/* fullscreen is pretty obvious */
/** Holds the xcb_window_t (just an ID) for the leader window (logical
* parent for toolwindows and similar floating windows) */
xcb_window_t leader;
/** fullscreen is pretty obvious */
bool fullscreen;
/* floating? (= not in tiling layout) This cannot be simply a bool because we want to keep track
* of whether the status was set by the application (by setting WM_CLASS to tools for example) or
* by the user. The users choice overwrites automatic mode, of course. The order of the values
* is important because we check with >= FLOATING_AUTO_ON if a client is floating. */
/** floating? (= not in tiling layout) This cannot be simply a bool
* because we want to keep track of whether the status was set by the
* application (by setting WM_CLASS to tools for example) or by the
* user. The users choice overwrites automatic mode, of course. The
* order of the values is important because we check with >=
* FLOATING_AUTO_ON if a client is floating. */
enum { FLOATING_AUTO_OFF = 0, FLOATING_USER_OFF = 1, FLOATING_AUTO_ON = 2, FLOATING_USER_ON = 3 } floating;
/* Ensure TITLEBAR_TOP maps to 0 because we use calloc for initialization later */
/** Ensure TITLEBAR_TOP maps to 0 because we use calloc for
* initialization later */
enum { TITLEBAR_TOP = 0, TITLEBAR_LEFT, TITLEBAR_RIGHT, TITLEBAR_BOTTOM, TITLEBAR_OFF } titlebar_position;
/* If a client is set as a dock, it is placed at the very bottom of the screen and its
requested size is used */
/** 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;
/* After leaving fullscreen mode, a client needs to be reconfigured (configuration =
setting X, Y, width and height). By setting the force_reconfigure flag, render_layout()
will reconfigure the client. */
/* After leaving fullscreen mode, a client needs to be reconfigured
* (configuration = setting X, Y, width and height). By setting the
* force_reconfigure flag, render_layout() will reconfigure the
* client. */
bool force_reconfigure;
/* When reparenting a window, an unmap-notify is sent. As we delete windows when theyre
unmapped, we need to ignore that one. Therefore, this flag is set when reparenting. */
/* When reparenting a window, an unmap-notify is sent. As we delete
* windows when theyre unmapped, we need to ignore that
* one. Therefore, this flag is set when reparenting. */
bool awaiting_useless_unmap;
/* XCB contexts */
xcb_window_t frame; /* Our window: The frame around the client */
xcb_gcontext_t titlegc; /* The titlebars graphic context inside the frame */
xcb_window_t child; /* The clients window */
xcb_window_t frame; /**< Our window: The frame around the
* client */
xcb_gcontext_t titlegc; /**< The titlebars graphic context
* inside the frame */
xcb_window_t child; /**< The clients window */
/* The following entry provides the necessary list pointers to use Client with LIST_* macros */
/** The following entry provides the necessary list pointers to use
* Client with LIST_* macros */
CIRCLEQ_ENTRY(Client) clients;
SLIST_ENTRY(Client) dock_clients;
SLIST_ENTRY(Client) focus_clients;
TAILQ_ENTRY(Client) floating_clients;
};
/*
* A container is either in default or stacking mode. It sits inside each cell of the table.
/**
* A container is either in default or stacking mode. It sits inside each cell
* of the table.
*
*/
struct Container {
@ -354,37 +444,41 @@ struct Container {
int width;
int height;
/* When in stacking mode, we draw the titlebars of each client onto a separate window */
/* When in stacking mode, we draw the titlebars of each client onto a
* separate window */
struct Stack_Window stack_win;
/* Backpointer to the workspace this container is in */
Workspace *workspace;
/* Ensure MODE_DEFAULT maps to 0 because we use calloc for initialization later */
/* Ensure MODE_DEFAULT maps to 0 because we use calloc for
* initialization later */
enum { MODE_DEFAULT = 0, MODE_STACK } mode;
CIRCLEQ_HEAD(client_head, Client) clients;
};
/*
* This is a virtual screen (Xinerama). This can be a single one, though two monitors
* might be connected, if youre running clone mode. There can also be multiple of them.
/**
* This is a virtual screen (Xinerama). This can be a single one, though two
* monitors might be connected, if youre running clone mode. There can also
* be multiple of them.
*
*/
struct Screen {
/* Virtual screen number */
/** Virtual screen number */
int num;
/* Current workspace selected on this virtual screen */
/** Current workspace selected on this virtual screen */
int current_workspace;
/* x, y, width, height */
/** x, y, width, height */
Rect rect;
/* The bar window */
/** The bar window */
xcb_window_t bar;
xcb_gcontext_t bargc;
/* Contains all clients with _NET_WM_WINDOW_TYPE == _NET_WM_WINDOW_TYPE_DOCK */
/** Contains all clients with _NET_WM_WINDOW_TYPE ==
* _NET_WM_WINDOW_TYPE_DOCK */
SLIST_HEAD(dock_clients_head, Client) dock_clients;
TAILQ_ENTRY(Screen) screens;

View File

@ -18,38 +18,42 @@ typedef void(*callback_t)(Rect*, uint32_t, uint32_t);
typedef enum { BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM} border_t;
/**
* Enters floating mode for the given client.
* Correctly takes care of the position/size (separately stored for tiling/floating mode)
* and repositions/resizes/redecorates the client.
* Enters floating mode for the given client. Correctly takes care of the
* position/size (separately stored for tiling/floating mode) and
* repositions/resizes/redecorates the client.
*
* If the automatic flag is set to true, this was an automatic update by a change of the
* window class from the application which can be overwritten by the user.
* If the automatic flag is set to true, this was an automatic update by a
* change of the window class from the application which can be overwritten by
* the user.
*
*/
void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic);
void toggle_floating_mode(xcb_connection_t *conn, Client *client,
bool automatic);
/**
* Removes the floating client from its workspace and attaches it to the new workspace.
* This is centralized here because it may happen if you move it via keyboard and
* if you move it using your mouse.
* Removes the floating client from its workspace and attaches it to the new
* workspace. This is centralized here because it may happen if you move it
* via keyboard and if you move it using your mouse.
*
*/
void floating_assign_to_workspace(Client *client, Workspace *new_workspace);
/**
* Called whenever the user clicks on a border (not the titlebar!) of a floating window.
* Determines on which border the user clicked and launches the drag_pointer function
* with the resize_callback.
* Called whenever the user clicks on a border (not the titlebar!) of a
* floating window. Determines on which border the user clicked and launches
* the drag_pointer function with the resize_callback.
*
*/
int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event);
int floating_border_click(xcb_connection_t *conn, Client *client,
xcb_button_press_event_t *event);
/**
* Called when the user clicked on the titlebar of a floating window.
* Calls the drag_pointer function with the drag_window callback
*
*/
void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event);
void floating_drag_window(xcb_connection_t *conn, Client *client,
xcb_button_press_event_t *event);
/**
* Changes focus in the given direction for floating clients.
@ -58,13 +62,15 @@ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_pre
* changing to top/bottom means cycling through the Z-index.
*
*/
void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction);
void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused,
direction_t direction);
/**
* Moves the client 10px to the specified direction.
*
*/
void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction);
void floating_move(xcb_connection_t *conn, Client *currently_focused,
direction_t direction);
/**
* Hides all floating clients (or show them if they are currently hidden) on

View File

@ -12,56 +12,82 @@
#define _HANDLERS_H
/**
* Due to bindings like Mode_switch + <a>, we need to bind some keys in XCB_GRAB_MODE_SYNC.
* Therefore, we just replay all key presses.
* Due to bindings like Mode_switch + <a>, we need to bind some keys in
* XCB_GRAB_MODE_SYNC. Therefore, we just replay all key presses.
*
*/
int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event);
int handle_key_release(void *ignored, xcb_connection_t *conn,
xcb_key_release_event_t *event);
/**
* There was a key press. We compare this key code with our bindings table and pass
* the bound action to parse_command().
* There was a key press. We compare this key code with our bindings table and
* pass the bound action to parse_command().
*
*/
int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event);
int handle_key_press(void *ignored, xcb_connection_t *conn,
xcb_key_press_event_t *event);
/**
* When the user moves the mouse pointer onto a window, this callback gets called.
* When the user moves the mouse pointer onto a window, this callback gets
* called.
*
*/
int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_event_t *event);
int handle_enter_notify(void *ignored, xcb_connection_t *conn,
xcb_enter_notify_event_t *event);
/**
* Checks if the button press was on a stack window, handles focus setting and returns true
* if so, or false otherwise.
* When the user moves the mouse but does not change the active window
* (e.g. when having no windows opened but moving mouse on the root screen
* and crossing virtual screen boundaries), this callback gets called.
*
*/
int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event);
int handle_motion_notify(void *ignored, xcb_connection_t *conn,
xcb_motion_notify_event_t *event);
/**
* Called when the keyboard mapping changes (for example by using Xmodmap),
* we need to update our key bindings then (re-translate symbols).
*
*/
int handle_mapping_notify(void *ignored, xcb_connection_t *conn,
xcb_mapping_notify_event_t *event);
/**
* Checks if the button press was on a stack window, handles focus setting and
* returns true if so, or false otherwise.
*
*/
int handle_button_press(void *ignored, xcb_connection_t *conn,
xcb_button_press_event_t *event);
/**
* A new window appeared on the screen (=was mapped), so lets manage it.
*
*/
int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_event_t *event);
int handle_map_request(void *prophs, xcb_connection_t *conn,
xcb_map_request_event_t *event);
/**
* Configuration notifies are only handled because we need to set up ignore for the following
* enter notify events
* Configuration notifies are only handled because we need to set up ignore
* for the following enter notify events
*
*/
int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event);
/**
* Configure requests are received when the application wants to resize windows on their own.
* Configure requests are received when the application wants to resize
* windows on their own.
*
* We generate a synthethic configure notify event to signalize the client its "new" position.
* We generate a synthethic configure notify event to signalize the client its
* "new" position.
*
*/
int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event);
int handle_configure_request(void *prophs, xcb_connection_t *conn,
xcb_configure_request_event_t *event);
/**
* Our window decorations were unmapped. That means, the window will be killed now,
* so we better clean up before.
* Our window decorations were unmapped. That means, the window will be killed
* now, so we better clean up before.
*
*/
int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event);
@ -71,67 +97,87 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
*
*/
int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop);
xcb_window_t window, xcb_atom_t atom,
xcb_get_property_reply_t *prop);
/**
* We handle legacy window names (titles) which are in COMPOUND_TEXT encoding. However, we
* just pass them along, so when containing non-ASCII characters, those will be rendering
* incorrectly. In order to correctly render unicode window titles in i3, an application
* has to set _NET_WM_NAME, which is in UTF-8 encoding.
* We handle legacy window names (titles) which are in COMPOUND_TEXT
* encoding. However, we just pass them along, so when containing non-ASCII
* characters, those will be rendering incorrectly. In order to correctly
* render unicode window titles in i3, an application has to set _NET_WM_NAME,
* which is in UTF-8 encoding.
*
* On every update, a message is put out to the user, so he may improve the situation and
* update applications which display filenames in their title to correctly use
* _NET_WM_NAME and therefore support unicode.
* On every update, a message is put out to the user, so he may improve the
* situation and update applications which display filenames in their title to
* correctly use _NET_WM_NAME and therefore support unicode.
*
*/
int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop);
int handle_windowname_change_legacy(void *data, xcb_connection_t *conn,
uint8_t state, xcb_window_t window,
xcb_atom_t atom, xcb_get_property_reply_t
*prop);
/**
* Store the window classes for jumping to them later.
*
*/
int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop);
xcb_window_t window, xcb_atom_t atom,
xcb_get_property_reply_t *prop);
/**
* Expose event means we should redraw our windows (= title bar)
*
*/
int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *event);
int handle_expose_event(void *data, xcb_connection_t *conn,
xcb_expose_event_t *event);
/**
* Handle client messages (EWMH)
*
*/
int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message_event_t *event);
int handle_client_message(void *data, xcb_connection_t *conn,
xcb_client_message_event_t *event);
/**
* Handles _NET_WM_WINDOW_TYPE changes
*
*/
int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
xcb_atom_t atom, xcb_get_property_reply_t *property);
int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom,
xcb_get_property_reply_t *property);
/**
* Handles the size hints set by a window, but currently only the part necessary for displaying
* clients proportionally inside their frames (mplayer for example)
* Handles the size hints set by a window, but currently only the part
* necessary for displaying clients proportionally inside their frames
* (mplayer for example)
*
* See ICCCM 4.1.2.3 for more details
*
*/
int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
xcb_atom_t name, xcb_get_property_reply_t *reply);
int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t name,
xcb_get_property_reply_t *reply);
/**
* Handles the transient for hints set by a window, signalizing that this window is a popup window
* for some other window.
* Handles the transient for hints set by a window, signalizing that this
* window is a popup window for some other window.
*
* See ICCCM 4.1.2.6 for more details
*
*/
int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
xcb_atom_t name, xcb_get_property_reply_t *reply);
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

@ -11,6 +11,7 @@
#include <xcb/xcb.h>
#include <xcb/xcb_property.h>
#include <xcb/xcb_event.h>
#include <xcb/xcb_keysyms.h>
#include <X11/XKBlib.h>
@ -20,8 +21,10 @@
#ifndef _I3_H
#define _I3_H
#define NUM_ATOMS 17
#define NUM_ATOMS 18
extern xcb_connection_t *global_conn;
extern xcb_key_symbols_t *keysyms;
extern char **start_argv;
extern Display *xkbdpy;
extern TAILQ_HEAD(bindings_head, Binding) bindings;
@ -30,6 +33,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

@ -14,24 +14,27 @@
#define _LAYOUT_H
/**
* Gets the unoccupied space (= space which is available for windows which were resized by the user)
* This is necessary to render both, customly resized windows and never touched
* windows correctly, meaning that the aspect ratio will be maintained when opening new windows.
* Gets the unoccupied space (= space which is available for windows which
* were resized by the user) This is necessary to render both, customly
* resized windows and never touched windows correctly, meaning that the
* aspect ratio will be maintained when opening new windows.
*
*/
int get_unoccupied_x(Workspace *workspace);
/**
* (Re-)draws window decorations for a given Client onto the given drawable/graphic context.
* When in stacking mode, the window decorations are drawn onto an own window.
* (Re-)draws window decorations for a given Client onto the given
* drawable/graphic context. When in stacking mode, the window decorations
* are drawn onto an own window.
*
*/
void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable, xcb_gcontext_t gc, int offset);
void decorate_window(xcb_connection_t *conn, Client *client,
xcb_drawable_t drawable, xcb_gcontext_t gc, int offset);
/**
* Redecorates the given client correctly by checking if its in a stacking container and
* re-rendering the stack window or just calling decorate_window if its not in a stacking
* container.
* Redecorates the given client correctly by checking if its in a stacking
* container and re-rendering the stack window or just calling decorate_window
* if its not in a stacking container.
*
*/
void redecorate_window(xcb_connection_t *conn, Client *client);
@ -43,25 +46,30 @@ void redecorate_window(xcb_connection_t *conn, Client *client);
void reposition_client(xcb_connection_t *conn, Client *client);
/**
* Pushes the clients width/height to X11 and resizes the child window
* Pushes the clients width/height to X11 and resizes the child window. This
* function also updates the clients position, so if you work on tiling clients
* only, you can use this function instead of separate calls to reposition_client
* and resize_client to reduce flickering.
*
*/
void resize_client(xcb_connection_t *conn, Client *client);
/**
* Renders the given container. Is called by render_layout() or individually (for example
* when focus changes in a stacking container)
* Renders the given container. Is called by render_layout() or individually
* (for example when focus changes in a stacking container)
*
*/
void render_container(xcb_connection_t *conn, Container *container);
/**
* Modifies the event mask of all clients on the given workspace to either ignore or to handle
* enter notifies. It is handy to ignore notifies because they will be sent when a window is mapped
* under the cursor, thus when the user didnt enter the window actively at all.
* Modifies the event mask of all clients on the given workspace to either
* ignore or to handle enter notifies. It is handy to ignore notifies because
* they will be sent when a window is mapped under the cursor, thus when the
* user didnt enter the window actively at all.
*
*/
void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bool ignore_enter_notify);
void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace,
bool ignore_enter_notify);
/**
* Renders the given workspace on the given screen
@ -70,11 +78,11 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bo
void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws);
/**
* Renders the whole layout, that is: Go through each screen, each workspace, each container
* and render each client. This also renders the bars.
* Renders the whole layout, that is: Go through each screen, each workspace,
* each container and render each client. This also renders the bars.
*
* If you dont need to render *everything*, you should call render_container on the container
* you want to refresh.
* If you dont need to render *everything*, you should call render_container
* on the container you want to refresh.
*
*/
void render_layout(xcb_connection_t *conn);

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -16,21 +16,26 @@
#define _MANAGE_H
/**
* Go through all existing windows (if the window manager is restarted) and manage them
* Go through all existing windows (if the window manager is restarted) and
* manage them
*
*/
void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root);
void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t
*prophs, xcb_window_t root);
/**
* Do some sanity checks and then reparent the window.
*
*/
void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
xcb_window_t window, window_attributes_t wa);
xcb_window_t window,
xcb_get_window_attributes_cookie_t cookie,
bool needs_to_be_mapped);
/**
* reparent_window() gets called when a new window was opened and becomes a child of the root
* window, or it gets called by us when we manage the already existing windows at startup.
* reparent_window() gets called when a new window was opened and becomes a
* child of the root window, or it gets called by us when we manage the
* already existing windows at startup.
*
* Essentially, this is the point where we take over control.
*

View File

@ -21,7 +21,8 @@ typedef enum { O_HORIZONTAL, O_VERTICAL } resize_orientation_t;
* the table column/row.
*
*/
int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second,
resize_orientation_t orientation, xcb_button_press_event_t *event);
int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first,
int second, resize_orientation_t orientation,
xcb_button_press_event_t *event);
#endif

View File

@ -37,19 +37,35 @@ void expand_table_rows_at_head(Workspace *workspace);
/** Add one column to the table */
void expand_table_cols(Workspace *workspace);
/** Inserts one column at the tables head */
/**
* Inserts one column at the tables head
*
*/
void expand_table_cols_at_head(Workspace *workspace);
/** Performs simple bounds checking for the given column/row */
/**
* Performs simple bounds checking for the given column/row
*
*/
bool cell_exists(int col, int row);
/** Shrinks the table by "compacting" it, that is, removing completely empty rows/columns */
/**
* Shrinks the table by "compacting" it, that is, removing completely empty
* rows/columns
*
*/
void cleanup_table(xcb_connection_t *conn, Workspace *workspace);
/** Fixes col/rowspan (makes sure there are no overlapping windows) */
/**
* Fixes col/rowspan (makes sure there are no overlapping windows)
*
*/
void fix_colrowspan(xcb_connection_t *conn, Workspace *workspace);
/** Prints the tables contents in human-readable form for debugging */
/**
* Prints the tables contents in human-readable form for debugging
*
*/
void dump_table(xcb_connection_t *conn, Workspace *workspace);
#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) ? \
@ -32,8 +34,8 @@
} \
while (0)
/* ##__VA_ARGS__ means: leave out __VA_ARGS__ completely if it is empty, that is,
delete the preceding comma */
/** ##__VA_ARGS__ means: leave out __VA_ARGS__ completely if it is empty, that
is, delete the preceding comma */
#define LOG(fmt, ...) slog("%s:%s:%d - " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__)
TAILQ_HEAD(keyvalue_table_head, keyvalue_element);
@ -51,28 +53,22 @@ 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)
* Safe-wrapper around malloc which exits if malloc returns NULL (meaning that
* there is no more memory available)
*
*/
void *smalloc(size_t size);
/**
* Safe-wrapper around calloc which exits if malloc returns NULL (meaning that there
* is no more memory available)
* Safe-wrapper around calloc which exits if malloc returns NULL (meaning that
* there is no more memory available)
*
*/
void *scalloc(size_t size);
/**
* Safe-wrapper around strdup which exits if malloc returns NULL (meaning that there
* is no more memory available)
* Safe-wrapper around strdup which exits if malloc returns NULL (meaning that
* there is no more memory available)
*
*/
char *sstrdup(const char *str);
@ -84,40 +80,43 @@ char *sstrdup(const char *str);
bool table_put(struct keyvalue_table_head *head, uint32_t key, void *value);
/**
* Removes the element from the given keyvalue-table with the given key and returns its value;
* Removes the element from the given keyvalue-table with the given key and
* returns its value;
*
*/
void *table_remove(struct keyvalue_table_head *head, uint32_t key);
/**
* Returns the value of the element of the given keyvalue-table with the given key.
* Returns the value of the element of the given keyvalue-table with the given
* key.
*
*/
void *table_get(struct keyvalue_table_head *head, uint32_t key);
/**
* Starts the given application by passing it through a shell. We use double fork
* to avoid zombie processes. As the started applications parent exits (immediately),
* the application is reparented to init (process-id 1), which correctly handles
* childs, so we dont have to do it :-).
* Starts the given application by passing it through a shell. We use double
* fork to avoid zombie processes. As the started applications parent exits
* (immediately), the application is reparented to init (process-id 1), which
* correctly handles childs, so we dont have to do it :-).
*
* The shell is determined by looking for the SHELL environment variable. If it
* does not exist, /bin/sh is used.
* The shell is determined by looking for the SHELL environment variable. If
* it does not exist, /bin/sh is used.
*
*/
void start_application(const char *command);
/**
* Checks a generic cookie for errors and quits with the given message if there
* was an error.
* Checks a generic cookie for errors and quits with the given message if
* there was an error.
*
*/
void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_message);
void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie,
char *err_message);
/**
* Converts the given string to UCS-2 big endian for use with
* xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
* a buffer containing the UCS-2 encoded string (16 bit per glyph) is
* xcb_image_text_16(). The amount of real glyphs is stored in real_strlen, a
* buffer containing the UCS-2 encoded string (16 bit per glyph) is
* returned. It has to be freed when done.
*
*/
@ -128,34 +127,13 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen);
* the given container, optionally excluding the given client.
*
*/
Client *get_last_focused_client(xcb_connection_t *conn, Container *container, Client *exclude);
/**
* Unmaps all clients (and stack windows) of the given workspace.
*
* This needs to be called separately when temporarily rendering
* a workspace which is not the active workspace to force
* reconfiguration of all clients, like in src/xinerama.c when
* re-assigning a workspace to another screen.
*
*/
void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws);
/**
* Unmaps all clients (and stack windows) of the given workspace.
*
* This needs to be called separately when temporarily rendering
* a workspace which is not the active workspace to force
* reconfiguration of all clients, like in src/xinerama.c when
* re-assigning a workspace to another screen.
*
*/
void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws);
Client *get_last_focused_client(xcb_connection_t *conn, Container *container,
Client *exclude);
/**
* Sets the given client as focused by updating the data structures correctly,
* updating the X input focus and finally re-decorating both windows (to signalize
* the user the new focus situation)
* updating the X input focus and finally re-decorating both windows (to
* signalize the user the new focus situation)
*
*/
void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways);
@ -168,7 +146,8 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways);
void leave_stack_mode(xcb_connection_t *conn, Container *container);
/**
* Switches the layout of the given container taking care of the necessary house-keeping
* Switches the layout of the given container taking care of the necessary
* house-keeping
*
*/
void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode);
@ -179,7 +158,7 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode);
* will be checked.
*
*/
Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle,
Client *specific);
Client *get_matching_client(xcb_connection_t *conn,
const char *window_classtitle, Client *specific);
#endif

68
include/workspace.h Normal file
View File

@ -0,0 +1,68 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include <xcb/xcb.h>
#include "data.h"
#include "xinerama.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);
/** Switches to the given workspace */
void workspace_show(xcb_connection_t *conn, int workspace);
/**
* Initializes the given workspace if it is not already initialized. The given
* screen is to be understood as a fallback, if the workspace itself either
* was not assigned to a particular screen or cannot be placed there because
* the screen is not attached at the moment.
*
*/
void workspace_initialize(Workspace *ws, i3Screen *screen);
/**
* Gets the first unused workspace for the given screen, taking into account
* the preferred_screen setting of every workspace (workspace assignments).
*
*/
Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *screen);
/**
* Unmaps all clients (and stack windows) of the given workspace.
*
* This needs to be called separately when temporarily rendering a workspace
* which is not the active workspace to force reconfiguration of all clients,
* like in src/xinerama.c when re-assigning a workspace to another screen.
*
*/
void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws);
void workspace_map_clients(xcb_connection_t *conn, Workspace *ws);
#endif

View File

@ -17,7 +17,8 @@
#define _NET_WM_STATE_ADD 1
#define _NET_WM_STATE_TOGGLE 2
/* This is the equivalent of XC_left_ptr. Im not sure why xcb doesnt have a constant for that. */
/** This is the equivalent of XC_left_ptr. Im not sure why xcb doesnt have a
* constant for that. */
#define XCB_CURSOR_LEFT_PTR 68
#define XCB_CURSOR_SB_H_DOUBLE_ARROW 108
#define XCB_CURSOR_SB_V_DOUBLE_ARROW 116
@ -25,14 +26,15 @@
/* from X11/keysymdef.h */
#define XCB_NUM_LOCK 0xff7f
/* The event masks are defined here because we dont only set them once but we need to set slight
variations of them (without XCB_EVENT_MASK_ENTER_WINDOW while rendering the layout) */
/* The XCB_CW_EVENT_MASK for the child (= real window) */
/* The event masks are defined here because we dont only set them once but we
need to set slight variations of them (without XCB_EVENT_MASK_ENTER_WINDOW
while rendering the layout) */
/** The XCB_CW_EVENT_MASK for the child (= real window) */
#define CHILD_EVENT_MASK (XCB_EVENT_MASK_PROPERTY_CHANGE | \
XCB_EVENT_MASK_STRUCTURE_NOTIFY | \
XCB_EVENT_MASK_ENTER_WINDOW)
/* The XCB_CW_EVENT_MASK for its frame */
/** The XCB_CW_EVENT_MASK for its frame */
#define FRAME_EVENT_MASK (XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed/released */ \
XCB_EVENT_MASK_BUTTON_RELEASE | \
XCB_EVENT_MASK_EXPOSURE | /* …our window needs to be redrawn */ \
@ -58,14 +60,15 @@ enum { _NET_SUPPORTED = 0,
WM_PROTOCOLS,
WM_DELETE_WINDOW,
UTF8_STRING,
WM_STATE
WM_STATE,
WM_CLIENT_LEADER
};
extern unsigned int xcb_numlock_mask;
/**
* Loads a font for usage, getting its height. This function is used very often, so it
* maintains a cache.
* Loads a font for usage, getting its height. This function is used very
* often, so it maintains a cache.
*
*/
i3Font *load_font(xcb_connection_t *conn, const char *pattern);
@ -75,39 +78,43 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern);
*
* The hex_color has to start with #, for example #FF00FF.
*
* NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
* This has to be done by the caller.
* NOTE that get_colorpixel() does _NOT_ check the given color code for
* validity. This has to be done by the caller.
*
*/
uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
/**
* Convenience wrapper around xcb_create_window which takes care of depth, generating an ID and checking
* for errors.
* Convenience wrapper around xcb_create_window which takes care of depth,
* generating an ID and checking for errors.
*
*/
xcb_window_t create_window(xcb_connection_t *conn, Rect r, uint16_t window_class, int cursor,
uint32_t mask, uint32_t *values);
xcb_window_t create_window(xcb_connection_t *conn, Rect r, uint16_t window_class,
int cursor, bool map, uint32_t mask, uint32_t *values);
/**
* Changes a single value in the graphic context (so one doesnt have to define an array of values)
* Changes a single value in the graphic context (so one doesnt have to
* define an array of values)
*
*/
void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value);
void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc,
uint32_t mask, uint32_t value);
/**
* Draws a line from x,y to to_x,to_y using the given color
*
*/
void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc,
uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t to_x, uint32_t to_y);
void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable,
xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x,
uint32_t y, uint32_t to_x, uint32_t to_y);
/**
* Draws a rectangle from x,y with width,height using the given color
*
*/
void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc,
uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t width, uint32_t height);
void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable,
xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x,
uint32_t y, uint32_t width, uint32_t height);
/**
* Generates a configure_notify event and sends it to the given window
@ -118,14 +125,15 @@ void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext
void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window);
/**
* Generates a configure_notify_event with absolute coordinates (relative to the X root
* window, not to the clients frame) for the given client.
* Generates a configure_notify_event with absolute coordinates (relative to
* the X root window, not to the clients frame) for the given client.
*
*/
void fake_absolute_configure_notify(xcb_connection_t *conn, Client *client);
/**
* Finds out which modifier mask is the one for numlock, as the user may change this.
* Finds out which modifier mask is the one for numlock, as the user may
* change this.
*
*/
void xcb_get_numlock_mask(xcb_connection_t *conn);
@ -136,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

View File

@ -17,15 +17,22 @@ TAILQ_HEAD(screens_head, Screen);
extern struct screens_head *virtual_screens;
/**
* We have just established a connection to the X server and need the initial Xinerama
* information to setup workspaces for each screen.
* Returns true if both screen objects describe the same screen (checks their
* size and position).
*
*/
bool screens_are_equal(i3Screen *screen1, i3Screen *screen2);
/**
* We have just established a connection to the X server and need the initial
* Xinerama information to setup workspaces for each screen.
*
*/
void initialize_xinerama(xcb_connection_t *conn);
/**
* This is called when the rootwindow receives a configure_notify event and therefore the
* number/position of the Xinerama screens could have changed.
* This is called when the rootwindow receives a configure_notify event and
* therefore the number/position of the Xinerama screens could have changed.
*
*/
void xinerama_requery_screens(xcb_connection_t *conn);
@ -43,13 +50,13 @@ i3Screen *get_screen_at(int x, int y, struct screens_head *screenlist);
i3Screen *get_screen_containing(int x, int y);
/**
* Gets the screen which is the last one in the given direction, for example the screen
* on the most bottom when direction == D_DOWN, the screen most right when direction == D_RIGHT
* and so on.
* Gets the screen which is the last one in the given direction, for example
* the screen on the most bottom when direction == D_DOWN, the screen most
* right when direction == D_RIGHT and so on.
*
* This function always returns a screen.
*
*/
i3Screen *get_screen_most(direction_t direction);
i3Screen *get_screen_most(direction_t direction, i3Screen *current);
#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

@ -1,7 +1,7 @@
i3(1)
=====
Michael Stapelberg <michael+i3@stapelberg.de>
v3.beta, May 2009
v3.gamma, August 2009
== NAME
@ -232,9 +232,13 @@ your login manager (xdm, slim, gdm, …) as soon as you login.
# Disable DPMS turning off the screen
xset dpms force on
xset s off
# Disable bell
xset -b
# Enable zapping (C-A-<Bksp> kills X)
setxkbmap -option terminate:ctrl_alt_bksp
# Enforce correct locales from the beginning
unset LC_COLLATE
export LC_CTYPE=de_DE.UTF-8
@ -249,6 +253,9 @@ export LC_TELEPHONE=de_DE.UTF-8
export LC_MEASUREMENT=de_DE.UTF-8
export LC_IDENTIFICATION=de_DE.UTF-8
# Use XToolkit in java applications
export AWT_TOOLKIT=XToolkit
# Set background color
xsetroot -solid "#333333"

View File

@ -39,9 +39,11 @@ void client_remove_from_container(xcb_connection_t *conn, Client *client, Contai
/* If the container will be empty now and is in stacking mode, we need to
unmap the stack_win */
if (CIRCLEQ_EMPTY(&(container->clients)) && container->mode == MODE_STACK) {
LOG("Unmapping stack window\n");
struct Stack_Window *stack_win = &(container->stack_win);
stack_win->rect.height = 0;
xcb_unmap_window(conn, stack_win->window);
xcb_flush(conn);
}
}
@ -248,3 +250,67 @@ 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);
}
/*
* Unmap the client, correctly setting any state which is needed.
*
*/
void client_unmap(xcb_connection_t *conn, Client *client) {
/* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */
long data[] = { XCB_WM_STATE_WITHDRAWN, XCB_NONE };
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
xcb_unmap_window(conn, client->frame);
}
/*
* Map the client, correctly restoring any state needed.
*
*/
void client_map(xcb_connection_t *conn, Client *client) {
/* Set WM_STATE_NORMAL because GTK applications dont want to drag & drop if we dont.
* Also, xprop(1) needs that to work. */
long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
xcb_map_window(conn, client->frame);
}

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, cs);
else if (direction == D_LEFT)
target = get_screen_most(D_RIGHT, cs);
else if (direction == D_UP)
target = get_screen_most(D_DOWN, cs);
else target = get_screen_most(D_UP, cs);
}
LOG("Switching to ws %d\n", target->current_workspace + 1);
workspace_show(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)
@ -108,7 +146,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
if ((screen = get_screen_containing(container->x, destination_y)) == NULL) {
LOG("Wrapping screen around vertically\n");
/* No screen found? Then wrap */
screen = get_screen_most((direction == D_UP ? D_DOWN : D_UP));
screen = get_screen_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->screen);
}
t_ws = &(workspaces[screen->current_workspace]);
new_row = (direction == D_UP ? (t_ws->rows - 1) : 0);
@ -150,7 +188,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
int destination_x = (direction == D_LEFT ? (container->x - 1) : (container->x + container->width + 1));
if ((screen = get_screen_containing(destination_x, container->y)) == NULL) {
LOG("Wrapping screen around horizontally\n");
screen = get_screen_most((direction == D_LEFT ? D_RIGHT : D_LEFT));
screen = get_screen_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->screen);
}
t_ws = &(workspaces[screen->current_workspace]);
new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0);
@ -489,12 +527,10 @@ 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);
client_unmap(conn, client);
} else {
/* If this is not the case, we move the window to a workspace
* which is on another screen, so we also need to adjust its
@ -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,12 +610,10 @@ 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);
client_unmap(conn, current_client);
} else {
if (current_client->fullscreen) {
LOG("Calling client_enter_fullscreen again\n");
@ -592,117 +626,10 @@ 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);
}
/*
* Switches to the given workspace
*
*/
void show_workspace(xcb_connection_t *conn, int workspace) {
Client *client;
bool need_warp = false;
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
/* t_ws (to workspace) is just a convenience pointer to the workspace were switching to */
Workspace *t_ws = &(workspaces[workspace-1]);
LOG("show_workspace(%d)\n", workspace);
/* Store current_row/current_col */
c_ws->current_row = current_row;
c_ws->current_col = current_col;
/* Check if the workspace has not been used yet */
if (t_ws->screen == NULL) {
LOG("initializing new workspace, setting num to %d\n", workspace);
t_ws->screen = c_ws->screen;
/* Copy the dimensions from the virtual screen */
memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
}
if (c_ws->screen != t_ws->screen) {
/* We need to switch to the other screen first */
LOG("moving over to other screen.\n");
/* Store the old client */
Client *old_client = CUR_CELL->currently_focused;
c_ws = &(workspaces[t_ws->screen->current_workspace]);
current_col = c_ws->current_col;
current_row = c_ws->current_row;
if (CUR_CELL->currently_focused != NULL)
need_warp = true;
else {
Rect *dims = &(c_ws->screen->rect);
xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0,
dims->x + (dims->width / 2), dims->y + (dims->height / 2));
}
/* Re-decorate the old client, its not focused anymore */
if ((old_client != NULL) && !old_client->dock)
redecorate_window(conn, old_client);
else xcb_flush(conn);
}
/* Check if we need to change something or if were already there */
if (c_ws->screen->current_workspace == (workspace-1)) {
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused != SLIST_END(&(c_ws->focus_stack))) {
set_focus(conn, last_focused, true);
if (need_warp) {
client_warp_pointer_into(conn, last_focused);
xcb_flush(conn);
}
}
return;
}
t_ws->screen->current_workspace = workspace-1;
Workspace *old_workspace = c_ws;
c_ws = &workspaces[workspace-1];
/* Unmap all clients of the old workspace */
unmap_workspace(conn, old_workspace);
current_row = c_ws->current_row;
current_col = c_ws->current_col;
LOG("new current row = %d, current col = %d\n", current_row, current_col);
ignore_enter_notify_forall(conn, c_ws, true);
/* Map all clients on the new workspace */
FOR_TABLE(c_ws)
CIRCLEQ_FOREACH(client, &(c_ws->table[cols][rows]->clients), clients)
xcb_map_window(conn, client->frame);
/* Map all floating clients */
if (!c_ws->floating_hidden)
TAILQ_FOREACH(client, &(c_ws->floating_clients), floating_clients)
xcb_map_window(conn, client->frame);
/* Map all stack windows, if any */
struct Stack_Window *stack_win;
SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
if (stack_win->container->workspace == c_ws)
xcb_map_window(conn, stack_win->window);
ignore_enter_notify_forall(conn, c_ws, false);
/* Restore focus on the new workspace */
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused != SLIST_END(&(c_ws->focus_stack))) {
set_focus(conn, last_focused, true);
if (need_warp) {
client_warp_pointer_into(conn, last_focused);
xcb_flush(conn);
}
} else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
render_layout(conn);
}
/*
* Jumps to the given window class / title.
* Title is matched using strstr, that is, matches if it appears anywhere
@ -748,7 +675,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
}
/* Move to the target workspace */
show_workspace(conn, ws);
workspace_show(conn, ws);
if (result < 3)
return;
@ -846,6 +773,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)
workspace_show(conn, i+1);
}
/*
* Parses a command, see file CMDMODE for more information
*
@ -875,6 +834,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]);
@ -922,7 +887,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* Is it just 's' for stacking or 'd' for default? */
if ((command[0] == 's' || command[0] == 'd') && (command[1] == '\0')) {
if (last_focused == NULL || client_is_floating(last_focused)) {
if (last_focused != NULL && client_is_floating(last_focused)) {
LOG("not switching, this is a floating client\n");
return;
}
@ -931,13 +896,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 +924,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 +961,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;
@ -995,7 +979,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
if (*rest == '\0') {
/* No rest? This was a workspace number, not a times specification */
show_workspace(conn, times);
workspace_show(conn, times);
return;
}
@ -1046,6 +1030,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 +1043,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,10 +1061,12 @@ 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;
}
}
LOG("--- done ---\n");
}

View File

@ -14,10 +14,17 @@
#include <stdlib.h>
#include <glob.h>
/* We need Xlib for XStringToKeysym */
#include <X11/Xlib.h>
#include <xcb/xcb_keysyms.h>
#include "i3.h"
#include "util.h"
#include "config.h"
#include "xcb.h"
#include "table.h"
#include "workspace.h"
Config config;
@ -43,9 +50,7 @@ static void replace_variable(char *buffer, const char *key, const char *value) {
/* To prevent endless recursions when the user makes an error configuring,
* we stop after 100 replacements. That should be vastly more than enough. */
int c = 0;
LOG("Replacing %s with %s\n", key, value);
while ((pos = strcasestr(buffer, key)) != NULL && c++ < 100) {
LOG("replacing variable %s in \"%s\" with \"%s\"\n", key, buffer, value);
char *rest = pos + strlen(key);
*pos = '\0';
char *replaced;
@ -57,6 +62,75 @@ static void replace_variable(char *buffer, const char *key, const char *value) {
}
}
/**
* Ungrabs all keys, to be called before re-grabbing the keys because of a
* mapping_notify event or a configuration file reload
*
*/
void ungrab_all_keys(xcb_connection_t *conn) {
LOG("Ungrabbing all keys\n");
xcb_ungrab_key(conn, XCB_GRAB_ANY, root, XCB_BUTTON_MASK_ANY);
}
static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint32_t keycode) {
LOG("Grabbing %d\n", keycode);
if ((bind->mods & BIND_MODE_SWITCH) != 0)
xcb_grab_key(conn, 0, root, 0, 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, 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 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) {
/* The easy case: the user specified a keycode directly. */
if (bind->keycode > 0) {
grab_keycode_for_binding(conn, bind, bind->keycode);
continue;
}
/* We need to translate the symbol to a keycode */
xcb_keysym_t keysym = XStringToKeysym(bind->symbol);
if (keysym == NoSymbol) {
LOG("Could not translate string to key symbol: \"%s\"\n", bind->symbol);
continue;
}
xcb_keycode_t *keycodes = xcb_key_symbols_get_keycode(keysyms, keysym);
if (keycodes == NULL) {
LOG("Could not translate symbol \"%s\"\n", bind->symbol);
continue;
}
uint32_t last_keycode = 0;
bind->number_keycodes = 0;
for (xcb_keycode_t *walk = keycodes; *walk != 0; walk++) {
/* We hope duplicate keycodes will be returned in order
* and skip them */
if (last_keycode == *walk)
continue;
grab_keycode_for_binding(conn, bind, *walk);
last_keycode = *walk;
bind->number_keycodes++;
}
LOG("Translated symbol \"%s\" to %d keycode\n", bind->symbol, bind->number_keycodes);
bind->translated_to = smalloc(bind->number_keycodes * sizeof(xcb_keycode_t));
memcpy(bind->translated_to, keycodes, bind->number_keycodes * sizeof(xcb_keycode_t));
free(keycodes);
}
}
/*
* Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
*
@ -64,7 +138,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) \
@ -176,7 +272,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath)
}
/* key bindings */
if (strcasecmp(key, "bind") == 0) {
if (strcasecmp(key, "bind") == 0 || strcasecmp(key, "bindsym") == 0) {
#define CHECK_MODIFIER(name) \
if (strncasecmp(walk, #name, strlen(#name)) == 0) { \
modifiers |= BIND_##name; \
@ -201,14 +297,25 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath)
break;
}
/* Now check for the keycode */
int keycode = strtol(walk, &rest, 10);
if (!rest || *rest != ' ')
die("Invalid binding\n");
Binding *new = scalloc(sizeof(Binding));
/* Now check for the keycode or copy the symbol */
if (strcasecmp(key, "bind") == 0) {
int keycode = strtol(walk, &rest, 10);
if (!rest || *rest != ' ')
die("Invalid binding (keycode)\n");
new->keycode = keycode;
} else {
rest = walk;
char *sym = rest;
while (*rest != '\0' && *rest != ' ')
rest++;
if (*rest != ' ')
die("Invalid binding (keysym)\n");
new->symbol = strndup(sym, (rest - sym));
}
rest++;
LOG("keycode = %d, modifiers = %d, command = *%s*\n", keycode, modifiers, rest);
Binding *new = smalloc(sizeof(Binding));
new->keycode = keycode;
LOG("keycode = %d, symbol = %s, modifiers = %d, command = *%s*\n", new->keycode, new->symbol, modifiers, rest);
new->mods = modifiers;
new->command = sstrdup(rest);
TAILQ_INSERT_TAIL(&bindings, new, bindings);
@ -239,26 +346,81 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath)
continue;
}
/* workspace "workspace number" [screen <screen>] ["name of the workspace"]
* with screen := <number> | <position>, e.g. screen 1280 or screen 1 */
if (strcasecmp(key, "name") == 0 || strcasecmp(key, "workspace") == 0) {
LOG("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 (strncasecmp(name, "screen ", strlen("screen ")) == 0) {
char *screen = strdup(name + strlen("screen "));
if ((end = strchr(screen, ' ')) != NULL)
*end = '\0';
LOG("Setting preferred screen for workspace %d to \"%s\"\n", ws_num, screen);
workspaces[ws_num - 1].preferred_screen = screen;
name += strlen("screen ") + strlen(screen);
}
/* Strip leading whitespace */
while (*name != '\0' && *name == ' ')
name++;
LOG("rest to parse = %s\n", name);
if (name == '\0') {
free(ws_str);
continue;
}
LOG("setting name to \"%s\"\n", name);
workspace_set_name(&(workspaces[ws_num - 1]), name);
free(ws_str);
continue;
}
/* assign window class[/window title] → workspace */
if (strcasecmp(key, "assign") == 0) {
LOG("assign: \"%s\"\n", value);
char *class_title = sstrdup(value);
char *class_title;
char *target;
char *end;
/* If the window class/title is quoted we skip quotes */
if (class_title[0] == '"') {
class_title++;
char *end = strchr(class_title, '"');
if (end == NULL)
die("Malformed assignment, couldn't find terminating quote\n");
*end = '\0';
if (value[0] == '"') {
class_title = sstrdup(value+1);
end = strchr(class_title, '"');
} else {
class_title = sstrdup(value);
/* If it is not quoted, we terminate it at the first space */
char *end = strchr(class_title, ' ');
if (end == NULL)
die("Malformed assignment, couldn't find terminating space\n");
*end = '\0';
end = strchr(class_title, ' ');
}
if (end == NULL)
die("Malformed assignment, couldn't find terminating quote\n");
*end = '\0';
/* Strip trailing whitespace */
while (strlen(value) > 0 && value[strlen(value)-1] == ' ')
value[strlen(value)-1] = '\0';
/* Strip trailing whitespace */
while (strlen(value) > 0 && value[strlen(value)-1] == ' ')
@ -266,20 +428,36 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath)
/* 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;
}
@ -303,14 +481,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);
@ -319,5 +504,20 @@ 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++) {
Workspace *ws = &(workspaces[i]);
if (ws->name != NULL) {
/* If the font was not specified when the workspace name
* was loaded, we need to predict the text width now */
if (ws->text_width == 0)
ws->text_width = predict_text_width(global_conn,
config.font, ws->name, ws->name_len);
continue;
}
workspace_set_name(&(workspaces[i]), NULL);
}
return;
}

View File

@ -415,8 +415,8 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) {
LOG("floating_hidden is now: %d\n", workspace->floating_hidden);
TAILQ_FOREACH(client, &(workspace->floating_clients), floating_clients) {
if (workspace->floating_hidden)
xcb_unmap_window(conn, client->frame);
else xcb_map_window(conn, client->frame);
client_unmap(conn, client);
else client_map(conn, client);
}
/* If we just unmapped all floating windows we should ensure that the focus

View File

@ -35,6 +35,7 @@
#include "client.h"
#include "manage.h"
#include "floating.h"
#include "workspace.h"
/* After mapping/unmapping windows, a notify event is generated. However, we dont want it,
since itd trigger an infinite loop of switching between the different windows when
@ -47,8 +48,6 @@ static void add_ignore_event(const int sequence) {
event->sequence = sequence;
event->added = time(NULL);
LOG("Adding sequence %d to ignorelist\n", sequence);
SLIST_INSERT_HEAD(&ignore_events, event, ignore_events);
}
@ -70,7 +69,6 @@ static bool event_is_ignored(const int sequence) {
SLIST_FOREACH(event, &ignore_events, ignore_events) {
if (event->sequence == sequence) {
LOG("Ignoring event (sequence %d)\n", sequence);
SLIST_REMOVE(&ignore_events, event, Ignore_Event, ignore_events);
free(event);
return true;
@ -86,7 +84,6 @@ static bool event_is_ignored(const int sequence) {
*
*/
int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) {
LOG("got key release, just passing\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_KEYBOARD, event->time);
xcb_flush(conn);
return 1;
@ -119,9 +116,24 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_
/* Find the binding */
Binding *bind;
TAILQ_FOREACH(bind, &bindings, bindings)
if (bind->keycode == event->detail && bind->mods == state_filtered)
break;
TAILQ_FOREACH(bind, &bindings, bindings) {
/* First compare the modifiers */
if (bind->mods != state_filtered)
continue;
/* If a symbol was specified by the user, we need to look in
* the array of translated keycodes for the events keycode */
if (bind->symbol != NULL) {
if (memmem(bind->translated_to,
bind->number_keycodes * sizeof(xcb_keycode_t),
&(event->detail), sizeof(xcb_keycode_t)) != NULL)
break;
} else {
/* This case is easier: The user specified a keycode */
if (bind->keycode == event->detail)
break;
}
}
/* No match? Then it was an actively grabbed key, that is with Mode_switch, and
the user did not press Mode_switch, so just pass it */
@ -140,6 +152,29 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_
return 1;
}
/*
* Called with coordinates of an enter_notify event or motion_notify event
* to check if the user crossed virtual screen boundaries and adjust the
* current workspace, if so.
*
*/
static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
i3Screen *screen;
if ((screen = get_screen_containing(x, y)) == NULL) {
LOG("ERROR: No such screen\n");
return;
}
if (screen == c_ws->screen)
return;
c_ws->current_row = current_row;
c_ws->current_col = current_col;
c_ws = &workspaces[screen->current_workspace];
current_row = c_ws->current_row;
current_col = c_ws->current_col;
LOG("We're now on virtual screen number %d\n", screen->num);
}
/*
* When the user moves the mouse pointer onto a window, this callback gets called.
@ -176,17 +211,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
/* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */
if (client == NULL) {
LOG("Getting screen at %d x %d\n", event->root_x, event->root_y);
i3Screen *screen = get_screen_containing(event->root_x, event->root_y);
if (screen == NULL) {
LOG("ERROR: No such screen\n");
return 0;
}
c_ws->current_row = current_row;
c_ws->current_col = current_col;
c_ws = &workspaces[screen->current_workspace];
current_row = c_ws->current_row;
current_col = c_ws->current_col;
LOG("We're now on virtual screen number %d\n", screen->num);
check_crossing_screen_boundary(event->root_x, event->root_y);
return 1;
}
@ -212,6 +237,44 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
return 1;
}
/*
* When the user moves the mouse but does not change the active window
* (e.g. when having no windows opened but moving mouse on the root screen
* and crossing virtual screen boundaries), this callback gets called.
*
*/
int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notify_event_t *event) {
/* Skip events where the pointer was over a child window, we are only
* interested in events on the root window. */
if (event->child != 0)
return 1;
check_crossing_screen_boundary(event->root_x, event->root_y);
return 1;
}
/*
* Called when the keyboard mapping changes (for example by using Xmodmap),
* we need to update our key bindings then (re-translate symbols).
*
*/
int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_notify_event_t *event) {
if (event->request != XCB_MAPPING_KEYBOARD &&
event->request != XCB_MAPPING_MODIFIER)
return 0;
LOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n");
xcb_refresh_keyboard_mapping(keysyms, event);
xcb_get_numlock_mask(conn);
ungrab_all_keys(conn);
grab_all_keys(conn);
return 0;
}
/*
* Checks if the button press was on a stack window, handles focus setting and returns true
* if so, or false otherwise.
@ -271,21 +334,27 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e
int add = (event->detail == XCB_BUTTON_INDEX_4 ? -1 : 1);
for (int i = c_ws->num + add; (i >= 0) && (i < 10); i += add)
if (workspaces[i].screen == screen) {
show_workspace(conn, i+1);
workspace_show(conn, i+1);
return true;
}
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)) {
show_workspace(conn, i+1);
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)) {
workspace_show(conn, i+1);
return true;
}
drawn += workspaces[i].text_width + 5 + 5 + 2;
}
return true;
}
@ -293,8 +362,7 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e
}
int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) {
LOG("button press!\n");
LOG("state = %d\n", event->state);
LOG("Button %d pressed\n", event->state);
/* This was either a focus for a clients parent (= titlebar)… */
Client *client = table_get(&by_child, event->event);
bool border_click = false;
@ -308,7 +376,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 +416,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);
@ -421,22 +501,13 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
*/
int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_event_t *event) {
xcb_get_window_attributes_cookie_t cookie;
xcb_get_window_attributes_reply_t *reply;
cookie = xcb_get_window_attributes_unchecked(conn, event->window);
if ((reply = xcb_get_window_attributes_reply(conn, cookie, NULL)) == NULL) {
LOG("Could not get window attributes\n");
return -1;
}
window_attributes_t wa = { TAG_VALUE };
LOG("override_redirect = %d\n", reply->override_redirect);
wa.u.override_redirect = reply->override_redirect;
LOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence);
add_ignore_event(event->sequence);
manage_window(prophs, conn, event->window, wa);
manage_window(prophs, conn, event->window, cookie, false);
return 1;
}
@ -447,13 +518,11 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve
*
*/
int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event) {
LOG("configure-request, serial %d\n", event->sequence);
LOG("event->window = %08x\n", event->window);
LOG("application wants to be at %dx%d with %dx%d\n", event->x, event->y, event->width, event->height);
LOG("window 0x%08x wants to be at %dx%d with %dx%d\n",
event->window, event->x, event->y, event->width, event->height);
Client *client = table_get(&by_child, event->window);
if (client == NULL) {
LOG("This client is not mapped, so we don't care and just tell the client that he will get its size\n");
uint32_t mask = 0;
uint32_t values[7];
int c = 0;
@ -478,6 +547,16 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
return 1;
}
if (client->fullscreen) {
LOG("Client is in fullscreen mode\n");
Rect child_rect = client->workspace->rect;
child_rect.x = child_rect.y = 0;
fake_configure_notify(conn, child_rect, client->child);
return 1;
}
/* Floating clients can be reconfigured */
if (client_is_floating(client)) {
i3Font *font = load_font(conn, config.font);
@ -512,16 +591,6 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
return 1;
}
if (client->fullscreen) {
LOG("Client is in fullscreen mode\n");
Rect child_rect = client->container->workspace->rect;
child_rect.x = child_rect.y = 0;
fake_configure_notify(conn, child_rect, client->child);
return 1;
}
fake_absolute_configure_notify(conn, client);
return 1;
@ -535,15 +604,12 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event) {
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
LOG("handle_configure_event for window %08x\n", event->window);
LOG("event->type = %d, \n", event->response_type);
LOG("event->x = %d, ->y = %d, ->width = %d, ->height = %d\n", event->x, event->y, event->width, event->height);
/* We ignore this sequence twice because events for child and frame should be ignored */
add_ignore_event(event->sequence);
add_ignore_event(event->sequence);
if (event->event == root) {
LOG("event->x = %d, ->y = %d, ->width = %d, ->height = %d\n", event->x, event->y, event->width, event->height);
LOG("reconfigure of the root window, need to xinerama\n");
/* FIXME: Somehow, this is occuring too often. Therefore, we check for 0/0,
but is there a better way? */
@ -569,7 +635,6 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
/* First, we need to check if the client is awaiting an unmap-request which
was generated by us reparenting the window. In that case, we just ignore it. */
if (client != NULL && client->awaiting_useless_unmap) {
LOG("Dropping this unmap request, it was generated by reparenting\n");
client->awaiting_useless_unmap = false;
return 1;
}
@ -620,6 +685,9 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
LOG("child of 0x%08x.\n", client->frame);
xcb_reparent_window(conn, client->child, root, 0, 0);
client_unmap(conn, client);
xcb_destroy_window(conn, client->frame);
xcb_flush(conn);
table_remove(&by_parent, client->frame);
@ -644,11 +712,8 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
break;
}
if (workspace_empty) {
LOG("setting ws to NULL for workspace %d (%p)\n", client->workspace->num,
client->workspace);
if (workspace_empty)
client->workspace->screen = NULL;
}
FREE(client->window_class);
FREE(client->name);
@ -669,7 +734,6 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
*/
int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
LOG("window's name changed.\n");
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
LOG("_NET_WM_NAME not specified, not changing\n");
return 1;
@ -684,7 +748,7 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop));
/* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */
char *ucs2_name = convert_utf8_to_ucs2(new_name, &new_len);
LOG("Name should change to \"%s\"\n", new_name);
LOG("_NET_WM_NAME changed to \"%s\"\n", new_name);
free(new_name);
/* Check if they are the same and dont update if so.
@ -694,7 +758,6 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
if ((new_len == client->name_len) &&
(client->name != NULL) &&
(memcmp(client->name, ucs2_name, new_len * 2) == 0)) {
LOG("Name did not change, not updating\n");
free(ucs2_name);
return 1;
}
@ -731,7 +794,6 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
*/
int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
LOG("window's name changed (legacy).\n");
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
LOG("prop == NULL\n");
return 1;
@ -740,10 +802,9 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
if (client == NULL)
return 1;
if (client->uses_net_wm_name) {
LOG("This client is capable of _NET_WM_NAME, ignoring legacy name\n");
/* Client capable of _NET_WM_NAME, ignore legacy name changes */
if (client->uses_net_wm_name)
return 1;
}
/* Save the old pointer to make the update atomic */
char *new_name;
@ -753,18 +814,17 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
return 1;
}
/* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */
LOG("Name should change to \"%s\"\n", new_name);
LOG("WM_NAME changed to \"%s\"\n", new_name);
/* Check if they are the same and dont update if so. */
if (client->name != NULL &&
strlen(new_name) == strlen(client->name) &&
strcmp(client->name, new_name) == 0) {
LOG("Name did not change, not updating\n");
free(new_name);
return 1;
}
LOG("Using legacy window title. Note that in order to get Unicode window titles in i3,"
LOG("Using legacy window title. Note that in order to get Unicode window titles in i3, "
"the application has to set _NET_WM_NAME which is in UTF-8 encoding.\n");
char *old_name = client->name;
@ -792,7 +852,6 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
*/
int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
LOG("window class changed\n");
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
LOG("prop == NULL\n");
return 1;
@ -807,15 +866,13 @@ int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
return 1;
}
LOG("changed to %s\n", new_class);
LOG("WM_CLASS changed to %s\n", new_class);
char *old_class = client->window_class;
client->window_class = new_class;
FREE(old_class);
if (!client->initialized) {
LOG("Client is not yet initialized, not putting it to floating\n");
if (!client->initialized)
return 1;
}
if (strcmp(new_class, "tools") == 0 || strcmp(new_class, "Dialog") == 0) {
LOG("tool/dialog window, should we put it floating?\n");
@ -856,11 +913,8 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
return 1;
}
LOG("got client %s\n", client->name);
if (client->dock) {
LOG("this is a dock\n");
if (client->dock)
return 1;
}
if (client->container == NULL || client->container->mode != MODE_STACK)
decorate_window(conn, client, client->frame, client->titlegc, 0);
@ -897,14 +951,10 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
*
*/
int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message_event_t *event) {
LOG("client_message\n");
if (event->type == atoms[_NET_WM_STATE]) {
if (event->format != 32 || event->data.data32[1] != atoms[_NET_WM_STATE_FULLSCREEN])
return 0;
LOG("fullscreen\n");
Client *client = table_get(&by_child, event->window);
if (client == NULL)
return 0;
@ -942,14 +992,14 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi
*/
int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
xcb_atom_t name, xcb_get_property_reply_t *reply) {
LOG("handle_normal_hints\n");
Client *client = table_get(&by_child, window);
if (client == NULL) {
LOG("No such client\n");
LOG("Received WM_SIZE_HINTS for unknown client\n");
return 1;
}
xcb_size_hints_t size_hints;
LOG("client is %08x / child %08x\n", client->frame, client->child);
CLIENT_LOG(client);
/* If the hints were already in this event, use them, if not, request them */
if (reply != NULL)
@ -958,20 +1008,24 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
xcb_get_wm_normal_hints_reply(conn, xcb_get_wm_normal_hints_unchecked(conn, client->child), &size_hints, NULL);
if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) {
LOG("min size set\n");
LOG("gots min_width = %d, min_height = %d\n", size_hints.min_width, size_hints.min_height);
// TODO: Minimum size is not yet implemented
//LOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
}
if ((size_hints.flags & XCB_SIZE_HINT_P_RESIZE_INC)) {
if (size_hints.width_inc > 0)
client->width_increment = size_hints.width_inc;
if (size_hints.height_inc > 0)
client->height_increment = size_hints.height_inc;
}
/* If no aspect ratio was set or if it was invalid, we ignore the hints */
if (!(size_hints.flags & XCB_SIZE_HINT_P_ASPECT) ||
(size_hints.min_aspect_num <= 0) ||
(size_hints.min_aspect_den <= 0)) {
LOG("No aspect ratio set, ignoring\n");
return 1;
}
LOG("window is %08x / %s\n", client->child, client->name);
int base_width = 0, base_height = 0;
/* base_width/height are the desired size of the window.
@ -991,7 +1045,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den;
LOG("min_aspect = %f, max_aspect = %f\n", min_aspect, max_aspect);
LOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
LOG("width = %f, height = %f\n", width, height);
/* Sanity checks, this is user-input, in a way */
@ -1026,7 +1080,6 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
*/
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) {
LOG("Transient hint!\n");
Client *client = table_get(&by_child, window);
if (client == NULL) {
LOG("No such client\n");
@ -1036,16 +1089,12 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_
xcb_window_t transient_for;
if (reply != NULL) {
if (!xcb_get_wm_transient_for_from_reply(&transient_for, reply)) {
LOG("Not transient for any window\n");
if (!xcb_get_wm_transient_for_from_reply(&transient_for, reply))
return 1;
}
} else {
if (!xcb_get_wm_transient_for_reply(conn, xcb_get_wm_transient_for_unchecked(conn, window),
&transient_for, NULL)) {
LOG("Not transient for any window\n");
&transient_for, NULL))
return 1;
}
}
if (client->floating == FLOATING_AUTO_OFF) {
@ -1055,3 +1104,32 @@ 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) {
if (prop == NULL) {
prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
false, window, WM_CLIENT_LEADER, WINDOW, 0, 32), NULL);
if (prop == NULL)
return 1;
}
Client *client = table_get(&by_child, window);
if (client == NULL)
return 1;
xcb_window_t *leader = xcb_get_property_value(prop);
if (leader == NULL || *leader == 0)
return 1;
LOG("Client leader 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,26 @@ 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) {
/* Distinguish if the window is currently focused… */
if (client_is_floating(client) || CUR_CELL->currently_focused == client)
last_focused = SLIST_FIRST(&(client->workspace->focus_stack));
if (client_is_floating(client)) {
if (last_focused == client)
color = &(config.client.focused);
/* …or if it is the focused window in a not focused container */
else color = &(config.client.focused_inactive);
} else color = &(config.client.unfocused);
else color = &(config.client.unfocused);
} else {
if (client->container->currently_focused == client) {
/* Distinguish if the window is currently focused… */
if (last_focused == client && c_ws == client->workspace)
color = &(config.client.focused);
/* …or if it is the focused window in a not focused container */
else color = &(config.client.focused_inactive);
} else color = &(config.client.unfocused);
}
/* Our plan is the following:
- Draw a rect around the whole client in color->background
@ -145,10 +152,15 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
}
/* Draw the lines */
xcb_draw_line(conn, drawable, gc, color->border, 0, offset, client->rect.width, offset);
xcb_draw_line(conn, drawable, gc, color->border, 2, offset + font->height + 3,
client->rect.width - 3, offset + font->height + 3);
if (client->titlebar_position != TITLEBAR_OFF) {
/* Draw the lines */
xcb_draw_line(conn, drawable, gc, color->border, 0, offset, client->rect.width, offset);
if ((client->container == NULL ||
client->container->mode != MODE_STACK ||
CIRCLEQ_NEXT_OR_NULL(&(client->container->clients), client, clients) == NULL))
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) {
@ -196,20 +208,26 @@ void reposition_client(xcb_connection_t *conn, Client *client) {
LOG("Client is on workspace %p with screen %p\n", client->workspace, client->workspace->screen);
LOG("but screen at %d, %d is %p\n", client->rect.x, client->rect.y, screen);
floating_assign_to_workspace(client, &workspaces[screen->current_workspace]);
LOG("fixed that\n");
}
/*
* Pushes the clients width/height to X11 and resizes the child window
* Pushes the clients width/height to X11 and resizes the child window. This
* function also updates the clients position, so if you work on tiling clients
* only, you can use this function instead of separate calls to reposition_client
* and resize_client to reduce flickering.
*
*/
void resize_client(xcb_connection_t *conn, Client *client) {
i3Font *font = load_font(conn, config.font);
LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
LOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
xcb_configure_window(conn, client->frame,
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
&(client->rect.width));
XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT,
&(client->rect.x));
/* Adjust the position of the child inside its frame.
* The coordinates of the child are relative to its frame, we
@ -227,11 +245,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;
@ -263,6 +286,20 @@ void resize_client(xcb_connection_t *conn, Client *client) {
LOG("new_height = %f, new_width = %d\n", new_height, new_width);
}
if (client->height_increment > 1) {
int old_height = rect->height;
rect->height = ((int)(rect->height / client->height_increment) * client->height_increment) + 1;
LOG("Lost %d pixel due to client's height_increment (%d px)\n",
old_height - rect->height, client->height_increment);
}
if (client->width_increment > 1) {
int old_width = rect->width;
rect->width = ((int)(rect->width / client->width_increment) * client->width_increment) + 1;
LOG("Lost %d pixel due to client's width_increment (%d px)\n",
old_width - rect->width, client->width_increment);
}
LOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
xcb_configure_window(conn, client->child, mask, &(rect->x));
@ -282,14 +319,10 @@ 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++;
if (container->mode == MODE_DEFAULT) {
LOG("got %d clients in this default container.\n", num_clients);
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
/* If the client is in fullscreen mode, it does not get reconfigured */
if (container->workspace->fullscreen_client == client) {
@ -302,15 +335,13 @@ void render_container(xcb_connection_t *conn, Container *container) {
if (client->force_reconfigure |
update_if_necessary(&(client->rect.x), container->x) |
update_if_necessary(&(client->rect.y), container->y +
(container->height / num_clients) * current_client))
reposition_client(conn, client);
/* TODO: vertical default layout */
if (client->force_reconfigure |
(container->height / num_clients) * current_client) |
update_if_necessary(&(client->rect.width), container->width) |
update_if_necessary(&(client->rect.height), container->height / num_clients))
resize_client(conn, client);
/* TODO: vertical default layout */
client->force_reconfigure = false;
current_client++;
@ -322,7 +353,7 @@ void render_container(xcb_connection_t *conn, Container *container) {
/* Check if we need to remap our stack title window, it gets unmapped when the container
is empty in src/handlers.c:unmap_notify() */
if (stack_win->rect.height == 0)
if (stack_win->rect.height == 0 && num_clients > 0)
xcb_map_window(conn, stack_win->window);
/* Check if we need to reconfigure our stack title window */
@ -362,6 +393,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 */
@ -374,19 +408,19 @@ void render_container(xcb_connection_t *conn, Container *container) {
* Note the bitwise OR instead of logical OR to force evaluation of both statements */
if (client->force_reconfigure |
update_if_necessary(&(client->rect.x), container->x) |
update_if_necessary(&(client->rect.y), container->y + (decoration_height * num_clients)))
reposition_client(conn, client);
if (client->force_reconfigure |
update_if_necessary(&(client->rect.y), container->y + (decoration_height * num_clients)) |
update_if_necessary(&(client->rect.width), container->width) |
update_if_necessary(&(client->rect.height), container->height - (decoration_height * num_clients)))
resize_client(conn, client);
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);
}
}
@ -395,7 +429,7 @@ static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int
SLIST_FOREACH(client, &(r_ws->screen->dock_clients), dock_clients) {
LOG("client is at %d, should be at %d\n", client->rect.y, *height);
if (client->force_reconfigure |
update_if_necessary(&(client->rect.x), 0) |
update_if_necessary(&(client->rect.x), r_ws->rect.x) |
update_if_necessary(&(client->rect.y), *height))
reposition_client(conn, client);
@ -411,11 +445,9 @@ static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int
}
static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int width, int height) {
LOG("Rendering internal bar\n");
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,21 +464,29 @@ 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");
}
/*
@ -459,8 +499,6 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bo
Client *client;
uint32_t values[1];
LOG("Ignore enter_notify = %d\n", ignore_enter_notify);
FOR_TABLE(workspace)
CIRCLEQ_FOREACH(client, &(workspace->table[cols][rows]->clients), clients) {
/* Change event mask for the decorations */
@ -494,8 +532,6 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
/* Space for the internal bar */
height -= (font->height + 6);
LOG("got %d rows and %d cols\n", r_ws->rows, r_ws->cols);
int xoffset[r_ws->rows];
int yoffset[r_ws->cols];
/* Initialize offsets */
@ -504,19 +540,12 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
for (int rows = 0; rows < r_ws->rows; rows++)
xoffset[rows] = r_ws->rect.x;
dump_table(conn, r_ws);
ignore_enter_notify_forall(conn, r_ws, true);
/* Go through the whole table and render whats necessary */
FOR_TABLE(r_ws) {
Container *container = r_ws->table[cols][rows];
int single_width = -1, single_height;
LOG("\n");
LOG("========\n");
LOG("container has %d colspan, %d rowspan\n",
container->colspan, container->rowspan);
LOG("container at %d, %d\n", xoffset[rows], yoffset[cols]);
/* Update position of the container */
container->row = rows;
container->col = cols;
@ -544,7 +573,6 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
xoffset[rows] += single_width;
yoffset[cols] += single_height;
LOG("==========\n");
}
ignore_enter_notify_forall(conn, r_ws, false);
@ -564,10 +592,8 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
void render_layout(xcb_connection_t *conn) {
i3Screen *screen;
TAILQ_FOREACH(screen, virtual_screens, screens) {
LOG("Rendering screen %d\n", screen->num);
TAILQ_FOREACH(screen, virtual_screens, screens)
render_workspace(conn, screen, &(workspaces[screen->current_workspace]));
}
xcb_flush(conn);
}

View File

@ -18,6 +18,7 @@
#include <assert.h>
#include <limits.h>
#include <locale.h>
#include <fcntl.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XKB.h>
@ -45,6 +46,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;
@ -52,6 +56,8 @@ char **start_argv;
/* This is our connection to X11 for use with XKB */
Display *xkbdpy;
xcb_key_symbols_t *keysyms;
/* The list of key bindings */
struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
@ -69,8 +75,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
@ -102,13 +112,40 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
}
}
/*
* When using xmodmap to change the keyboard mapping, this event
* is only sent via XKB. Therefore, we need this special handler.
*
*/
static void xkb_got_event(EV_P_ struct ev_io *w, int revents) {
LOG("got xkb event, yay\n");
XEvent ev;
/* When using xmodmap, every change (!) gets an own event.
* Therefore, we just read all events and only handle the
* mapping_notify once (we do not receive any other XKB
* events anyway). */
while (XPending(xkbdpy))
XNextEvent(xkbdpy, &ev);
xcb_key_symbols_free(keysyms);
keysyms = xcb_key_symbols_alloc(global_conn);
xcb_get_numlock_mask(global_conn);
ungrab_all_keys(global_conn);
LOG("Re-grabbing...\n");
grab_all_keys(global_conn);
LOG("Done\n");
}
int main(int argc, char *argv[], char *env[]) {
int i, screens, opt;
char *override_configpath = NULL;
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 +182,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 +209,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;
@ -186,6 +224,11 @@ int main(int argc, char *argv[], char *env[]) {
return 1;
}
if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) {
fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n");
return 1;
}
int i1;
if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
fprintf(stderr, "XKB not supported by X-server\n");
@ -193,18 +236,30 @@ int main(int argc, char *argv[], char *env[]) {
}
/* end of ugliness */
if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, XkbMapNotifyMask, XkbMapNotifyMask)) {
fprintf(stderr, "Could not set XKB event mask\n");
return 1;
}
/* Initialize event loop using libev */
struct ev_loop *loop = ev_default_loop(0);
if (loop == NULL)
die("Could not initialize libev. Bad LIBEV_FLAGS?\n");
struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io));
struct ev_io *xkb = scalloc(sizeof(struct ev_io));
struct ev_check *xcb_check = scalloc(sizeof(struct ev_check));
struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare));
ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
ev_io_start(loop, xcb_watcher);
ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ);
ev_io_start(loop, xkb);
/* Flush the buffer so that libev can properly get new events */
XFlush(xkbdpy);
ev_check_init(xcb_check, xcb_check_cb);
ev_check_start(loop, xcb_check);
@ -250,6 +305,13 @@ int main(int argc, char *argv[], char *env[]) {
/* Configure request = window tried to change size on its own */
xcb_event_set_configure_request_handler(&evenths, handle_configure_request, NULL);
/* Motion notify = user moved his cursor (over the root window and may
* cross virtual screen boundaries doing that) */
xcb_event_set_motion_notify_handler(&evenths, handle_motion_notify, NULL);
/* Mapping notify = keyboard mapping changed (Xmodmap), re-grab bindings */
xcb_event_set_mapping_notify_handler(&evenths, handle_mapping_notify, NULL);
/* Client message are sent to the root window. The only interesting client message
for us is _NET_WM_STATE, we honour _NET_WM_STATE_FULLSCREEN */
xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL);
@ -261,13 +323,16 @@ 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 |
XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video
projector), the root window gets a
ConfigureNotify */
XCB_EVENT_MASK_POINTER_MOTION |
XCB_EVENT_MASK_PROPERTY_CHANGE |
XCB_EVENT_MASK_ENTER_WINDOW };
xcb_change_window_attributes(conn, root, mask, values);
@ -300,6 +365,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 +382,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");
@ -323,23 +392,11 @@ int main(int argc, char *argv[], char *env[]) {
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
keysyms = xcb_key_symbols_alloc(conn);
xcb_get_numlock_mask(conn);
/* 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;
@ -356,8 +413,6 @@ int main(int argc, char *argv[], char *env[]) {
xcb_flush(conn);
manage_existing_windows(conn, &prophs, root);
/* Get pointer position to see on which screen were starting */
xcb_query_pointer_reply_t *reply;
if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
@ -370,9 +425,22 @@ int main(int argc, char *argv[], char *env[]) {
LOG("ERROR: No screen at %d x %d\n", reply->root_x, reply->root_y);
return 0;
}
if (screen->current_workspace != 0) {
LOG("Ok, I need to go to the other workspace\n");
c_ws = &workspaces[screen->current_workspace];
LOG("Starting on %d\n", screen->current_workspace);
c_ws = &workspaces[screen->current_workspace];
manage_existing_windows(conn, &prophs, root);
/* Create the UNIX domain socket for IPC */
if (config.ipc_socket_path != NULL) {
int ipc_socket = ipc_create_socket(config.ipc_socket_path);
if (ipc_socket == -1) {
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 */

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
@ -53,10 +54,8 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *pr
cookies[i] = xcb_get_window_attributes(conn, children[i]);
/* Call manage_window with the attributes for every window */
for(i = 0; i < len; ++i) {
window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
manage_window(prophs, conn, children[i], wa);
}
for(i = 0; i < len; ++i)
manage_window(prophs, conn, children[i], cookies[i], true);
free(reply);
free(cookies);
@ -66,28 +65,28 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *pr
* Do some sanity checks and then reparent the window.
*
*/
void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
LOG("managing window.\n");
void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
xcb_window_t window, xcb_get_window_attributes_cookie_t cookie,
bool needs_to_be_mapped) {
xcb_drawable_t d = { window };
xcb_get_geometry_cookie_t geomc;
xcb_get_geometry_reply_t *geom;
xcb_get_window_attributes_reply_t *attr = 0;
if (wa.tag == TAG_COOKIE) {
/* Check if the window is mapped (it could be not mapped when intializing and
calling manage_window() for every window) */
if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
return;
geomc = xcb_get_geometry(conn, d);
if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
goto out;
wa.tag = TAG_VALUE;
wa.u.override_redirect = attr->override_redirect;
/* Check if the window is mapped (it could be not mapped when intializing and
calling manage_window() for every window) */
if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
LOG("Could not get attributes\n");
return;
}
if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE)
goto out;
/* Dont manage clients with the override_redirect flag */
if (wa.u.override_redirect)
if (attr->override_redirect)
goto out;
/* Check if the window is already managed */
@ -95,13 +94,6 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_
goto out;
/* Get the initial geometry (position, size, …) */
geomc = xcb_get_geometry(conn, d);
if (!attr) {
wa.tag = TAG_COOKIE;
wa.u.cookie = xcb_get_window_attributes(conn, window);
if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
return;
}
if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
goto out;
@ -114,6 +106,7 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_
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);
@ -134,33 +127,33 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
int16_t x, int16_t y, uint16_t width, uint16_t height) {
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
utf8_title_cookie, title_cookie, class_cookie;
utf8_title_cookie, title_cookie,
class_cookie, leader_cookie;
uint32_t mask = 0;
uint32_t values[3];
uint16_t original_height = height;
bool map_frame = true;
/* We are interested in property changes */
mask = XCB_CW_EVENT_MASK;
values[0] = CHILD_EVENT_MASK;
xcb_change_window_attributes(conn, child, mask, values);
/* Map the window first to avoid flickering */
xcb_map_window(conn, child);
/* Place requests for properties ASAP */
wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX);
title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
Client *new = table_get(&by_child, child);
/* Events for already managed windows should already be filtered in manage_window() */
assert(new == NULL);
LOG("reparenting new client\n");
LOG("Reparenting window 0x%08x\n", child);
LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
new = calloc(sizeof(Client), 1);
new->force_reconfigure = true;
@ -179,6 +172,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
new->child = child;
new->rect.width = width;
new->rect.height = height;
new->width_increment = 1;
new->height_increment = 1;
/* Pre-initialize the values for floating */
new->floating_rect.x = -1;
new->floating_rect.width = width;
@ -194,8 +189,6 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
mask |= XCB_CW_EVENT_MASK;
values[1] = FRAME_EVENT_MASK;
LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
i3Font *font = load_font(conn, config.font);
width = min(width, c_ws->rect.x + c_ws->rect.width);
height = min(height, c_ws->rect.y + c_ws->rect.height);
@ -205,12 +198,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);
/* Set WM_STATE_NORMAL because GTK applications dont want to drag & drop if we dont.
* Also, xprop(1) needs that to work. */
long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
/* Put the client inside the save set. Upon termination (whether killed or normal exit
does not matter) of the window manager, these clients will be correctly reparented
@ -253,11 +241,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] ||
@ -269,6 +259,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");
@ -309,16 +305,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);
LOG("DEBUG: should have all infos now\n");
preply = xcb_get_property_reply(conn, leader_cookie, NULL);
handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
struct Assignment *assign;
TAILQ_FOREACH(assign, &assignments, assignments) {
if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
continue;
if (assign->floating) {
if (assign->floating == ASSIGN_FLOATING_ONLY ||
assign->floating == ASSIGN_FLOATING) {
new->floating = FLOATING_AUTO_ON;
LOG("Assignment matches, putting client into floating mode\n");
break;
if (assign->floating == ASSIGN_FLOATING_ONLY)
break;
}
LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
@ -329,21 +329,15 @@ 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);
t_ws->screen = c_ws->screen;
/* Copy the dimensions from the virtual screen */
memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
}
workspace_initialize(t_ws, c_ws->screen);
new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
new->workspace = t_ws;
old_focused = new->container->currently_focused;
if (t_ws->screen->current_workspace != t_ws->num)
xcb_unmap_window(conn, new->frame);
map_frame = workspace_is_visible(t_ws);
break;
}
}
@ -354,14 +348,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 */
@ -387,10 +373,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);
@ -423,4 +426,21 @@ 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)
client_map(conn, new);
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,19 +69,19 @@ void die(char *fmt, ...) {
*/
void *smalloc(size_t size) {
void *result = malloc(size);
exit_if_null(result, "Too less memory for 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, "Too less memory for calloc(%d)\n", size);
exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size);
return result;
}
char *sstrdup(const char *str) {
char *result = strdup(str);
exit_if_null(result, "Too less memory for strdup()\n");
exit_if_null(result, "Error: out of memory (strdup())\n");
return result;
}
@ -238,65 +224,6 @@ Client *get_last_focused_client(xcb_connection_t *conn, Container *container, Cl
return NULL;
}
/*
* Unmaps all clients (and stack windows) of the given workspace.
*
* This needs to be called separately when temporarily rendering
* a workspace which is not the active workspace to force
* reconfiguration of all clients, like in src/xinerama.c when
* re-assigning a workspace to another screen.
*
*/
void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) {
Client *client;
struct Stack_Window *stack_win;
/* Ignore notify events because they would cause focus to be changed */
ignore_enter_notify_forall(conn, u_ws, true);
/* Unmap all clients of the given workspace */
int unmapped_clients = 0;
FOR_TABLE(u_ws)
CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) {
LOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child);
xcb_unmap_window(conn, client->frame);
unmapped_clients++;
}
/* To find floating clients, we traverse the focus stack */
SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) {
if (!client_is_floating(client))
continue;
LOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child);
xcb_unmap_window(conn, client->frame);
unmapped_clients++;
}
/* If we did not unmap any clients, the workspace is empty and we can destroy it, at least
* if it is not the current workspace. */
if (unmapped_clients == 0 && u_ws != c_ws) {
/* Re-assign the workspace of all dock clients which use this workspace */
Client *dock;
LOG("workspace %p is empty\n", u_ws);
SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) {
if (dock->workspace != u_ws)
continue;
LOG("Re-assigning dock client to c_ws (%p)\n", c_ws);
dock->workspace = c_ws;
}
u_ws->screen = NULL;
}
/* Unmap the stack windows on the given workspace, if any */
SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
if (stack_win->container->workspace == u_ws)
xcb_unmap_window(conn, stack_win->window);
ignore_enter_notify_forall(conn, u_ws, false);
}
/*
* Sets the given client as focused by updating the data structures correctly,
@ -313,10 +240,8 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
Client *old_client = SLIST_FIRST(&(c_ws->focus_stack));
/* Check if the focus needs to be changed at all */
if (!set_anyways && (old_client == client)) {
LOG("old_client == client, not changing focus\n");
if (!set_anyways && (old_client == client))
return;
}
/* Store current_row/current_col */
c_ws->current_row = current_row;
@ -334,7 +259,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
current_row = client->container->row;
}
LOG("set_focus(frame %08x, child %08x, name %s)\n", client->frame, client->child, client->name);
CLIENT_LOG(client);
/* Set focus to the entered window, and flush xcb buffer immediately */
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME);
//xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10);
@ -362,11 +287,6 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
redecorate_window(conn, last_focused);
}
/* 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);
/* 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)) {
@ -386,6 +306,11 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
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);
/* redecorate_window flushes, so we dont need to */
redecorate_window(conn, client);
}
@ -401,7 +326,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;
@ -437,11 +363,15 @@ 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, false, 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);
stack_win->rect.height = 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;

353
src/workspace.c Normal file
View File

@ -0,0 +1,353 @@
/*
* 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 <string.h>
#include <limits.h>
#include <err.h>
#include "util.h"
#include "data.h"
#include "i3.h"
#include "config.h"
#include "xcb.h"
#include "table.h"
#include "xinerama.h"
#include "layout.h"
#include "workspace.h"
#include "client.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));
if (config.font != NULL)
ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len);
else ws->text_width = 0;
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);
}
/*
* Switches to the given workspace
*
*/
void workspace_show(xcb_connection_t *conn, int workspace) {
bool need_warp = false;
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
/* t_ws (to workspace) is just a convenience pointer to the workspace were switching to */
Workspace *t_ws = &(workspaces[workspace-1]);
LOG("show_workspace(%d)\n", workspace);
/* Store current_row/current_col */
c_ws->current_row = current_row;
c_ws->current_col = current_col;
/* Check if the workspace has not been used yet */
workspace_initialize(t_ws, c_ws->screen);
if (c_ws->screen != t_ws->screen) {
/* We need to switch to the other screen first */
LOG("moving over to other screen.\n");
/* Store the old client */
Client *old_client = CUR_CELL->currently_focused;
c_ws = &(workspaces[t_ws->screen->current_workspace]);
current_col = c_ws->current_col;
current_row = c_ws->current_row;
if (CUR_CELL->currently_focused != NULL)
need_warp = true;
else {
Rect *dims = &(c_ws->screen->rect);
xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0,
dims->x + (dims->width / 2), dims->y + (dims->height / 2));
}
/* Re-decorate the old client, its not focused anymore */
if ((old_client != NULL) && !old_client->dock)
redecorate_window(conn, old_client);
else xcb_flush(conn);
}
/* Check if we need to change something or if were already there */
if (c_ws->screen->current_workspace == (workspace-1)) {
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused != SLIST_END(&(c_ws->focus_stack))) {
set_focus(conn, last_focused, true);
if (need_warp) {
client_warp_pointer_into(conn, last_focused);
xcb_flush(conn);
}
}
return;
}
t_ws->screen->current_workspace = workspace-1;
Workspace *old_workspace = c_ws;
c_ws = &workspaces[workspace-1];
/* Unmap all clients of the old workspace */
workspace_unmap_clients(conn, old_workspace);
current_row = c_ws->current_row;
current_col = c_ws->current_col;
LOG("new current row = %d, current col = %d\n", current_row, current_col);
workspace_map_clients(conn, c_ws);
/* Restore focus on the new workspace */
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused != SLIST_END(&(c_ws->focus_stack))) {
set_focus(conn, last_focused, true);
if (need_warp) {
client_warp_pointer_into(conn, last_focused);
xcb_flush(conn);
}
} else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
render_layout(conn);
}
/*
* Parses the preferred_screen property of a workspace. You can either specify
* the screen number (it is not given that the screen numbering always stays
* the same) or the screen coordinates (exact coordinates, e.g. 1280 will match
* the screen starting at x=1280, but 1281 will not). For coordinates, you can
* either specify an x coordinate ("1280") or an y coordinate ("x800") or both
* ("1280x800").
*
*/
static i3Screen *get_screen_from_preference(struct screens_head *slist, char *preference) {
i3Screen *screen;
char *rest;
int preferred_screen = strtol(preference, &rest, 10);
LOG("Getting screen for preference \"%s\" (%d)\n", preference, preferred_screen);
if ((rest == preference) || (preferred_screen >= num_screens)) {
int x = INT_MAX, y = INT_MAX;
if (strchr(preference, 'x') != NULL) {
/* Check if only the y coordinate was specified */
if (*preference == 'x')
y = atoi(preference+1);
else {
x = atoi(preference);
y = atoi(strchr(preference, 'x') + 1);
}
} else {
x = atoi(preference);
}
LOG("Looking for screen at %d x %d\n", x, y);
TAILQ_FOREACH(screen, slist, screens)
if ((x == INT_MAX || screen->rect.x == x) &&
(y == INT_MAX || screen->rect.y == y)) {
LOG("found %p\n", screen);
return screen;
}
LOG("none found\n");
return NULL;
} else {
int c = 0;
TAILQ_FOREACH(screen, slist, screens)
if (c++ == preferred_screen)
return screen;
}
return NULL;
}
/*
* Initializes the given workspace if it is not already initialized. The given
* screen is to be understood as a fallback, if the workspace itself either
* was not assigned to a particular screen or cannot be placed there because
* the screen is not attached at the moment.
*
*/
void workspace_initialize(Workspace *ws, i3Screen *screen) {
if (ws->screen != NULL) {
LOG("Workspace already initialized\n");
return;
}
/* If this workspace has no preferred screen or if the screen it wants
* to be on is not available at the moment, we initialize it with
* the screen which was given */
if (ws->preferred_screen == NULL ||
(ws->screen = get_screen_from_preference(virtual_screens, ws->preferred_screen)) == NULL)
ws->screen = screen;
else { LOG("yay, found assignment\n"); }
/* Copy the dimensions from the virtual screen */
memcpy(&(ws->rect), &(ws->screen->rect), sizeof(Rect));
}
/*
* Gets the first unused workspace for the given screen, taking into account
* the preferred_screen setting of every workspace (workspace assignments).
*
*/
Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *screen) {
Workspace *result = NULL;
for (int c = 0; c < 10; c++) {
Workspace *ws = &(workspaces[c]);
if (ws->preferred_screen == NULL ||
!screens_are_equal(get_screen_from_preference(slist, ws->preferred_screen), screen))
continue;
result = ws;
break;
}
if (result == NULL) {
/* No assignment found, returning first unused workspace */
for (int c = 0; c < 10; c++) {
if (workspaces[c].screen != NULL)
continue;
result = &(workspaces[c]);
break;
}
}
if (result != NULL) {
workspace_initialize(result, screen);
return result;
}
LOG("WARNING: No free workspace found to assign!\n");
return NULL;
}
/*
* Maps all clients (and stack windows) of the given workspace.
*
*/
void workspace_map_clients(xcb_connection_t *conn, Workspace *ws) {
Client *client;
ignore_enter_notify_forall(conn, ws, true);
/* Map all clients on the new workspace */
FOR_TABLE(ws)
CIRCLEQ_FOREACH(client, &(ws->table[cols][rows]->clients), clients)
client_map(conn, client);
/* Map all floating clients */
if (!ws->floating_hidden)
TAILQ_FOREACH(client, &(ws->floating_clients), floating_clients)
client_map(conn, client);
/* Map all stack windows, if any */
struct Stack_Window *stack_win;
SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
if (stack_win->container->workspace == ws)
xcb_map_window(conn, stack_win->window);
ignore_enter_notify_forall(conn, ws, false);
}
/*
* Unmaps all clients (and stack windows) of the given workspace.
*
* This needs to be called separately when temporarily rendering
* a workspace which is not the active workspace to force
* reconfiguration of all clients, like in src/xinerama.c when
* re-assigning a workspace to another screen.
*
*/
void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
Client *client;
struct Stack_Window *stack_win;
/* Ignore notify events because they would cause focus to be changed */
ignore_enter_notify_forall(conn, u_ws, true);
/* Unmap all clients of the given workspace */
int unmapped_clients = 0;
FOR_TABLE(u_ws)
CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) {
LOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child);
client_unmap(conn, client);
unmapped_clients++;
}
/* To find floating clients, we traverse the focus stack */
SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) {
if (!client_is_floating(client))
continue;
LOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child);
client_unmap(conn, client);
unmapped_clients++;
}
/* If we did not unmap any clients, the workspace is empty and we can destroy it, at least
* if it is not the current workspace. */
if (unmapped_clients == 0 && u_ws != c_ws) {
/* Re-assign the workspace of all dock clients which use this workspace */
Client *dock;
LOG("workspace %p is empty\n", u_ws);
SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) {
if (dock->workspace != u_ws)
continue;
LOG("Re-assigning dock client to c_ws (%p)\n", c_ws);
dock->workspace = c_ws;
}
u_ws->screen = NULL;
}
/* Unmap the stack windows on the given workspace, if any */
SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
if (stack_win->container->workspace == u_ws)
xcb_unmap_window(conn, stack_win->window);
ignore_enter_notify_forall(conn, u_ws, false);
}

108
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,7 +121,8 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl
xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
/* Map the window (= make it visible) */
xcb_map_window(conn, result);
if (map)
xcb_map_window(conn, result);
return result;
}
@ -179,8 +181,6 @@ void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window)
xcb_send_event(conn, false, window, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&generated_event);
xcb_flush(conn);
LOG("Told the client it is at %dx%d with %dx%d\n", r.x, r.y, r.width, r.height);
}
/*
@ -259,3 +259,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

@ -27,6 +27,7 @@
#include "layout.h"
#include "xcb.h"
#include "config.h"
#include "workspace.h"
/* This TAILQ of i3Screens stores the virtual screens, used for handling overlapping screens
* (xrandr --same-as) */
@ -34,6 +35,25 @@ struct screens_head *virtual_screens;
static bool xinerama_enabled = true;
/*
* Returns true if both screen objects describe the same screen (checks their
* size and position).
*
*/
bool screens_are_equal(i3Screen *screen1, i3Screen *screen2) {
/* If one of both objects (or both) are NULL, we cannot compare them */
if (screen1 == NULL || screen2 == NULL)
return false;
/* If the pointers are equal, take the short-circuit */
if (screen1 == screen2)
return true;
/* Compare their size - other properties are not relevant to determine
* if a screen is equal to another one */
return (memcmp(&(screen1->rect), &(screen2->rect), sizeof(Rect)) == 0);
}
/*
* Looks in virtual_screens for the i3Screen whose start coordinates are x, y
*
@ -72,7 +92,7 @@ i3Screen *get_screen_containing(int x, int y) {
* This function always returns a screen.
*
*/
i3Screen *get_screen_most(direction_t direction) {
i3Screen *get_screen_most(direction_t direction, i3Screen *current) {
i3Screen *screen, *candidate = NULL;
int position = 0;
TAILQ_FOREACH(screen, virtual_screens, screens) {
@ -84,6 +104,14 @@ i3Screen *get_screen_most(direction_t direction) {
} \
break;
if (((direction == D_UP) || (direction == D_DOWN)) &&
(current->rect.x != screen->rect.x))
continue;
if (((direction == D_LEFT) || (direction == D_RIGHT)) &&
(current->rect.y != screen->rect.y))
continue;
switch (direction) {
case D_UP:
WIN(screen->rect.y, <= position);
@ -114,14 +142,12 @@ 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);
SLIST_INIT(&(screen->dock_clients));
/* Copy dimensions */
memcpy(&(workspace->rect), &(screen->rect), sizeof(Rect));
LOG("that is virtual screen at %d x %d with %d x %d\n",
screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
}
@ -142,7 +168,6 @@ static void disable_xinerama(xcb_connection_t *conn) {
num_screens = 1;
s->num = 0;
initialize_screen(conn, s, &(workspaces[0]));
TAILQ_INSERT_TAIL(virtual_screens, s, screens);
@ -175,7 +200,7 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis
for (int screen = 0; screen < screens; screen++) {
i3Screen *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org, screenlist);
if (s!= NULL) {
if (s != NULL) {
/* This screen already exists. We use the littlest screen so that the user
can always see the complete workspace */
s->rect.width = min(s->rect.width, screen_info[screen].width);
@ -235,13 +260,14 @@ void initialize_xinerama(xcb_connection_t *conn) {
FREE(reply);
i3Screen *s;
i3Screen *screen;
num_screens = 0;
/* Just go through each workspace and associate as many screens as we can. */
TAILQ_FOREACH(s, virtual_screens, screens) {
s->num = num_screens;
initialize_screen(conn, s, &(workspaces[num_screens]));
TAILQ_FOREACH(screen, virtual_screens, screens) {
screen->num = num_screens;
num_screens++;
Workspace *ws = get_first_workspace_for_screen(virtual_screens, screen);
initialize_screen(conn, screen, ws);
}
}
@ -268,65 +294,85 @@ void xinerama_requery_screens(xcb_connection_t *conn) {
query_screens(conn, new_screens);
i3Screen *first = TAILQ_FIRST(new_screens),
*screen;
*screen,
*old_screen;
int screen_count = 0;
/* Mark each workspace which currently is assigned to a screen, so we
* can garbage-collect afterwards */
for (int c = 0; c < 10; c++)
workspaces[c].reassigned = (workspaces[c].screen == NULL);
TAILQ_FOREACH(screen, new_screens, screens) {
screen->num = screen_count;
screen->current_workspace = -1;
for (int c = 0; c < 10; c++)
if ((workspaces[c].screen != NULL) &&
(workspaces[c].screen->num == screen_count)) {
LOG("Found a matching screen\n");
/* Try to use the same workspace, if its available */
if (workspaces[c].screen->current_workspace)
screen->current_workspace = workspaces[c].screen->current_workspace;
if (screen->current_workspace == -1)
screen->current_workspace = c;
TAILQ_FOREACH(old_screen, virtual_screens, screens) {
if (old_screen->num != screen_count)
continue;
/* Re-use the old bar window */
screen->bar = workspaces[c].screen->bar;
screen->bargc = workspaces[c].screen->bargc;
LOG("Found a matching screen\n");
/* Use the same workspace */
screen->current_workspace = old_screen->current_workspace;
Rect bar_rect = {screen->rect.x,
screen->rect.height - (font->height + 6),
screen->rect.x + screen->rect.width,
font->height + 6};
/* Re-use the old bar window */
screen->bar = old_screen->bar;
screen->bargc = old_screen->bargc;
LOG("old_screen->bar = %p\n", old_screen->bar);
xcb_configure_window(conn, screen->bar, XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT, &(bar_rect.x));
Rect bar_rect = {screen->rect.x,
screen->rect.height - (font->height + 6),
screen->rect.x + screen->rect.width,
font->height + 6};
/* Copy the list head for the dock clients */
screen->dock_clients = workspaces[c].screen->dock_clients;
LOG("configuring bar to be at %d x %d with %d x %d\n",
bar_rect.x, bar_rect.y, bar_rect.height, bar_rect.width);
xcb_configure_window(conn, screen->bar, XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT, &(bar_rect.x));
/* Update the dimensions */
memcpy(&(workspaces[c].rect), &(screen->rect), sizeof(Rect));
workspaces[c].screen = screen;
/* Copy the list head for the dock clients */
screen->dock_clients = old_screen->dock_clients;
/* Update the dimensions */
for (int c = 0; c < 10; c++) {
Workspace *ws = &(workspaces[c]);
if (ws->screen != old_screen)
continue;
LOG("re-assigning ws %d\n", ws->num);
memcpy(&(ws->rect), &(screen->rect), sizeof(Rect));
ws->screen = screen;
ws->reassigned = true;
}
break;
}
if (screen->current_workspace == -1) {
/* Create a new workspace for this screen, its new */
for (int c = 0; c < 10; c++)
if (workspaces[c].screen == NULL) {
LOG("fix: initializing new workspace, setting num to %d\n", c);
initialize_screen(conn, screen, &(workspaces[c]));
break;
}
/* Find the first unused workspace, preferring the ones
* which are assigned to this screen and initialize
* the screen with it. */
LOG("getting first ws for screen %p\n", screen);
Workspace *ws = get_first_workspace_for_screen(new_screens, screen);
initialize_screen(conn, screen, ws);
/* As this workspace just got visible (we got a new screen
* without workspace), we need to map its clients */
workspace_map_clients(conn, ws);
}
screen_count++;
}
/* Check for workspaces which are out of bounds */
for (int c = 0; c < 10; c++) {
if ((workspaces[c].screen == NULL) || (workspaces[c].screen->num < num_screens))
if (workspaces[c].reassigned)
continue;
/* f_ws is a shortcut to the workspace to fix */
Workspace *f_ws = &(workspaces[c]);
Client *client;
LOG("Closing bar window\n");
LOG("Closing bar window (%p)\n", f_ws->screen->bar);
xcb_destroy_window(conn, f_ws->screen->bar);
LOG("Workspace %d's screen out of bounds, assigning to first screen\n", c+1);
@ -342,10 +388,15 @@ void xinerama_requery_screens(xcb_connection_t *conn) {
render_workspace(conn, first, f_ws);
/* …unless we want to see them at the moment, we should hide that workspace */
if (first->current_workspace == c)
if (workspace_is_visible(f_ws))
continue;
unmap_workspace(conn, f_ws);
workspace_unmap_clients(conn, f_ws);
if (c_ws == f_ws) {
LOG("Need to adjust c_ws...\n");
c_ws = &(workspaces[first->current_workspace]);
}
}
xcb_flush(conn);

View File

@ -83,7 +83,7 @@ a {
<ul>
<li>
<a href="http://packages.debian.org/sid/i3">Debian GNU/Linux</a> (in unstable currently)
<a href="http://packages.debian.org/sid/i3">Debian GNU/Linux</a>
</li>
<li>
<a href="http://aur.archlinux.org/packages.php?ID=24720">Arch Linux</a>
@ -108,11 +108,17 @@ a {
<h2>Downloads</h2>
<p>
The current stable version is 3.β (transcribed 3.b because many systems still cant
The current stable version is 3.γ (transcribed 3.c because many systems still cant
handle UTF-8 in version numbers).
</p>
<ul>
<li>
<a href="/downloads/i3-3.c.tar.bz2">i3-3.c.tar.bz2</a>
(<a href="/downloads/i3-3.c.tar.bz2.asc">GPG signature</a>), Version 3.γ, 107 KiB, 2009-08-19,
<a href="/downloads/RELEASE-NOTES-3.c.txt">release notes</a>
</li>
<li>
<a href="/downloads/i3-3.b.tar.bz2">i3-3.b.tar.bz2</a>
(<a href="/downloads/i3-3.b.tar.bz2.asc">GPG signature</a>), Version 3.β, 96 KiB, 2009-06-26,