Merge branch 'next'
Conflicts: src/manage.c src/util.c src/xinerama.c
This commit is contained in:
commit
ec0113f631
87
Makefile
87
Makefile
@ -1,72 +1,6 @@
|
||||
UNAME=$(shell uname)
|
||||
DEBUG=1
|
||||
INSTALL=install
|
||||
GIT_VERSION=$(shell git describe --tags --always)
|
||||
VERSION=$(shell git describe --tags --abbrev=0)
|
||||
TOPDIR=$(shell pwd)
|
||||
|
||||
CFLAGS += -std=c99
|
||||
CFLAGS += -pipe
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += -Wunused
|
||||
CFLAGS += -Iinclude
|
||||
CFLAGS += -I/usr/local/include
|
||||
CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\"
|
||||
|
||||
# Check if pkg-config is installed, because without pkg-config, the following
|
||||
# check for the version of libxcb cannot be done.
|
||||
ifeq ($(shell which pkg-config 2>/dev/null 1>/dev/null || echo 1),1)
|
||||
$(error "pkg-config was not found")
|
||||
endif
|
||||
|
||||
ifeq ($(shell pkg-config --exists xcb-keysyms || echo 1),1)
|
||||
$(error "pkg-config could not find xcb-keysyms.pc")
|
||||
endif
|
||||
|
||||
ifeq ($(shell pkg-config --exact-version=0.3.3 xcb-keysyms && echo 1),1)
|
||||
# xcb-keysyms fixed API from 0.3.3 to 0.3.4, so for some months, we will
|
||||
# have this here. Distributions should upgrade their libxcb in the meantime.
|
||||
CFLAGS += -DOLD_XCB_KEYSYMS_API
|
||||
endif
|
||||
|
||||
LDFLAGS += -lm
|
||||
LDFLAGS += -lxcb-event
|
||||
LDFLAGS += -lxcb-property
|
||||
LDFLAGS += -lxcb-keysyms
|
||||
LDFLAGS += -lxcb-atom
|
||||
LDFLAGS += -lxcb-aux
|
||||
LDFLAGS += -lxcb-icccm
|
||||
LDFLAGS += -lxcb-xinerama
|
||||
LDFLAGS += -lX11
|
||||
LDFLAGS += -lev
|
||||
LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib
|
||||
|
||||
ifeq ($(UNAME),NetBSD)
|
||||
# We need -idirafter instead of -I to prefer the system’s iconv over GNU libiconv
|
||||
CFLAGS += -idirafter /usr/pkg/include
|
||||
LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME),FreeBSD)
|
||||
LDFLAGS += -liconv
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME),Linux)
|
||||
CFLAGS += -D_GNU_SOURCE
|
||||
endif
|
||||
|
||||
ifeq ($(DEBUG),1)
|
||||
# Extended debugging flags, macros shall be available in gcc
|
||||
CFLAGS += -gdwarf-2
|
||||
CFLAGS += -g3
|
||||
else
|
||||
CFLAGS += -O2
|
||||
endif
|
||||
|
||||
# Don’t print command lines which are run
|
||||
.SILENT:
|
||||
|
||||
# Always remake the following targets
|
||||
.PHONY: install clean dist distclean
|
||||
include $(TOPDIR)/common.mk
|
||||
|
||||
# Depend on the object files of all source-files in src/*.c and on all header files
|
||||
FILES=$(patsubst %.c,%.o,$(wildcard src/*.c))
|
||||
@ -80,6 +14,9 @@ src/%.o: src/%.c ${HEADERS}
|
||||
all: ${FILES}
|
||||
echo "LINK i3"
|
||||
$(CC) -o i3 ${FILES} $(LDFLAGS)
|
||||
echo ""
|
||||
echo "SUBDIR i3-msg"
|
||||
$(MAKE) TOPDIR=$(TOPDIR) -C i3-msg
|
||||
|
||||
install: all
|
||||
echo "INSTALL"
|
||||
@ -89,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
41
RELEASE-NOTES-3.c
Normal 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: Don’t 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 don’t 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: Don’t hide assigned clients to inactive but visible workspaces
|
||||
|
||||
-- Michael Stapelberg, 2009-08-19
|
70
common.mk
Normal file
70
common.mk
Normal 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 system’s iconv over GNU libiconv
|
||||
CFLAGS += -idirafter /usr/pkg/include
|
||||
LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME),FreeBSD)
|
||||
LDFLAGS += -liconv
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME),Linux)
|
||||
CFLAGS += -D_GNU_SOURCE
|
||||
endif
|
||||
|
||||
ifeq ($(DEBUG),1)
|
||||
# Extended debugging flags, macros shall be available in gcc
|
||||
CFLAGS += -gdwarf-2
|
||||
CFLAGS += -g3
|
||||
else
|
||||
CFLAGS += -O2
|
||||
endif
|
||||
|
||||
# Don’t print command lines which are run
|
||||
.SILENT:
|
||||
|
||||
# Always remake the following targets
|
||||
.PHONY: install clean dist distclean
|
||||
|
27
debian/changelog
vendored
27
debian/changelog
vendored
@ -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: Don’t 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 don’t 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: Don’t 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
2
debian/control
vendored
@ -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
510
docs/GPN-2009-06-27/i3.tex
Normal 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)}, user’s 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}
|
BIN
docs/GPN-2009-06-27/reparenting.dia
Normal file
BIN
docs/GPN-2009-06-27/reparenting.dia
Normal file
Binary file not shown.
1799
docs/GPN-2009-06-27/reparenting.eps
Normal file
1799
docs/GPN-2009-06-27/reparenting.eps
Normal file
File diff suppressed because it is too large
Load Diff
BIN
docs/GPN-2009-06-27/screenshot.png
Normal file
BIN
docs/GPN-2009-06-27/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
8166
docs/GPN-2009-06-27/screenshot.ps
Normal file
8166
docs/GPN-2009-06-27/screenshot.ps
Normal file
File diff suppressed because it is too large
Load Diff
1708
docs/GPN-2009-06-27/xft.eps
Normal file
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
BIN
docs/GPN-2009-06-27/xft.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
BIN
docs/GPN-2009-06-27/xserver_konzept.dia
Normal file
BIN
docs/GPN-2009-06-27/xserver_konzept.dia
Normal file
Binary file not shown.
1340
docs/GPN-2009-06-27/xserver_konzept.eps
Normal file
1340
docs/GPN-2009-06-27/xserver_konzept.eps
Normal file
File diff suppressed because it is too large
Load Diff
@ -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.
|
||||
|
||||
|
291
docs/userguide
291
docs/userguide
@ -1,7 +1,7 @@
|
||||
i3 User’s 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 don’t 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), you’d need to have to match on Firefox
|
||||
in this case.
|
||||
|
||||
You can use the special workspace +~+ to specify that matching clients should
|
||||
be put into floating mode.
|
||||
You can prefix or suffix workspaces with a `~` to specify that matching clients
|
||||
should be put into floating mode. If you specify only a `~`, the client will
|
||||
not be put onto any workspace, but will be set floating on the current one.
|
||||
|
||||
*Syntax*:
|
||||
----------------------------------------------------
|
||||
assign ["]window class[/window title]["] [→] workspace
|
||||
----------------------------------------------------
|
||||
------------------------------------------------------------
|
||||
assign ["]window class[/window title]["] [→] [~ | workspace]
|
||||
------------------------------------------------------------
|
||||
|
||||
*Examples*:
|
||||
----------------------
|
||||
@ -273,7 +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 you’ve achieved some important goal. Instead
|
||||
of figuring out how to navigate to your mailclient, it would be more convenient to
|
||||
have a shortcut.
|
||||
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 you’ve achieved some important goal. Instead
|
||||
of figuring out how to navigate to your mailclient, it would be more convenient to
|
||||
have a shortcut.
|
||||
|
||||
*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 don’t 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
28
i3-msg/Makefile
Normal 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
113
i3-msg/main.c
Normal 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, it’s 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;
|
||||
}
|
@ -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 doesn’t 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 doesn’t matter) below the
|
||||
* first floating client, so that floating windows are always on top.
|
||||
*
|
||||
*/
|
||||
void client_set_below_floating(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Returns true if the client is floating. Makes the code more beatiful, as floating
|
||||
* is not simply a boolean, but also saves whether the user selected the current state
|
||||
* or whether it was automatically set.
|
||||
* 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 client’s 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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
334
include/data.h
334
include/data.h
@ -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.
|
||||
*
|
||||
* Let’s 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 you’re 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 you’re 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;
|
||||
|
||||
/* We’re going to paint on it, so a graphics context will be needed */
|
||||
xcb_gcontext_t gc;
|
||||
|
||||
/* The rect with which the pixmap was created */
|
||||
Rect rect;
|
||||
|
||||
/* The rect of the object to which this pixmap belongs. Necessary to
|
||||
* find out when we need to re-create the pixmap. */
|
||||
Rect *referred_rect;
|
||||
|
||||
xcb_drawable_t referred_drawable;
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains data for the windows needed to draw the titlebars on in stacking
|
||||
* mode
|
||||
*
|
||||
*/
|
||||
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 workspace’s name (in glyphs) */
|
||||
int name_len;
|
||||
|
||||
/** Width of the workspace’s name (in pixels) rendered in config.font */
|
||||
int text_width;
|
||||
|
||||
/** x, y, width, height */
|
||||
Rect rect;
|
||||
|
||||
/* table dimensions */
|
||||
/** 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. I’ve always wanted
|
||||
* to be a three-star programmer :) */
|
||||
/** This is a two-dimensional dynamic array of
|
||||
* Container-pointers. I’ve always wanted to be a three-star
|
||||
* programmer :) */
|
||||
Container ***table;
|
||||
|
||||
/* width_factor and height_factor contain the amount of space (percentage) a column/row
|
||||
has of all the space which is available for resized windows. This ensures that
|
||||
non-resized windows (newly opened, for example) have the same size as always */
|
||||
/** 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, /* don’t float, but put on a workspace */
|
||||
ASSIGN_FLOATING_ONLY, /* float, but don’t assign on a workspace */
|
||||
ASSIGN_FLOATING /* float and put on a workspace */
|
||||
} floating;
|
||||
|
||||
/** The number of the workspace to assign to. */
|
||||
int workspace;
|
||||
TAILQ_ENTRY(Assignment) assignments;
|
||||
};
|
||||
|
||||
/*
|
||||
/**
|
||||
* 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 don’t have a container, we have this workspace-backpointer */
|
||||
/** Because dock clients don’t 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 user’s choice overwrites automatic mode, of course. The order of the values
|
||||
* is important because we check with >= FLOATING_AUTO_ON if a client is floating. */
|
||||
/** floating? (= not in tiling layout) This cannot be simply a bool
|
||||
* because we want to keep track of whether the status was set by the
|
||||
* application (by setting WM_CLASS to tools for example) or by the
|
||||
* user. The user’s choice overwrites automatic mode, of course. The
|
||||
* order of the values is important because we check with >=
|
||||
* FLOATING_AUTO_ON if a client is floating. */
|
||||
enum { FLOATING_AUTO_OFF = 0, FLOATING_USER_OFF = 1, FLOATING_AUTO_ON = 2, FLOATING_USER_ON = 3 } floating;
|
||||
|
||||
/* Ensure TITLEBAR_TOP maps to 0 because we use calloc for initialization later */
|
||||
/** 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 they’re
|
||||
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 they’re unmapped, we need to ignore that
|
||||
* one. Therefore, this flag is set when reparenting. */
|
||||
bool awaiting_useless_unmap;
|
||||
|
||||
/* XCB contexts */
|
||||
xcb_window_t frame; /* Our window: The frame around the client */
|
||||
xcb_gcontext_t titlegc; /* The titlebar’s graphic context inside the frame */
|
||||
xcb_window_t child; /* The client’s window */
|
||||
xcb_window_t frame; /**< Our window: The frame around the
|
||||
* client */
|
||||
xcb_gcontext_t titlegc; /**< The titlebar’s graphic context
|
||||
* inside the frame */
|
||||
xcb_window_t child; /**< The client’s window */
|
||||
|
||||
/* The following entry provides the necessary list pointers to use Client with LIST_* macros */
|
||||
/** 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 you’re 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 you’re 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;
|
||||
|
@ -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
|
||||
|
@ -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 let’s 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
|
||||
|
@ -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
24
include/i3/ipc.h
Normal 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 (don’t do that) */
|
||||
#define I3_IPC_MAGIC "i3-ipc"
|
||||
|
||||
/** The payload of the message will be interpreted as a command */
|
||||
#define I3_IPC_MESSAGE_TYPE_COMMAND 0
|
||||
|
||||
#endif
|
35
include/ipc.h
Normal file
35
include/ipc.h
Normal 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
|
@ -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 it’s in a stacking container and
|
||||
* re-rendering the stack window or just calling decorate_window if it’s not in a stacking
|
||||
* container.
|
||||
* Redecorates the given client correctly by checking if it’s in a stacking
|
||||
* container and re-rendering the stack window or just calling decorate_window
|
||||
* if it’s not in a stacking container.
|
||||
*
|
||||
*/
|
||||
void redecorate_window(xcb_connection_t *conn, Client *client);
|
||||
@ -43,25 +46,30 @@ void redecorate_window(xcb_connection_t *conn, Client *client);
|
||||
void reposition_client(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Pushes the client’s width/height to X11 and resizes the child window
|
||||
* Pushes the client’s width/height to X11 and resizes the child window. This
|
||||
* function also updates the client’s position, so if you work on tiling clients
|
||||
* only, you can use this function instead of separate calls to reposition_client
|
||||
* and resize_client to reduce flickering.
|
||||
*
|
||||
*/
|
||||
void resize_client(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Renders the given container. Is called by render_layout() or individually (for example
|
||||
* when focus changes in a stacking container)
|
||||
* Renders the given container. Is called by render_layout() or individually
|
||||
* (for example when focus changes in a stacking container)
|
||||
*
|
||||
*/
|
||||
void render_container(xcb_connection_t *conn, Container *container);
|
||||
|
||||
/**
|
||||
* Modifies the event mask of all clients on the given workspace to either ignore or to handle
|
||||
* enter notifies. It is handy to ignore notifies because they will be sent when a window is mapped
|
||||
* under the cursor, thus when the user didn’t enter the window actively at all.
|
||||
* Modifies the event mask of all clients on the given workspace to either
|
||||
* ignore or to handle enter notifies. It is handy to ignore notifies because
|
||||
* they will be sent when a window is mapped under the cursor, thus when the
|
||||
* user didn’t enter the window actively at all.
|
||||
*
|
||||
*/
|
||||
void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bool ignore_enter_notify);
|
||||
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 don’t need to render *everything*, you should call render_container on the container
|
||||
* you want to refresh.
|
||||
* If you don’t need to render *everything*, you should call render_container
|
||||
* on the container you want to refresh.
|
||||
*
|
||||
*/
|
||||
void render_layout(xcb_connection_t *conn);
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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 table’s head */
|
||||
/**
|
||||
* Inserts one column at the table’s head
|
||||
*
|
||||
*/
|
||||
void expand_table_cols_at_head(Workspace *workspace);
|
||||
|
||||
/** Performs simple bounds checking for the given column/row */
|
||||
/**
|
||||
* 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 table’s contents in human-readable form for debugging */
|
||||
/**
|
||||
* Prints the table’s contents in human-readable form for debugging
|
||||
*
|
||||
*/
|
||||
void dump_table(xcb_connection_t *conn, Workspace *workspace);
|
||||
|
||||
#endif
|
||||
|
@ -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 application’s parent exits (immediately),
|
||||
* the application is reparented to init (process-id 1), which correctly handles
|
||||
* childs, so we don’t have to do it :-).
|
||||
* Starts the given application by passing it through a shell. We use double
|
||||
* fork to avoid zombie processes. As the started application’s parent exits
|
||||
* (immediately), the application is reparented to init (process-id 1), which
|
||||
* correctly handles childs, so we don’t have to do it :-).
|
||||
*
|
||||
* The shell is determined by looking for the SHELL environment variable. If it
|
||||
* does not exist, /bin/sh is used.
|
||||
* 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
68
include/workspace.h
Normal 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
|
@ -17,7 +17,8 @@
|
||||
#define _NET_WM_STATE_ADD 1
|
||||
#define _NET_WM_STATE_TOGGLE 2
|
||||
|
||||
/* This is the equivalent of XC_left_ptr. I’m not sure why xcb doesn’t have a constant for that. */
|
||||
/** This is the equivalent of XC_left_ptr. I’m not sure why xcb doesn’t 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 don’t 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 don’t 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 doesn’t have to define an array of values)
|
||||
* Changes a single value in the graphic context (so one doesn’t 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 client’s frame) for the given client.
|
||||
* Generates a configure_notify_event with absolute coordinates (relative to
|
||||
* the X root window, not to the client’s frame) for the given client.
|
||||
*
|
||||
*/
|
||||
void fake_absolute_configure_notify(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -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
555
logo.svg
Normal 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 |
@ -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"
|
||||
|
||||
|
66
src/client.c
66
src/client.c
@ -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 child’s position inside our window gets updated */
|
||||
client->force_reconfigure = true;
|
||||
|
||||
/* For clients inside a container, we can simply render the container.
|
||||
* If the client is floating, we need to render the whole layout */
|
||||
if (client->container != NULL)
|
||||
render_container(conn, client->container);
|
||||
else render_layout(conn);
|
||||
|
||||
redecorate_window(conn, client);
|
||||
}
|
||||
|
||||
/*
|
||||
* Unmap the client, correctly setting any state which is needed.
|
||||
*
|
||||
*/
|
||||
void client_unmap(xcb_connection_t *conn, Client *client) {
|
||||
/* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */
|
||||
long data[] = { XCB_WM_STATE_WITHDRAWN, XCB_NONE };
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
|
||||
|
||||
xcb_unmap_window(conn, client->frame);
|
||||
}
|
||||
|
||||
/*
|
||||
* Map the client, correctly restoring any state needed.
|
||||
*
|
||||
*/
|
||||
void client_map(xcb_connection_t *conn, Client *client) {
|
||||
/* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t.
|
||||
* Also, xprop(1) needs that to work. */
|
||||
long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
|
||||
|
||||
xcb_map_window(conn, client->frame);
|
||||
}
|
||||
|
249
src/commands.c
249
src/commands.c
@ -26,6 +26,9 @@
|
||||
#include "client.h"
|
||||
#include "floating.h"
|
||||
#include "xcb.h"
|
||||
#include "config.h"
|
||||
#include "workspace.h"
|
||||
#include "commands.h"
|
||||
|
||||
bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
|
||||
/* If this container is empty, we’re done */
|
||||
@ -53,7 +56,7 @@ bool focus_window_in_container(xcb_connection_t *conn, Container *container, dir
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef enum { THING_WINDOW, THING_CONTAINER } thing_t;
|
||||
typedef enum { THING_WINDOW, THING_CONTAINER, THING_SCREEN } thing_t;
|
||||
|
||||
static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) {
|
||||
LOG("focusing direction %d\n", direction);
|
||||
@ -79,6 +82,41 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
|
||||
return;
|
||||
}
|
||||
|
||||
/* For focusing screens, situation is different: we get the rect
|
||||
* of the current screen, then get the screen which is on its
|
||||
* right/left/bottom/top and just switch to the workspace on
|
||||
* the target screen. */
|
||||
if (thing == THING_SCREEN) {
|
||||
i3Screen *cs = c_ws->screen;
|
||||
assert(cs != NULL);
|
||||
Rect bounds = cs->rect;
|
||||
|
||||
if (direction == D_RIGHT)
|
||||
bounds.x += bounds.width;
|
||||
else if (direction == D_LEFT)
|
||||
bounds.x -= bounds.width;
|
||||
else if (direction == D_UP)
|
||||
bounds.y -= bounds.height;
|
||||
else bounds.y += bounds.height;
|
||||
|
||||
i3Screen *target = get_screen_containing(bounds.x, bounds.y);
|
||||
if (target == NULL) {
|
||||
LOG("Target screen NULL\n");
|
||||
/* Wrap around if the target screen is out of bounds */
|
||||
if (direction == D_RIGHT)
|
||||
target = get_screen_most(D_LEFT, 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 we’re moving it to an invisible screen, we need to unmap it */
|
||||
if (target_invisible) {
|
||||
if (!workspace_is_visible(t_ws)) {
|
||||
LOG("This workspace is not visible, unmapping\n");
|
||||
xcb_unmap_window(conn, client->frame);
|
||||
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 we’re moving it to an invisible screen, we need to unmap it */
|
||||
if (target_invisible) {
|
||||
if (!workspace_is_visible(to_container->workspace)) {
|
||||
LOG("This workspace is not visible, unmapping\n");
|
||||
xcb_unmap_window(conn, current_client->frame);
|
||||
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 we’re 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, it’s 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 we’re 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;
|
||||
}
|
||||
|
||||
/* It’s 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 screen’s contents
|
||||
* (e.g. all workspaces) with the next/previous/…
|
||||
* screen */
|
||||
LOG("Not yet implemented\n");
|
||||
continue;
|
||||
}
|
||||
if (client_is_floating(last_focused)) {
|
||||
floating_move(conn, last_focused, direction);
|
||||
continue;
|
||||
@ -1066,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");
|
||||
}
|
||||
|
256
src/config.c
256
src/config.c
@ -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);
|
||||
@ -318,6 +503,21 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath)
|
||||
free(v->value);
|
||||
free(v);
|
||||
}
|
||||
|
||||
/* Set an empty name for every workspace which got no name */
|
||||
for (int i = 0; i < 10; i++) {
|
||||
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;
|
||||
}
|
||||
|
@ -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
|
||||
|
278
src/handlers.c
278
src/handlers.c
@ -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 don’t want it,
|
||||
since it’d 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 event’s 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 client’s 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 don’t 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 don’t 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
232
src/ipc.c
Normal 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. I’m not sure for what the
|
||||
* IPC interface will be used in the future, thus I’m not implementing a
|
||||
* mechanism for arbitrarily long messages, as it seems like overkill
|
||||
* at the moment.
|
||||
*
|
||||
*/
|
||||
static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
|
||||
char buf[2048];
|
||||
int n = read(w->fd, buf, sizeof(buf));
|
||||
|
||||
/* On error or an empty message, we close the connection */
|
||||
if (n <= 0) {
|
||||
#if 0
|
||||
/* FIXME: I get these when closing a client socket,
|
||||
* therefore we just treat them as an error. Is this
|
||||
* correct? */
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
return;
|
||||
#endif
|
||||
|
||||
/* If not, there was some kind of error. We don’t bother
|
||||
* and close the connection */
|
||||
close(w->fd);
|
||||
|
||||
/* Delete the client from the list of clients */
|
||||
struct ipc_client *current;
|
||||
TAILQ_FOREACH(current, &all_clients, clients) {
|
||||
if (current->fd != w->fd)
|
||||
continue;
|
||||
|
||||
/* We can call TAILQ_REMOVE because we break out of the
|
||||
* TAILQ_FOREACH afterwards */
|
||||
TAILQ_REMOVE(&all_clients, current, clients);
|
||||
break;
|
||||
}
|
||||
|
||||
ev_io_stop(EV_A_ w);
|
||||
|
||||
LOG("IPC: client disconnected\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Terminate the message correctly */
|
||||
buf[n] = '\0';
|
||||
|
||||
/* Check if the message starts with the i3 IPC magic code */
|
||||
if (n < strlen(I3_IPC_MAGIC)) {
|
||||
LOG("IPC: message too short, ignoring\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(buf, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
|
||||
LOG("IPC: message does not start with the IPC magic\n");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t *message = (uint8_t*)buf;
|
||||
message += strlen(I3_IPC_MAGIC);
|
||||
n -= strlen(I3_IPC_MAGIC);
|
||||
|
||||
/* The next 32 bit after the magic are the message size */
|
||||
uint32_t message_size = *((uint32_t*)message);
|
||||
message += sizeof(uint32_t);
|
||||
n -= sizeof(uint32_t);
|
||||
|
||||
/* The last 32 bits of the header are the message type */
|
||||
uint32_t message_type = *((uint32_t*)message);
|
||||
message += sizeof(uint32_t);
|
||||
n -= sizeof(uint32_t);
|
||||
|
||||
ipc_handle_message(message, n, message_size, message_type);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handler for activity on the listening socket, meaning that a new client
|
||||
* has just connected and we should accept() him. Sets up the event handler
|
||||
* for activity on the new connection and inserts the file descriptor into
|
||||
* the list of clients.
|
||||
*
|
||||
*/
|
||||
void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
|
||||
struct sockaddr_un peer;
|
||||
socklen_t len = sizeof(struct sockaddr_un);
|
||||
int client;
|
||||
if ((client = accept(w->fd, (struct sockaddr*)&peer, &len)) < 0) {
|
||||
if (errno == EINTR)
|
||||
return;
|
||||
else perror("accept()");
|
||||
return;
|
||||
}
|
||||
|
||||
set_nonblock(client);
|
||||
|
||||
struct ev_io *package = calloc(sizeof(struct ev_io), 1);
|
||||
ev_io_init(package, ipc_receive_message, client, EV_READ);
|
||||
ev_io_start(EV_A_ package);
|
||||
|
||||
LOG("IPC: new client connected\n");
|
||||
|
||||
struct ipc_client *new = calloc(sizeof(struct ipc_client), 1);
|
||||
new->fd = client;
|
||||
|
||||
TAILQ_INSERT_TAIL(&all_clients, new, clients);
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates the UNIX domain socket at the given path, sets it to non-blocking
|
||||
* mode, bind()s and listen()s on it.
|
||||
*
|
||||
*/
|
||||
int ipc_create_socket(const char *filename) {
|
||||
int sockfd;
|
||||
|
||||
/* Unlink the unix domain socket before */
|
||||
unlink(filename);
|
||||
|
||||
if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
|
||||
perror("socket()");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_un addr;
|
||||
memset(&addr, 0, sizeof(struct sockaddr_un));
|
||||
addr.sun_family = AF_LOCAL;
|
||||
strcpy(addr.sun_path, filename);
|
||||
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) {
|
||||
perror("bind()");
|
||||
return -1;
|
||||
}
|
||||
|
||||
set_nonblock(sockfd);
|
||||
|
||||
if (listen(sockfd, 5) < 0) {
|
||||
perror("listen()");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return sockfd;
|
||||
}
|
142
src/layout.c
142
src/layout.c
@ -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) won’t get decorated */
|
||||
if (client->dock)
|
||||
return;
|
||||
|
||||
LOG("redecorating child %08x\n", client->child);
|
||||
if (client_is_floating(client) || client->container->currently_focused == client) {
|
||||
/* Distinguish if the window is currently focused… */
|
||||
if (client_is_floating(client) || CUR_CELL->currently_focused == client)
|
||||
last_focused = SLIST_FIRST(&(client->workspace->focus_stack));
|
||||
if (client_is_floating(client)) {
|
||||
if (last_focused == client)
|
||||
color = &(config.client.focused);
|
||||
/* …or if it is the focused window in a not focused container */
|
||||
else color = &(config.client.focused_inactive);
|
||||
} else color = &(config.client.unfocused);
|
||||
else color = &(config.client.unfocused);
|
||||
} else {
|
||||
if (client->container->currently_focused == client) {
|
||||
/* Distinguish if the window is currently focused… */
|
||||
if (last_focused == client && 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 client’s width/height to X11 and resizes the child window
|
||||
* Pushes the client’s width/height to X11 and resizes the child window. This
|
||||
* function also updates the client’s position, so if you work on tiling clients
|
||||
* only, you can use this function instead of separate calls to reposition_client
|
||||
* and resize_client to reduce flickering.
|
||||
*
|
||||
*/
|
||||
void resize_client(xcb_connection_t *conn, Client *client) {
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
|
||||
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 what’s 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);
|
||||
}
|
||||
|
116
src/mainx.c
116
src/mainx.c
@ -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 we’re 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 */
|
||||
|
140
src/manage.c
140
src/manage.c
@ -29,6 +29,7 @@
|
||||
#include "manage.h"
|
||||
#include "floating.h"
|
||||
#include "client.h"
|
||||
#include "workspace.h"
|
||||
|
||||
/*
|
||||
* Go through all existing windows (if the window manager is restarted) and manage them
|
||||
@ -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;
|
||||
|
||||
/* Don’t 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 font’s height */
|
||||
|
||||
/* Yo dawg, I heard you like windows, so I create a window around your window… */
|
||||
new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
|
||||
|
||||
/* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t.
|
||||
* Also, xprop(1) needs that to work. */
|
||||
long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, 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 it’s a dock we can’t make it float, so we break */
|
||||
new->floating = FLOATING_AUTO_OFF;
|
||||
break;
|
||||
} else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
|
||||
atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
|
||||
@ -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 we’re not in fullscreen mode and if it is not a dock window */
|
||||
if (new->container->workspace->fullscreen_client == NULL) {
|
||||
if (!client_is_floating(new))
|
||||
new->container->currently_focused = new;
|
||||
if (new->container == CUR_CELL)
|
||||
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
/* Insert into the currently active container, if it’s not a dock window */
|
||||
@ -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 GIMP’s color picker window) get mapped
|
||||
* to (0, 0), so we push them to a reasonable position
|
||||
* (centered over their leader) */
|
||||
if (new->leader != 0 && x == 0 && y == 0) {
|
||||
LOG("Floating client wants to (0x0), moving it over its leader instead\n");
|
||||
Client *leader = table_get(&by_child, new->leader);
|
||||
if (leader == NULL) {
|
||||
LOG("leader is NULL, centering it over current workspace\n");
|
||||
|
||||
x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
|
||||
y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
|
||||
} else {
|
||||
x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
|
||||
y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
|
||||
}
|
||||
}
|
||||
new->floating_rect.x = new->rect.x = x;
|
||||
new->floating_rect.y = new->rect.y = y;
|
||||
LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
|
||||
new->floating_rect.x, new->floating_rect.y,
|
||||
new->floating_rect.width, new->floating_rect.height);
|
||||
@ -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 we’re not in fullscreen mode and if it is not a dock window */
|
||||
if (new->workspace->fullscreen_client == NULL) {
|
||||
if (!client_is_floating(new))
|
||||
new->container->currently_focused = new;
|
||||
if (new->container == CUR_CELL || client_is_floating(new))
|
||||
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
110
src/util.c
110
src/util.c
@ -62,20 +62,6 @@ void slog(char *fmt, ...) {
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/*
|
||||
* Prints the message (see printf()) to stderr, then exits the program.
|
||||
*
|
||||
*/
|
||||
void die(char *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of
|
||||
* the called functions returns NULL, meaning that there is no more memory available
|
||||
@ -83,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 we’re in stacking mode, this renders the container to update changes in the title
|
||||
bars and to raise the focused client */
|
||||
if ((old_client != NULL) && (old_client != client) && !old_client->dock)
|
||||
redecorate_window(conn, old_client);
|
||||
|
||||
/* 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 we’re in stacking mode, this renders the container to update changes in the title
|
||||
bars and to raise the focused client */
|
||||
if ((old_client != NULL) && (old_client != client) && !old_client->dock)
|
||||
redecorate_window(conn, old_client);
|
||||
|
||||
/* redecorate_window flushes, so we don’t need to */
|
||||
redecorate_window(conn, client);
|
||||
}
|
||||
@ -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 it’s needed (see cached_pixmap_prepare). */
|
||||
memset(&(stack_win->pixmap), 0, sizeof(struct Cached_Pixmap));
|
||||
stack_win->pixmap.referred_rect = &stack_win->rect;
|
||||
stack_win->pixmap.referred_drawable = stack_win->window;
|
||||
|
||||
stack_win->container = container;
|
||||
|
||||
|
353
src/workspace.c
Normal file
353
src/workspace.c
Normal 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 we’re 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, it’s 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 we’re 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
108
src/xcb.c
@ -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 don’t have a table to lookup the infos per character, return the
|
||||
* minimum bounds */
|
||||
if (table == NULL)
|
||||
return &font_info->min_bounds;
|
||||
|
||||
result = &table[((row - font_info->min_byte1) *
|
||||
(font_info->max_char_or_byte2 - font_info->min_char_or_byte2 + 1)) +
|
||||
(col - font_info->min_char_or_byte2)];
|
||||
|
||||
/* If the character has an entry in the table, return it */
|
||||
if (result->character_width != 0 ||
|
||||
(result->right_side_bearing |
|
||||
result->left_side_bearing |
|
||||
result->ascent |
|
||||
result->descent) != 0)
|
||||
return result;
|
||||
|
||||
/* Otherwise, get the default character and return its charinfo */
|
||||
if (dont_fallback)
|
||||
return NULL;
|
||||
|
||||
return get_charinfo((font_info->default_char >> 8),
|
||||
(font_info->default_char & 0xFF),
|
||||
font_info,
|
||||
table,
|
||||
true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the width of the given text (16-bit characters, UCS) with given
|
||||
* real length (amount of glyphs) using the given font.
|
||||
*
|
||||
*/
|
||||
int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *text, int length) {
|
||||
xcb_query_font_reply_t *font_info;
|
||||
xcb_charinfo_t *table;
|
||||
int i, width = 0;
|
||||
i3Font *font = load_font(conn, font_pattern);
|
||||
|
||||
font_info = xcb_query_font_reply(conn, xcb_query_font_unchecked(conn, font->id), NULL);
|
||||
table = xcb_query_font_char_infos(font_info);
|
||||
|
||||
for (i = 0; i < 2 * length; i += 2) {
|
||||
xcb_charinfo_t *info = get_charinfo(text[i+1], text[i], font_info, table, false);
|
||||
if (info == NULL)
|
||||
continue;
|
||||
width += info->character_width;
|
||||
}
|
||||
|
||||
free(font_info);
|
||||
|
||||
return width;
|
||||
}
|
||||
|
145
src/xinerama.c
145
src/xinerama.c
@ -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 it’s 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, it’s 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);
|
||||
|
||||
|
@ -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 can’t
|
||||
The current stable version is 3.γ (transcribed 3.c because many systems still can’t
|
||||
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,
|
||||
|
Loading…
Reference in New Issue
Block a user