Merge branch 'next'

This commit is contained in:
Michael Stapelberg 2010-03-30 13:06:41 +02:00
commit 96128c9cfb
88 changed files with 7702 additions and 2593 deletions

19
CMDMODE
View File

@ -2,7 +2,8 @@
- Command mode
---------------------
This is the grammar for the command mode (your configuration file uses these commands, too).
This is the grammar for the 'command mode' (your configuration file
uses these commands, too).
left := <h> | <cursor-left>
right := <l> | <cursor-right>
@ -17,15 +18,17 @@ cmd := [ <times> ] [ <move> | <snap> ] <where>
with := <w> { [ <times> ] <where> }+ <space> <cmd>
jump := [ "<window class>[/<window title>]" | <workspace> [ <column> <row> ] ]
focus := focus [ <times> | floating | tiling | ft ]
(travels the focus stack backwards the given amount of times (by default 1), so
it selects the window which had the focus before you focused the current one when
specifying "focus 1".
The special values 'floating' (select the next floating window), 'tiling'
(select the next tiling window), 'ft' (if the current window is floating,
select the next tiling window and vice-versa) are also valid)
(travels the focus stack backwards, <times> number of times (by default 1).
So by specifying "focus 1" it selects the window which last had the focus
before you focused the current one window.
The following 3 special values are also valid:
'floating' (select the next floating window).
'tiling' (select the next tiling window).
'ft' (toggle tiling/floating: if the current window is floating,
select the next tiling window and vice-versa)
special := [ exec <path> | kill | exit | restart ]
input := [ <cmd> | <with> | <jump> | <focus> | <special> ]
input := [ <cmd> | <with> | <jump> | <focus> | <special> ]
you can cancel command mode by pressing escape anytime.

View File

@ -1,12 +1,14 @@
You need the following libraries. The version given is to be understand as the minimum
version. However, if any of these libraries changes the API, i3 may not compile anymore.
In that case, please try using the versions mentioned below until a fix is provided.
You need the following libraries. The version given is to be understood as the
minimum version required. However, if any of these libraries changes the API,
i3 may not compile anymore. In that case, please try using the versions
mentioned below until a fix is provided.
* xcb-proto-1.3 (2008-12-10)
* libxcb-1.1.93 (2008-12-11)
* xcb-util-0.3.3 (2009-01-31)
* libev
* flex and bison
* yajl (the IPC interface uses JSON to serialize data)
* asciidoc >= 8.3.0 for docs/hacking-howto
* asciidoc, xmlto, docbook-xml for man/i3.man
* Xlib, the one that comes with your X-Server
@ -24,6 +26,7 @@ http://xcb.freedesktop.org/dist/xcb-util-0.3.5.tar.bz2
http://libev.schmorp.de/
http://flex.sourceforge.net/
http://www.gnu.org/software/bison/
http://lloyd.github.com/yajl/
http://i3.zekjur.net/i3lock/
http://tools.suckless.org/dmenu

View File

@ -6,12 +6,22 @@ include $(TOPDIR)/common.mk
AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c
FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c))
FILES:=$(FILES:.c=.o)
HEADERS=$(wildcard include/*.h)
HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h))
# Recursively generate loglevels.h by explicitly calling make
# We need this step because we need to ensure that loglevels.h will be
# updated if necessary, but we also want to save rebuilds of the object
# files, so we cannot let the object files depend on loglevels.h.
ifeq ($(MAKECMDGOALS),loglevels.h)
UNUSED:=$(warning Generating loglevels.h)
else
UNUSED:=$(shell $(MAKE) loglevels.h)
endif
# Depend on the specific file (.c for each .o) and on all headers
src/%.o: src/%.c ${HEADERS}
echo "CC $<"
$(CC) $(CFLAGS) -c -o $@ $<
$(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/$(shell basename $< .c)/ { print NR }' loglevels.tmp))" -c -o $@ $<
all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES}
echo "LINK i3"
@ -22,25 +32,40 @@ all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES}
echo "SUBDIR i3-input"
$(MAKE) TOPDIR=$(TOPDIR) -C i3-input
src/cfgparse.yy.o: src/cfgparse.l
loglevels.h:
echo "LOGLEVELS"
for file in $$(ls src/*.c src/*.y src/*.l | grep -v 'cfgparse.\(tab\|yy\).c'); \
do \
echo $$(basename $$file .c); \
done > loglevels.tmp
(echo "char *loglevels[] = {"; for file in $$(cat loglevels.tmp); \
do \
echo "\"$$file\", "; \
done; \
echo "};") > include/loglevels.h;
src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS}
echo "LEX $<"
flex -i -o$(@:.o=.c) $<
$(CC) $(CFLAGS) -c -o $@ $(@:.o=.c)
$(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c)
src/cfgparse.y.o: src/cfgparse.y
src/cfgparse.y.o: src/cfgparse.y ${HEADERS}
echo "YACC $<"
bison --debug --verbose -b $(basename $< .y) -d $<
$(CC) $(CFLAGS) -c -o $@ $(<:.y=.tab.c)
$(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c)
install: all
echo "INSTALL"
$(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin
$(INSTALL) -d -m 0755 $(DESTDIR)/etc/i3
$(INSTALL) -d -m 0755 $(DESTDIR)/usr/share/xsessions
$(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.welcome $(DESTDIR)/etc/i3/welcome
$(INSTALL) -m 0644 i3.desktop $(DESTDIR)/usr/share/xsessions/
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
$(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/i3
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions
$(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/
$(INSTALL) -m 0755 i3-wsbar $(DESTDIR)$(PREFIX)/bin/
test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config
$(INSTALL) -m 0644 i3.welcome $(DESTDIR)$(SYSCONFDIR)/i3/welcome
$(INSTALL) -m 0644 i3.desktop $(DESTDIR)$(PREFIX)/share/xsessions/
$(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)$(PREFIX)/include/i3/
$(MAKE) TOPDIR=$(TOPDIR) -C i3-msg install
$(MAKE) TOPDIR=$(TOPDIR) -C i3-input install
@ -48,7 +73,7 @@ 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-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen Makefile i3-${VERSION}
cp DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome i3-wsbar 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
@ -64,7 +89,7 @@ dist: distclean
rm -rf i3-${VERSION}
clean:
rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.yy.c
rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.output src/cfgparse.yy.c loglevels.tmp include/loglevels.h
$(MAKE) -C docs clean
$(MAKE) -C man clean
$(MAKE) TOPDIR=$(TOPDIR) -C i3-msg clean

View File

@ -1,11 +1,15 @@
Dear package maintainer,
thanks for packaging i3. By doing so, you are improving your distribution and i3 in general.
thanks for packaging i3. By doing so, you are improving your distribution
and i3 in general.
Please read the file DEPENDS now, so you know which libraries are necessary and where to
get them from if your distribution does not already have packages for them.
Please read the file DEPENDS now, so you know which libraries are necessary
and where to get them from if your distribution does not already have
packages for them.
Please make sure the manpage for i3 will be properly created and installed
in your package.
Please make sure the manpage for i3 will be properly created and installed in your package.
On debian, this looks like this:
# Compilation
@ -17,10 +21,11 @@ On debian, this looks like this:
mkdir -p $(CURDIR)/debian/i3-wm/usr/share/man/man1
cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
If you got any questions, ideas, hints, problems or whatever, please do not
hesitate to contact me. I will help you out. Just drop me an E-Mail (find the address at
http://michael.stapelberg.de/Kontakt, scroll down to bottom), contact me using the same
address in jabber or ask on our IRC channel (#i3 on irc.twice-irc.de).
If you have any questions, ideas, hints, problems or whatever, please do not
hesitate to contact me. I will help you out. Just drop me an E-Mail (find the
address at http://michael.stapelberg.de/Kontakt, scroll down to bottom),
contact me using the same address in jabber or ask on our IRC channel:
(#i3 on irc.twice-irc.de).
Thanks again for your efforts,
Michael

View File

@ -1,13 +1,21 @@
UNAME=$(shell uname)
DEBUG=1
INSTALL=install
GIT_VERSION:=$(shell git describe --tags --always)
PREFIX=/usr
ifeq ($(PREFIX),/usr)
SYSCONFDIR=/etc
else
SYSCONFDIR=$(PREFIX)/etc
endif
GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1))"
VERSION:=$(shell git describe --tags --abbrev=0)
CFLAGS += -std=c99
CFLAGS += -pipe
CFLAGS += -Wall
CFLAGS += -Wunused
# unused-function, unused-label, unused-variable are turned on by -Wall
# We dont want unused-parameter because of the use of many callbacks
CFLAGS += -Wunused-value
CFLAGS += -Iinclude
CFLAGS += -I/usr/local/include
CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\"
@ -36,7 +44,9 @@ LDFLAGS += -lxcb-atom
LDFLAGS += -lxcb-aux
LDFLAGS += -lxcb-icccm
LDFLAGS += -lxcb-xinerama
LDFLAGS += -lxcb-randr
LDFLAGS += -lxcb
LDFLAGS += -lyajl
LDFLAGS += -lX11
LDFLAGS += -lev
LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib
@ -48,7 +58,6 @@ LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib
endif
ifeq ($(UNAME),OpenBSD)
CFLAGS += -ftrampolines
CFLAGS += -I${X11BASE}/include
LDFLAGS += -liconv
LDFLAGS += -L${X11BASE}/lib

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
i3-wm (3.e-0) unstable; urgency=low
* NOT YET RELEASED
-- Michael Stapelberg <michael@stapelberg.de> Mon, 21 Dec 2009 23:08:01 +0100
i3-wm (3.d-bf1-1) unstable; urgency=low
* Bugfix: Dont draw window title when titlebar is disabled

15
debian/control vendored
View File

@ -3,13 +3,12 @@ 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), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison
Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, 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, flex, bison, libyajl-dev, perl
Standards-Version: 3.8.3
Homepage: http://i3.zekjur.net/
Package: i3
Architecture: any
Priority: extra
Section: x11
Depends: i3-wm, ${misc:Depends}
Recommends: i3lock, dwm-tools, i3status
@ -22,25 +21,23 @@ Description: metapackage (i3 window manager, screen locker, menu, statusbar)
Package: i3-wm
Architecture: any
Priority: extra
Section: x11
Depends: ${shlibs:Depends}, ${misc:Depends}, x11-utils
Provides: x-window-manager
Suggests: rxvt-unicode | x-terminal-emulator
Recommends: xfonts-base
Description: an improved dynamic tiling window manager
Key features of i3 are correct implementation of Xinerama (workspaces are
Key features of i3 are good support of multi-monitor setups (workspaces are
assigned to virtual screens, i3 does the right thing when attaching new
monitors), XrandR support (not done yet), horizontal and vertical columns
(think of a table) in tiling. Also, special focus is on writing clean,
readable and well documented code. i3 uses xcb for asynchronous
communication with X11, and has several measures to be very fast.
monitors), XRandR support, horizontal and vertical columns (think of a table)
in tiling. Also, special focus is on writing clean, readable and well
documented code. i3 uses XCB for asynchronous communication with X11, and has
several measures to be very fast.
.
Please be aware i3 is primarily targeted at advanced users and developers.
Package: i3-wm-dbg
Architecture: any
Priority: extra
Section: debug
Depends: i3-wm (=${binary:Version}), ${misc:Depends}
Description: Debugging symbols for the i3 window manager

2
debian/i3-wm.docs vendored
View File

@ -8,3 +8,5 @@ docs/two_columns.png
docs/two_terminals.png
docs/modes.png
docs/stacklimit.png
docs/ipc.html
docs/multi-monitor.html

1
debian/rules vendored
View File

@ -45,6 +45,7 @@ install: build
cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
cp man/i3-msg.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
cp man/i3-input.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
cp man/i3-wsbar.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
# Build architecture-independent files here.

View File

@ -1,5 +1,5 @@
all: hacking-howto.html debugging.html userguide.html
all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html
hacking-howto.html: hacking-howto
asciidoc -a toc -n $<
@ -10,6 +10,12 @@ debugging.html: debugging
userguide.html: userguide
asciidoc -a toc -n $<
ipc.html: ipc
asciidoc -a toc -n $<
multi-monitor.html: multi-monitor
asciidoc -a toc -n $<
clean:
rm -f */*.{aux,log,toc,bm,pdf,dvi}
rm -f *.log *.html

View File

@ -1,54 +1,58 @@
Debugging i3: How To
==================
====================
Michael Stapelberg <michael+i3@stapelberg.de>
April 2009
March 2010
This document describes how to debug i3 suitably for sending us useful bug reports, even
if you have no clue of C programming.
This document describes how to debug i3 suitably for sending us useful bug
reports, even if you have no clue of C programming.
First of all: Thank you for being interested in debugging i3. It really means something
to us to get your bug fixed. If you have any questions about the debugging and/or need
further help, do not hesitate to contact us!
First of all: Thank you for being interested in debugging i3. It really means
something to us to get your bug fixed. If you have any questions about the
debugging and/or need further help, do not hesitate to contact us!
== Enabling logging
i3 spits out much information onto stdout. To have a clearly defined place where logfiles
will be saved, you should redirect stdout and stderr in xsession. While youre at it,
putting each run of i3 in a separate logfile with date/time in it is a good idea to not
get confused about the different logfiles later on.
i3 spits out much information onto stdout, if told so. To have a clearly
defined place where log files will be saved, you should redirect stdout and
stderr in xsession. While youre at it, putting each run of i3 in a separate
log file with date/time in it is a good idea to not get confused about the
different log files later on.
--------------------------------------------------------------------
exec /usr/bin/i3 >/home/michael/i3/i3log-$(date +'%F-%k-%M-%S') 2>&1
exec /usr/bin/i3 -V -d all >/home/michael/i3/i3log-$(date +'%F-%k-%M-%S') 2>&1
--------------------------------------------------------------------
== Enabling coredumps
== Enabling core dumps
When i3 crashes, often you have the chance of getting a coredump (an image of the memory
of the i3 process which can be loaded into a debugger). To get a core-dump, you have to
make sure that the user limit for core dump files is set high enough. Many systems ship
with a default value which even forbids core dumps completely. To disable the limit
completely and thus enable coredumps, use the following command (in your .xsession, before
starting i3):
When i3 crashes, often you have the chance of getting a 'core dump' (an image
of the memory of the i3 process which can be loaded into a debugger). To get a
core dump, you have to make sure that the user limit for core dump files is set
high enough. Many systems ship with a default value which even forbids core
dumps completely. To disable the limit completely and thus enable core dumps,
use the following command (in your .xsession, before starting i3):
-------------------
ulimit -c unlimited
-------------------
Furthermore, to easily recognize core dumps and allow multiple of them, you should set
a custom core dump filename pattern, using a command like the following:
Furthermore, to easily recognize core dumps and allow multiple of them, you
should set a custom core dump filename pattern, using a command like the
following:
---------------------------------------------
sudo sysctl -w kernel.core_pattern=core.%e.%p
---------------------------------------------
This will generate files which have the executables file name (%e) and the process id
(%p) in it. You can save this setting across reboots using +/etc/sysctl.conf+.
This will generate files which have the executables file name (%e) and the
process id (%p) in it. You can save this setting across reboots using
+/etc/sysctl.conf+.
== Compiling with debug symbols
To actually get useful coredumps, you should make sure that your version of i3 is compiled
with debug symbols, that is, that they are not stripped during the build process. You
can check whether your executable contains symbols by issuing the following command:
To actually get useful core dumps, you should make sure that your version of i3
is compiled with debug symbols, that is, that they are not stripped during the
build process. You can check whether your executable contains symbols by
issuing the following command:
----------------
file $(which i3)
@ -60,23 +64,23 @@ You should get an output like this:
linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
------------------------------------------------------------------------------
Notice the +not stripped+, which is the important part. If you have a version which is
stripped, please have a look if your distribution provides debug symbols (package +i3-wm-dbg+
on Debian for example) or if you can turn off stripping. If nothing helps, please build
i3 from source.
Notice the +not stripped+, which is the important part. If you have a version
which is stripped, please have a look if your distribution provides debug
symbols (package +i3-wm-dbg+ on Debian for example) or if you can turn off
stripping. If nothing helps, please build i3 from source.
== Generating a backtrace
Once you have made sure that your i3 is compiled with debug symbols and that coredumps
are enabled, you can start getting some sense out of the coredumps.
Once you have made sure that your i3 is compiled with debug symbols and that
core dumps are enabled, you can start making sense out of the core dumps.
Because the coredump depends on the original executable (and its debug symbols), please
do this as soon as you encounter the problem. If you re-compile i3, your coredump might
be useless afterwards.
Because the core dump depends on the original executable (and its debug
symbols), please do this as soon as you encounter the problem. If you
re-compile i3, your core dump might be useless afterwards.
Please install +gdb+, a debugger for C. No worries, you dont need to learn it now.
Start gdb using the following command (replacing the actual name of the coredump of
course):
Please install +gdb+, a debugger for C. No worries, you dont need to learn it
now. Start gdb using the following command (replacing the actual name of the
core dump of course):
----------------------------
gdb $(which i3) core.i3.3849
@ -88,11 +92,13 @@ Then, generate a backtrace using:
backtrace full
--------------
== Sending bugreports/debugging on IRC
== Sending bug reports/debugging on IRC
When sending bugreports, please paste the relevant part of the log (if in doubt, please send us rather
too much information than too less) and the whole backtrace (if there was a coredump).
When sending bug reports, please paste the relevant part of the log (if in
doubt, please send us rather too much information than too less) and the whole
backtrace (if there was a core dump).
When debugging with us in IRC, be prepared to use a so called nopaste service such as http://nopaste.info
because pasting large amounts of text in IRC sometimes leads to incomplete lines (servers have line
When debugging with us in IRC, be prepared to use a so called nopaste service
such as http://nopaste.info or http://pastebin.com because pasting large
amounts of text in IRC sometimes leads to incomplete lines (servers have line
length limitations) or flood kicks.

View File

@ -1,72 +1,81 @@
Hacking i3: How To
==================
Michael Stapelberg <michael+i3@stapelberg.de>
May 2009
December 2009
This document is intended to be the first thing you read before looking and/or touching
i3s source code. It should contain all important information to help you understand
why things are like they are. If it does not mention something you find necessary, please
do not hesitate to contact me.
This document is intended to be the first thing you read before looking and/or
touching i3s source code. It should contain all important information to help
you understand why things are like they are. If it does not mention something
you find necessary, please do not hesitate to contact me.
== Window Managers
A window manager is not necessarily needed to run X, but it is usually used in combination
to facilitate some things. The window manager's job is to take care of the placement of
windows, to provide the user some mechanisms to change the position/size of windows and
to communicate with clients to a certain extent (for example handle fullscreen requests
of clients such as MPlayer).
A window manager is not necessarily needed to run X, but it is usually used in
combination with X to facilitate some things. The window manager's job is to
take care of the placement of windows, to provide the user with some mechanisms
to change the position/size of windows and to communicate with clients to a
certain extent (for example handle fullscreen requests of clients such as
MPlayer).
There are no different contexts in which X11 clients run, so a window manager is just another
client, like all other X11 applications. However, it handles some events which normal clients
usually dont handle.
There are no different contexts in which X11 clients run, so a window manager
is just another client, like all other X11 applications. However, it handles
some events which normal clients usually dont handle.
In the case of i3, the tasks (and order of them) are the following:
. Grab the key bindings (events will be sent upon keypress/keyrelease)
. Iterate through all existing windows (if the window manager is not started as the first
client of X) and manage them (= reparent them, create window decorations)
. Iterate through all existing windows (if the window manager is not started as
the first client of X) and manage them (reparent them, create window
decorations, etc.)
. When new windows are created, manage them
. Handle the clients `_WM_STATE` property, but only the `_WM_STATE_FULLSCREEN`
. Handle the clients `WM_NAME` property
. Handle the clients size hints to display them proportionally
. Handle the clients urgency hint
. Handle enter notifications (focus follows mouse)
. Handle button (as in mouse buttons) presses for focus/raise on click
. Handle expose events to re-draw own windows such as decorations
. React to the users commands: Change focus, Move windows, Switch workspaces,
Change the layout mode of a container (default/stacking), Start a new application,
Restart the window manager
Change the layout mode of a container (default/stacking/tabbed), start a new
application, restart the window manager
In the following chapters, each of these tasks and their implementation details will be discussed.
In the following chapters, each of these tasks and their implementation details
will be discussed.
=== Tiling window managers
Traditionally, there are two approaches to managing windows: The most common one nowadays is
floating, which means the user can freely move/resize the windows. The other approach is called
tiling, which means that your window manager distributing windows to use as much space as
possible while not overlapping.
Traditionally, there are two approaches to managing windows: The most common
one nowadays is floating, which means the user can freely move/resize the
windows. The other approach is called tiling, which means that your window
manager distributes windows to use as much space as possible while not
overlapping each other.
The idea behind tiling is that you should not need to waste your time moving/resizing windows
while you usually want to get some work done. After all, most users sooner or later tend to
lay out their windows in a way which corresponds to tiling or stacking mode in i3. Therefore,
why not let i3 do this for you? Certainly, its faster than you could ever do it.
The idea behind tiling is that you should not need to waste your time
moving/resizing windows while you usually want to get some work done. After
all, most users sooner or later tend to lay out their windows in a way which
corresponds to tiling or stacking mode in i3. Therefore, why not let i3 do this
for you? Certainly, its faster than you could ever do it.
The problem with most tiling window managers is that they are too unflexible. In my opinion, a
window manager is just another tool, and similar to vim which can edit all kinds of text files
(like source code, HTML, …) and is not limited to a specific file type, a window manager should
not limit itself to a certain layout (like dwm, awesome, …) but provide mechanisms for you to
easily create the layout you need at the moment.
The problem with most tiling window managers is that they are too unflexible.
In my opinion, a window manager is just another tool, and similar to vim which
can edit all kinds of text files (like source code, HTML, …) and is not limited
to a specific file type, a window manager should not limit itself to a certain
layout (like dwm, awesome, …) but provide mechanisms for you to easily create
the layout you need at the moment.
=== The layout table
To accomplish flexible layouts, we decided to simply use a table. The table grows and shrinks
as you need it. Each cell holds a container which then holds windows (see picture below). You
can use different layouts for each container (default layout and stacking layout).
To accomplish flexible layouts, we decided to simply use a table. The table
grows and shrinks as you need it. Each cell holds a container which then holds
windows (see picture below). You can use different layouts for each container
(default layout and stacking layout).
So, when you open a terminal and immediately open another one, they reside in the same container,
in default layout. The layout table has exactly one column, one row and therefore one cell.
When you move one of the terminals to the right, the table needs to grow. It will be expanded
to two columns and one row. This enables you to have different layouts for each container.
The table then looks like this:
So, when you open a terminal and immediately open another one, they reside in
the same container, in default layout. The layout table has exactly one column,
one row and therefore one cell. When you move one of the terminals to the
right, the table needs to grow. It will be expanded to two columns and one row.
This enables you to have different layouts for each container. The table then
looks like this:
[width="15%",cols="^,^"]
|========
@ -81,9 +90,10 @@ When moving terminal 2 to the bottom, the table will be expanded again.
| | T2
|========
You can really think of the layout table like a traditional HTML table, if youve ever
designed one. Especially col- and rowspan work equally. Below you see an example of
colspan=2 for the first container (which has T1 as window).
You can really think of the layout table like a traditional HTML table, if
youve ever designed one. Especially col- and rowspan work similarly. Below,
you see an example of colspan=2 for the first container (which has T1 as
window).
[width="15%",cols="^asciidoc"]
|========
@ -100,19 +110,30 @@ Furthermore, you can freely resize table cells.
== Files
include/data.h::
Contains data definitions used by nearly all files. You really need to read this first.
Contains data definitions used by nearly all files. You really need to read
this first.
include/*.h::
Contains forward definitions for all public functions, aswell as doxygen-compatible
comments (so if you want to get a bit more of the big picture, either browse all
header files or use doxygen if you prefer that).
Contains forward definitions for all public functions, as well as
doxygen-compatible comments (so if you want to get a bit more of the big
picture, either browse all header files or use doxygen if you prefer that).
src/cfgparse.l::
Contains the lexer for i3s configuration file, written for +flex(1)+.
src/cfgparse.y::
Contains the parser for i3s configuration file, written for +bison(1)+.
src/click.c::
Contains all functions which handle mouse button clicks (right mouse button
clicks initiate resizing and thus are relatively complex).
src/client.c::
Contains all functions which are specific to a certain client (make it
fullscreen, see if its class/name matches a pattern, kill it, …).
src/commands.c::
Parsing commands and actually execute them (focussing, moving, …).
Parsing commands and actually executing them (focusing, moving, …).
src/config.c::
Parses the configuration file.
@ -124,7 +145,7 @@ src/floating.c::
Contains functions for floating mode (mostly resizing/dragging).
src/handlers.c::
Contains all handlers for all kind of X events (new window title, new hints,
Contains all handlers for all kinds of X events (new window title, new hints,
unmapping, key presses, button presses, …).
src/ipc.c::
@ -143,9 +164,6 @@ reparents the window and inserts it into our data structures.
src/resize.c::
Contains the functions to resize columns/rows in the table.
src/resize.c::
Contains the functions to resize columns/rows in the table.
src/table.c::
Manages the most important internal data structure, the design table.
@ -159,12 +177,13 @@ src/xcb.c::
Contains wrappers to use xcb more easily.
src/xinerama.c::
(Re-)initializes the available screens and converts them to virtual screens (see below).
(Re-)initializes the available screens and converts them to virtual screens
(see below).
== Data structures
See include/data.h for documented data structures. The most important ones are explained
right here.
See include/data.h for documented data structures. The most important ones are
explained right here.
image:bigpicture.png[The Big Picture]
@ -178,37 +197,41 @@ So, the hierarchy is:
=== Virtual screens
A virtual screen (type `i3Screen`) is generated from the connected screens obtained
through Xinerama. The difference to the raw Xinerama monitors as seen when using +xrandr(1)+
is that it falls back to the lowest common resolution of the logical screens.
A virtual screen (type `i3Screen`) is generated from the connected screens
obtained through Xinerama. The difference to the raw Xinerama monitors as seen
when using +xrandr(1)+ is that it falls back to the lowest common resolution of
the logical screens.
For example, if your notebook has 1280x800 and you connect a video projector with
1024x768, set up in clone mode (+xrandr \--output VGA \--mode 1024x768 \--same-as LVDS+),
i3 will have one virtual screen.
For example, if your notebook has 1280x800 and you connect a video projector
with 1024x768, set up in clone mode (+xrandr \--output VGA \--mode 1024x768
\--same-as LVDS+), i3 will have one virtual screen.
However, if you configure it using +xrandr \--output VGA \--mode 1024x768 \--right-of LVDS+,
i3 will generate two virtual screens. For each virtual screen, a new workspace will be
assigned. New workspaces are created on the screen you are currently on.
However, if you configure it using +xrandr \--output VGA \--mode 1024x768
\--right-of LVDS+, i3 will generate two virtual screens. For each virtual
screen, a new workspace will be assigned. New workspaces are created on the
screen you are currently on.
=== Workspace
A workspace is identified by its number. Basically, you could think of workspaces
as different desks in your bureau, if you like the desktop methaphor. They just contain
different sets of windows and are completely separate of each other. Other window
managers also call this ``Virtual desktops''.
A workspace is identified by its number. Basically, you could think of
workspaces as different desks in your office, if you like the desktop
methaphor. They just contain different sets of windows and are completely
separate of each other. Other window managers also call this ``Virtual
desktops''.
=== The layout table
Each workspace has a table, which is just a two-dimensional dynamic array containing
Containers (see below). This table grows and shrinks as you need it (by moving windows
to the right you can create a new column in the table, by moving them to the bottom
you create a new row).
Each workspace has a table, which is just a two-dimensional dynamic array
containing Containers (see below). This table grows and shrinks as you need it
(by moving windows to the right you can create a new column in the table, by
moving them to the bottom you create a new row).
=== Container
A container is the content of a tables cell. It holds an arbitrary amount of windows
and has a specific layout (default layout or stack layout). Containers can consume
multiple table cells by modifying their colspan/rowspan attribute.
A container is the content of a tables cell. It holds an arbitrary amount of
windows and has a specific layout (default layout, stack layout or tabbed
layout). Containers can consume multiple table cells by modifying their
colspan/rowspan attribute.
=== Client
@ -216,20 +239,22 @@ A client is x11-speak for a window.
== List/queue macros
i3 makes heavy use of the list macros defined in BSD operating systems. To ensure
that the operating system on which i3 is compiled has all the awaited features,
i3 comes with `include/queue.h`. On BSD systems, you can use man `queue(3)`. On Linux,
you have to use google.
i3 makes heavy use of the list macros defined in BSD operating systems. To
ensure that the operating system on which i3 is compiled has all the expected
features, i3 comes with `include/queue.h`. On BSD systems, you can use man
`queue(3)`. On Linux, you have to use google (or read the source).
The lists used are `SLIST` (single linked lists) and `CIRCLEQ` (circular queues).
Usually, only forward traversal is necessary, so an `SLIST` works fine. However,
for the windows inside a container, a `CIRCLEQ` is necessary to go from the currently
The lists used are `SLIST` (single linked lists), `CIRCLEQ` (circular
queues) and TAILQ (tail queues). Usually, only forward traversal is necessary,
so an `SLIST` works fine. If inserting elements at arbitrary positions or at
the end of a list is necessary, a `TAILQ` is used instead. However, for the
windows inside a container, a `CIRCLEQ` is necessary to go from the currently
selected window to the window above/below.
== Naming conventions
There is a row of standard variables used in many events. The following names should be
chosen for those:
There is a row of standard variables used in many events. The following names
should be chosen for those:
* ``conn'' is the xcb_connection_t
* ``event'' is the event of the particular type
@ -249,116 +274,138 @@ chosen for those:
=== Grabbing the bindings
Grabbing the bindings is quite straight-forward. You pass X your combination of modifiers and
the keycode you want to grab and whether you want to grab them actively or passively. Most
bindings (everything except for bindings using Mode_switch) are grabbed passively, that is,
just the window manager gets the event and cannot replay it.
Grabbing the bindings is quite straight-forward. You pass X your combination of
modifiers and the keycode you want to grab and whether you want to grab them
actively or passively. Most bindings (everything except for bindings using
Mode_switch) are grabbed passively, that is, just the window manager gets the
event and cannot replay it.
We need to grab bindings that use Mode_switch actively because of a bug in X. When the window
manager receives the keypress/keyrelease event for an actively grabbed keycode, it has to decide
what to do with this event: It can either replay it so that other applications get it or it
can prevent other applications from receiving it.
We need to grab bindings that use Mode_switch actively because of a bug in X.
When the window manager receives the keypress/keyrelease event for an actively
grabbed keycode, it has to decide what to do with this event: It can either
replay it so that other applications get it or it can prevent other
applications from receiving it.
So, why do we need to grab keycodes actively? Because X does not set the state-property of
keypress/keyrelease events properly. The Mode_switch bit is not set and we need to get it
using XkbGetState. This means we cannot pass X our combination of modifiers containing Mode_switch
when grabbing the key and therefore need to grab the keycode itself without any modiffiers.
This means, if you bind Mode_switch + keycode 38 ("a"), i3 will grab keycode 38 ("a") and
check on each press of "a" if the Mode_switch bit is set using XKB. If yes, it will handle
the event, if not, it will replay the event.
So, why do we need to grab keycodes actively? Because X does not set the
state-property of keypress/keyrelease events properly. The Mode_switch bit is
not set and we need to get it using XkbGetState. This means we cannot pass X
our combination of modifiers containing Mode_switch when grabbing the key and
therefore need to grab the keycode itself without any modifiers. This means,
if you bind Mode_switch + keycode 38 ("a"), i3 will grab keycode 38 ("a") and
check on each press of "a" if the Mode_switch bit is set using XKB. If yes, it
will handle the event, if not, it will replay the event.
=== Handling a keypress
As mentioned in "Grabbing the bindings", upon a keypress event, i3 first gets the correct state.
As mentioned in "Grabbing the bindings", upon a keypress event, i3 first gets
the correct state.
Then, it looks through all bindings and gets the one which matches the received event.
Then, it looks through all bindings and gets the one which matches the received
event.
The bound command is parsed directly in command mode.
== Manage windows (src/mainx.c, manage_window() and reparent_window())
`manage_window()` does some checks to decide whether the window should be managed at all:
`manage_window()` does some checks to decide whether the window should be
managed at all:
* Windows have to be mapped, that is, visible on screen
* The override_redirect must not be set. Windows with override_redirect shall not be
managed by a window manager
* The override_redirect must not be set. Windows with override_redirect shall
not be managed by a window manager
Afterwards, i3 gets the intial geometry and reparents the window if it wasnt already
managed.
Afterwards, i3 gets the intial geometry and reparents the window (see
`reparent_window()`) if it wasnt already managed.
Reparenting means that for each window which is reparented, a new window, slightly larger
than the original one, is created. The original window is then reparented to the bigger one
(called "frame").
Reparenting means that for each window which is reparented, a new window,
slightly larger than the original one, is created. The original window is then
reparented to the bigger one (called "frame").
After reparenting, the window type (`_NET_WM_WINDOW_TYPE`) is checked to see whether this
window is a dock (`_NET_WM_WINDOW_TYPE_DOCK`), like dzen2 for example. Docks are handled
differently, they dont have decorations and are not assigned to a specific container.
Instead, they are positioned at the bottom of the screen. To get the height which needsd
to be reserved for the window, the `_NET_WM_STRUT_PARTIAL` property is used.
After reparenting, the window type (`_NET_WM_WINDOW_TYPE`) is checked to see
whether this window is a dock (`_NET_WM_WINDOW_TYPE_DOCK`), like dzen2 for
example. Docks are handled differently, they dont have decorations and are not
assigned to a specific container. Instead, they are positioned at the bottom
of the screen. To get the height which needsd to be reserved for the window,
the `_NET_WM_STRUT_PARTIAL` property is used.
Furthermore, the list of assignments (to other workspaces, which may be on
other screens) is checked. If the window matches one of the users criteria,
it may either be put in floating mode or moved to a different workspace. If the
target workspace is not visible, the window will not be mapped.
== What happens when an application is started?
i3 does not care for applications. All it notices is when new windows are mapped (see
`src/handlers.c`, `handle_map_request()`). The window is then reparented (see section
"Manage windows").
i3 does not care for applications. All it notices is when new windows are
mapped (see `src/handlers.c`, `handle_map_request()`). The window is then
reparented (see section "Manage windows").
After reparenting the window, `render_layout()` is called which renders the internal
layout table. The window was placed in the currently focused container and
therefore the new window and the old windows (if any) need to be moved/resized
so that the currently active layout (default mode/stacking mode) is rendered
correctly. To move/resize windows, a window is ``configured'' in X11-speak.
After reparenting the window, `render_layout()` is called which renders the
internal layout table. The new window has been placed in the currently focused
container and therefore the new window and the old windows (if any) need to be
moved/resized so that the currently active layout (default/stacking/tabbed mode)
is rendered correctly. To move/resize windows, a window is ``configured'' in
X11-speak.
Some applications, such as MPlayer obivously assume the window manager is stupid
and try to configure their windows by themselves. This generates an event called
configurerequest. i3 handles these events and tells the window the size it had
before the configurerequest (with the exception of not yet mapped windows, which
get configured like they want to, and floating windows, which can reconfigure
themselves).
Some applications, such as MPlayer obviously assume the window manager is
stupid and try to configure their windows by themselves. This generates an
event called configurerequest. i3 handles these events and tells the window the
size it had before the configurerequest (with the exception of not yet mapped
windows, which get configured like they want to, and floating windows, which
can reconfigure themselves).
== _NET_WM_STATE
Only the _NET_WM_STATE_FULLSCREEN atom is handled. It calls ``toggle_fullscreen()'' for the
specific client which just configures the client to use the whole screen on which it
currently is. Also, it is set as fullscreen_client for the i3Screen.
Only the _NET_WM_STATE_FULLSCREEN atom is handled. It calls
``toggle_fullscreen()'' for the specific client which just configures the
client to use the whole screen on which it currently is. Also, it is set as
fullscreen_client for the i3Screen.
== WM_NAME
When the WM_NAME property of a window changes, its decoration (containing the title)
is re-rendered.
When the WM_NAME property of a window changes, its decoration (containing the
title) is re-rendered. Note that WM_NAME is in COMPOUND_TEXT encoding which is
totally uncommon and cumbersome. Therefore, the _NET_WM_NAME atom will be used
if present.
== _NET_WM_NAME
Like WM_NAME, this atom contains the title of a window. However, _NET_WM_NAME
is encoded in UTF-8. i3 will recode it to UCS-2 in order to be able to pass it
to X. Using an appropriate font (ISO-10646), you can see most special
characters (every special character contained in your font).
== Size hints
Size hints specify the minimum/maximum size for a given window aswell as its aspect ratio.
At the moment, as i3 does not have a floating mode yet, only the aspect ratio is parsed.
This is important for clients like mplayer, who only set the aspect ratio and resize their
window to be as small as possible (but only with some video outputs, for example in Xv,
while when using x11, mplayer does the necessary centering for itself).
Size hints specify the minimum/maximum size for a given window as well as its
aspect ratio. This is important for clients like mplayer, who only set the
aspect ratio and resize their window to be as small as possible (but only with
some video outputs, for example in Xv, while when using x11, mplayer does the
necessary centering for itself).
So, when an aspect ratio was specified, i3 adjusts the height of the window until the
size maintains the correct aspect ratio. For the code to do this, see src/layout.c,
function resize_client().
So, when an aspect ratio was specified, i3 adjusts the height of the window
until the size maintains the correct aspect ratio. For the code to do this, see
src/layout.c, function resize_client().
== Rendering (src/layout.c, render_layout() and render_container())
There are two entry points to rendering: render_layout() and render_container(). The
former one renders all virtual screens, the currently active workspace of each virtual
screen and all containers (inside the table cells) of these workspaces using
render_container(). Therefore, if you need to render only a single container, for
example because a window was removed, added or changed its title, you should directly
call render_container().
There are several entry points to rendering: `render_layout()`,
`render_workspace()` and `render_container()`. The former one calls
`render_workspace()` for every screen, which in turn will call
`render_container()` for every container inside its layout table. Therefore, if
you need to render only a single container, for example because a window was
removed, added or changed its title, you should directly call
render_container().
Rendering consists of two steps: In the first one, in render_layout(), each container
gets its position (screen offset + offset in the table) and size (container's width
times colspan/rowspan). Then, render_container() is called:
render_container() then takes different approaches, depending on the mode the container
is in.
Rendering consists of two steps: In the first one, in `render_workspace()`, each
container gets its position (screen offset + offset in the table) and size
(container's width times colspan/rowspan). Then, `render_container()` is called,
which takes different approaches, depending on the mode the container is in:
=== Common parts
On the frame (the window which was created around the clients window for the decorations),
a black rectangle is drawn as a background for windows like MPlayer, which dont completely
fit into the frame.
On the frame (the window which was created around the clients window for the
decorations), a black rectangle is drawn as a background for windows like
MPlayer, which do not completely fit into the frame.
=== Default mode
@ -366,100 +413,112 @@ Each clients gets the containers width and an equal amount of height.
=== Stack mode
In stack mode, a window containing the decorations of all windows inside the container
is placed at the top. The currently focused window is then given the whole remaining
space.
In stack mode, a window containing the decorations of all windows inside the
container is placed at the top. The currently focused window is then given the
whole remaining space.
=== Tabbed mode
Tabbed mode is like stack mode, except that the window decorations are drawn
in one single line at the top of the container.
=== Window decorations
The window decorations consist of a rectangle in the appropriate color (depends on whether
this window is the currently focused one or the last focused one in a not focused container
or not focused at all) forming the background. Afterwards, two lighter lines are drawn
and the last step is drawing the windows title (see WM_NAME) onto it.
The window decorations consist of a rectangle in the appropriate color (depends
on whether this window is the currently focused one, the last focused one in a
not focused container or not focused at all) forming the background.
Afterwards, two lighter lines are drawn and the last step is drawing the
windows title (see WM_NAME) onto it.
=== Fullscreen windows
For fullscreen windows, the `rect` (x, y, width, height) is not changed to allow the client
to easily go back to its previous position. Instead, fullscreen windows are skipped
when rendering.
For fullscreen windows, the `rect` (x, y, width, height) is not changed to
allow the client to easily go back to its previous position. Instead,
fullscreen windows are skipped when rendering.
=== Resizing containers
By clicking and dragging the border of a container, you can resize the whole column
(respectively row) which this container is in. This is necessary to keep the table
layout working and consistent.
By clicking and dragging the border of a container, you can resize the whole
column (respectively row) which this container is in. This is necessary to keep
the table layout working and consistent.
Currently, only vertical resizing is implemented.
The resizing works similarly to the resizing of floating windows or movement of
floating windows:
The resizing works similarly to the resizing of floating windows or movement of floating
windows:
* A new, invisible window with the size of the root window is created (+grabwin+)
* Another window, 2px width and as high as your screen (or vice versa for horizontal
resizing) is created. Its background color is the border color and it is only
there to signalize the user how big the container will be (it creates the impression
of dragging the border out of the container).
* The +drag_pointer+ function of +src/floating.c+ is called to grab the pointer and
enter an own event loop which will pass all events (expose events) but motion notify
events. This function then calls the specified callback (+resize_callback+) which
does some boundary checking and moves the helper window. As soon as the mouse
button is released, this loop will be terminated.
* The new width_factor for each involved column (respectively row) will be calculated.
* A new, invisible window with the size of the root window is created
(+grabwin+)
* Another window, 2px width and as high as your screen (or vice versa for
horizontal resizing) is created. Its background color is the border color and
it is only there to inform the user how big the container will be (it
creates the impression of dragging the border out of the container).
* The +drag_pointer+ function of +src/floating.c+ is called to grab the pointer
and enter its own event loop which will pass all events (expose events) but
motion notify events. This function then calls the specified callback
(+resize_callback+) which does some boundary checking and moves the helper
window. As soon as the mouse button is released, this loop will be
terminated.
* The new width_factor for each involved column (respectively row) will be
calculated.
== User commands / commandmode (src/commands.c)
Like in vim, you can control i3 using commands. They are intended to be a powerful
alternative to lots of shortcuts, because they can be combined. There are a few special
commands, which are the following:
Like in vim, you can control i3 using commands. They are intended to be a
powerful alternative to lots of shortcuts, because they can be combined. There
are a few special commands, which are the following:
exec <command>::
Starts the given command by passing it to `/bin/sh`.
restart::
Restarts i3 by executing `argv[0]` (the path with which you started i3) without forking.
Restarts i3 by executing `argv[0]` (the path with which you started i3) without
forking.
w::
"With". This is used to select a bunch of windows. Currently, only selecting the whole
container in which the window is in, is supported by specifying "w".
"With". This is used to select a bunch of windows. Currently, only selecting
the whole container in which the window is in, is supported by specifying "w".
f, s, d::
Toggle fullscreen, stacking, default mode for the current window/container.
The other commands are to be combined with a direction. The directions are h, j, k and l,
like in vim (h = left, j = down, k = up, l = right). When you just specify the direction
keys, i3 will move the focus in that direction. You can provide "m" or "s" before the
direction to move a window respectively or snap.
The other commands are to be combined with a direction. The directions are h,
j, k and l, like in vim (h = left, j = down, k = up, l = right). When you just
specify the direction keys, i3 will move the focus in that direction. You can
provide "m" or "s" before the direction to move a window respectively or snap.
== Gotchas
* Forgetting to call `xcb_flush(conn);` after sending a request. This usually leads to
code which looks like it works fine but which does not work under certain conditions.
* Forgetting to call `xcb_flush(conn);` after sending a request. This usually
leads to code which looks like it works fine but which does not work under
certain conditions.
== Using git / sending patches
For a short introduction into using git, see http://www.spheredev.org/wiki/Git_for_the_lazy
or, for more documentation, see http://git-scm.com/documentation
For a short introduction into using git, see
http://www.spheredev.org/wiki/Git_for_the_lazy or, for more documentation, see
http://git-scm.com/documentation
When you want to send a patch because you fixed a bug or implemented a cool feature (please
talk to us before working on features to see whether they are maybe already implemented, not
possible because of some reason or dont fit into the concept), please use git to create
a patchfile.
When you want to send a patch because you fixed a bug or implemented a cool
feature (please talk to us before working on features to see whether they are
maybe already implemented, not possible for some some reason, or dont fit
into the concept), please use git to create a patchfile.
First of all, update your working copy to the latest version of the master branch:
First of all, update your working copy to the latest version of the master
branch:
--------
git pull
--------
Afterwards, make the necessary changes for your bugfix/feature. Then, review the changes
using +git diff+ (you might want to enable colors in the diff using +git config diff.color auto+).
When you are definitely done, use +git commit -a+ to commit all changes youve made.
Afterwards, make the necessary changes for your bugfix/feature. Then, review
the changes using +git diff+ (you might want to enable colors in the diff using
+git config diff.color auto+). When you are definitely done, use +git commit
-a+ to commit all changes youve made.
Then, use the following command to generate a patchfile which we can directly apply to
the branch, preserving your commit message and name:
Then, use the following command to generate a patchfile which we can directly
apply to the branch, preserving your commit message and name:
-----------------------
git format-patch origin
-----------------------
Just send us the generated file via mail.
Just send us the generated file via email.

301
docs/ipc Normal file
View File

@ -0,0 +1,301 @@
IPC interface (interprocess communication)
==========================================
Michael Stapelberg <michael+i3@stapelberg.de>
March 2010
This document describes how to interface with i3 from a separate process. This
is useful for example to remote-control i3 (to write test cases for example) or
to get various information like the current workspaces to implement an external
workspace bar.
The method of choice for IPC in our case is a unix socket because it has very
little overhead on both sides and is usually available without headaches in
most languages. In the default configuration file, no ipc-socket path is
specified and thus no socket is created. The standard path (which +i3-msg+ and
+i3-input+ use) is +~/.i3/ipc.sock+.
== Establishing a connection
To establish a connection, simply open the IPC socket. The following code
snippet illustrates this in Perl:
-------------------------------------------------------------
use IO::Socket::UNIX;
my $sock = IO::Socket::UNIX->new(Peer => '~/.i3/ipc.sock');
-------------------------------------------------------------
== Sending messages to i3
To send a message to i3, you have to format in the binary message format which
i3 expects. This format specifies a magic string in the beginning to ensure
the integrity of messages (to prevent follow-up errors). Following the magic
string comes the length of the payload of the message as 32-bit integer, and
the type of the message as 32-bit integer (the integers are not converted, so
they are in native byte order).
The magic string currently is "i3-ipc" and will only be changed when a change
in the IPC API is done which breaks compatibility (we hope that we dont need
to do that).
Currently implemented message types are the following:
COMMAND (0)::
The payload of the message is a command for i3 (like the commands you
can bind to keys in the configuration file) and will be executed
directly after receiving it. There is no reply to this message.
GET_WORKSPACES (1)::
Gets the current workspaces. The reply will be a JSON-encoded list of
workspaces (see the reply section).
SUBSCRIBE (2)::
Subscribes your connection to certain events. See <<events>> for a
description of this message and the concept of events.
GET_OUTPUTS (3)::
Gets the current outputs. The reply will be a JSON-encoded list of outputs
(see the reply section).
So, a typical message could look like this:
--------------------------------------------------
"i3-ipc" <message length> <message type> <payload>
--------------------------------------------------
Or, as a hexdump:
------------------------------------------------------------------------------
00000000 69 33 2d 69 70 63 04 00 00 00 00 00 00 00 65 78 |i3-ipc........ex|
00000010 69 74 0a |it.|
------------------------------------------------------------------------------
To generate and send such a message, you could use the following code in Perl:
------------------------------------------------------------
sub format_ipc_command {
my ($msg) = @_;
my $len;
# Get the real byte count (vs. amount of characters)
{ use bytes; $len = length($msg); }
return "i3-ipc" . pack("LL", $len, 0) . $msg;
}
$sock->write(format_ipc_command("exit"));
------------------------------------------------------------------------------
== Receiving replies from i3
Replies from i3 usually consist of a simple string (the length of the string
is the message_length, so you can consider them length-prefixed) which in turn
contain the JSON serialization of a data structure. For example, the
GET_WORKSPACES message returns an array of workspaces (each workspace is a map
with certain attributes).
=== Reply format
The reply format is identical to the normal message format. There also is
the magic string, then the message length, then the message type and the
payload.
The following reply types are implemented:
COMMAND (0)::
Confirmation/Error code for the COMMAND message.
GET_WORKSPACES (1)::
Reply to the GET_WORKSPACES message.
SUBSCRIBE (2)::
Confirmation/Error code for the SUBSCRIBE message.
GET_OUTPUTS (3)::
Reply to the GET_OUTPUTS message.
=== COMMAND reply
The reply consists of a single serialized map. At the moment, the only
property is +success (bool)+, but this will be expanded in future versions.
*Example:*
-------------------
{ "success": true }
-------------------
=== GET_WORKSPACES reply
The reply consists of a serialized list of workspaces. Each workspace has the
following properties:
num (integer)::
The logical number of the workspace. Corresponds to the command
to switch to this workspace.
name (string)::
The name of this workspace (by default num+1), as changed by the
user. Encoded in UTF-8.
visible (boolean)::
Whether this workspace is currently visible on an output (multiple
workspaces can be visible at the same time).
focused (boolean)::
Whether this workspace currently has the focus (only one workspace
can have the focus at the same time).
urgent (boolean)::
Whether a window on this workspace has the "urgent" flag set.
rect (map)::
The rectangle of this workspace (equals the rect of the output it
is on), consists of x, y, width, height.
output (string)::
The video output this workspace is on (LVDS1, VGA1, …).
*Example:*
-------------------
[
{
"num": 0,
"name": "1",
"visible": true,
"focused": true,
"urgent": false,
"rect": {
"x": 0,
"y": 0,
"width": 1280,
"height": 800
},
"output": "LVDS1"
},
{
"num": 1,
"name": "2",
"visible": false,
"focused": false,
"urgent": false,
"rect": {
"x": 0,
"y": 0,
"width": 1280,
"height": 800
},
"output": "LVDS1"
}
]
-------------------
=== SUBSCRIBE reply
The reply consists of a single serialized map. The only property is
+success (bool)+, indicating whether the subscription was successful (the
default) or whether a JSON parse error occurred.
*Example:*
-------------------
{ "success": true }
-------------------
=== GET_OUTPUTS reply
The reply consists of a serialized list of outputs. Each output has the
following properties:
name (string)::
The name of this output (as seen in +xrandr(1)+). Encoded in UTF-8.
active (boolean)::
Whether this output is currently active (has a valid mode).
current_workspace (integer)::
The current workspace which is visible on this output. +null+ if the
output is not active.
rect (map)::
The rectangle of this output (equals the rect of the output it
is on), consists of x, y, width, height.
*Example:*
-------------------
[
{
"name": "LVDS1",
"active": true,
"current_workspace": 4,
"rect": {
"x": 0,
"y": 0,
"width": 1280,
"height": 800
}
},
{
"name": "VGA1",
"active": true,
"current_workspace": 1,
"rect": {
"x": 1280,
"y": 0,
"width": 1280,
"height": 1024
},
}
]
-------------------
== Events
[[events]]
To get informed when certain things happen in i3, clients can subscribe to
events. Events consist of a name (like "workspace") and an event reply type
(like I3_IPC_EVENT_WORKSPACE). The events sent by i3 are in the same format
as replies to specific commands.
Caveat: As soon as you subscribe to an event, it is not guaranteed any longer
that the requests to i3 are processed in order. This means, the following
situation can happen: You send a GET_WORKSPACES request but you receive a
"workspace" event before receiving the reply to GET_WORKSPACES. If your
program does not want to cope which such kinds of race conditions (an
event based library may not have a problem here), I suggest you create a
separate connection to receive events.
=== Subscribing to events
By sending a message of type SUBSCRIBE with a JSON-encoded array as payload
you can register to an event.
*Example:*
---------------------------------
type: SUBSCRIBE
payload: [ "workspace", "focus" ]
---------------------------------
=== Available events
workspace::
Sent when the user switches to a different workspace, when a new
workspace is initialized or when a workspace is removed (because the
last client vanished).
output::
Sent when RandR issues a change notification (of either screens,
outputs, CRTCs or output properties).
=== workspace event
This event consists of a single serialized map containing a property
+change (string)+ which indicates the type of the change ("focus", "init",
"empty", "urgent").
*Example:*
---------------------
{ "change": "focus" }
---------------------
=== output event
This event consists of a single serialized map containing a property
+change (string)+ which indicates the type of the change (currently only
"unspecified").
*Example:*
---------------------------
{ "change": "unspecified" }
---------------------------
== See also
For some languages, libraries are available (so you dont have to implement
all this on your own). This list names some (if you wrote one, please let me
know):
C::
i3 includes a headerfile +i3/ipc.h+ which provides you all constants.
However, there is no library yet.
Ruby::
http://github.com/badboy/i3-ipc
Perl::
http://search.cpan.org/search?query=AnyEvent::I3

BIN
docs/keyboard-layer1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

915
docs/keyboard-layer1.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 131 KiB

BIN
docs/keyboard-layer2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

896
docs/keyboard-layer2.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 129 KiB

60
docs/multi-monitor Normal file
View File

@ -0,0 +1,60 @@
The multi-monitor situation
===========================
Michael Stapelberg <michael+i3@stapelberg.de>
March 2010
…or: oh no, I have an nVidia graphics card!
== The quick fix
If you are using the nVidia binary graphics driver (also known as 'blob')
you need to use the +--force-xinerama+ flag (in your .xsession) when starting
i3, like so:
.Example:
----------------------------------------------
exec i3 --force-xinerama -V >>~/.i3/i3log 2>&1
----------------------------------------------
== The explanation
Starting with version 3.ε, i3 uses the RandR (Rotate and Resize) API instead
of Xinerama. The reason for this, is that RandR provides more information
about your outputs and connected screens than Xinerama does. To be specific,
the code which handled on-the-fly screen reconfiguration (meaning without
restarting the X server) was a very messy heuristic and most of the time did
not work correctly -- that is just not possible with the little information
Xinerama offers (just a list of screen resolutions, no identifiers for the
screens or any additional information). Xinerama simply was not designed
for dynamic configuration.
So RandR came along, as a more powerful alternative (RandR 1.2 to be specific).
It offers all of Xineramas possibilities and lots more. Using the RandR API
made our code much more robust and clean. Also, you can now reliably assign
workspaces to output names instead of some rather unreliable screen identifier
(position inside the list of screens, which could change, and so on…).
As RandR has been around for about three years as of this writing, it seemed
like a very good idea to us, and it still is a very good one. What we did not
expect, however, was the nVidia binary driver. It still does not support RandR
(as of March 2010), even though nVidia has announced that it will support RandR
eventually. What does this mean for you, if you are stuck with the binary
driver for some reason (say the free drivers dont work with your card)? First
of all, you are stuck with TwinView and cannot use +xrandr+. While this ruins
the user experience, the more grave problem is that the nVidia driver not only
does not support dynamic configuration using RandR, it also does not expose
correct multi-monitor information via the RandR API. So, in some setups, i3
will not find any screens; in others, it will find one large screen which
actually contains both of your physical screens (but it will not know that
these are two screens).
For this very reason, we decided to implement the following workaround: As
long as the nVidia driver does not support RandR, an option called
+--force-xinerama+ is available in i3. This option gets the list of screens
*once* when starting, and never updates it. As the nVidia driver cannot do
dynamic configuration anyways, this is not a big deal.
== See also
For more information on how to use multi-monitor setups, see the i3 Users
Guide.

View File

@ -1,64 +1,83 @@
i3 Users Guide
===============
Michael Stapelberg <michael+i3@stapelberg.de>
August 2009
March 2010
This document contains all information you need to configuring and using the i3
This document contains all the information you need to configure and use the i3
window manager. If it does not, please contact me on IRC, Jabber or E-Mail and
Ill help you out.
For a complete listing of the default keybindings, please see the manpage.
== Default keybindings
For the "too long; didnt read" people, here is an overview of the default
keybindings (click to see the full size image):
*Keys to use with Mod1 (alt):*
image:keyboard-layer1.png["Keys to use with Mod1 (alt)",width=600,link="keyboard-layer1.png"]
*Keys to use with Shift+Mod1:*
image:keyboard-layer2.png["Keys to use with Shift+Mod1",width=600,link="keyboard-layer2.png"]
As i3 uses keycodes in the default configuration, it does not matter which
keyboard layout you actually use. The key positions are what matters (of course
you can also use keysymbols, see <<keybindings>>).
The red keys are the modifiers you need to press (by default), the blue keys
are your homerow.
== Using i3
=== Creating terminals and moving around
=== Opening terminals and moving around
A very basic operation is to create a new terminal. By default, the keybinding
for that is Mod1+Enter, that is Alt+Enter in the default configuration. By
pressing Mod1+Enter, a new terminal will be created and it will fill the whole
space which is available on your screen.
One very basic operation is opening a new terminal. By default, the keybinding
for this is Mod1+Enter, that is Alt+Enter in the default configuration. By
pressing Mod1+Enter, a new terminal will be opened. It will fill the whole
space available on your screen.
image:single_terminal.png[Single terminal]
It is important to keep in mind that i3 uses a table to manage your windows. At
the moment, you have exactly one column and one row which leaves you with one
cell. In this cell, there is a container in which your newly opened terminal is.
cell. In this cell there is a container, which is where your new terminal is
opened.
If you now open another terminal, you still have only one cell. However, the
container has both of your terminals. So, a container is just a group of clients
with a specific layout. You can resize containers as they directly resemble
columns/rows of the layout table.
container in that cell holds both of your terminals. So, a container is just a
group of clients with a specific layout. Containers can be resized by adjusting
the size of the cell that holds them.
image:two_terminals.png[Two terminals]
To move the focus between the two terminals, you use the direction keys which
you may know from the editor +vi+. However, in i3, your homerow is used for
these keys (in +vi+, the keys are shifted to the left by one for compatibility
with most keyboard layouts). Therefore, +Mod1+J+ is left, +Mod1+K+ is down, +Mod1+L+
is up and `Mod1+;` is right. So, to switch between the terminals, use +Mod1+K+ or
+Mod1+L+.
with most keyboard layouts). Therefore, +Mod1+J+ is left, +Mod1+K+ is down,
+Mod1+L+ is up and `Mod1+;` is right. So, to switch between the terminals,
use +Mod1+K+ or +Mod1+L+.
To create a new row/column, you can simply move a terminal (or any other window)
to the direction you want to expand your table. So, lets expand the table to
the right by pressing `Mod1+Shift+;`.
To create a new row/column (and a new cell), you can simply move a terminal (or
any other window) in the direction you want to expand your table. So, lets
expand the table to the right by pressing `Mod1+Shift+;`.
image:two_columns.png[Two columns]
=== Changing mode of containers
=== Changing container modes
A container can be in different modes:
A container can have the following modes:
default::
Windows are sized so that every window gets an equal amount of space of the
Windows are sized so that every window gets an equal amount of space in the
container.
stacking::
Only the focused client of the container is displayed and you get a list of
Only the focused window in the container is displayed. You get a list of
windows at the top of the container.
tabbed::
The same principle as +stacking+, but the list of windows at the top is only
a single line which will be vertically split.
a single line which is vertically split.
To switch the mode, press +Mod1+e+ for default, +Mod1+h+ for stacking and
To switch modes, press +Mod1+e+ for default, +Mod1+h+ for stacking and
+Mod1+w+ for tabbed.
image:modes.png[Container modes]
@ -68,25 +87,29 @@ image:modes.png[Container modes]
To display a window fullscreen or to go out of fullscreen mode again, press
+Mod1+f+.
There is also a global fullscreen mode in i3 in which the client will use all
available outputs. To use it, or to get out of it again, press +Mod1+Shift+f+.
=== Opening other applications
Aside from opening applicatios from a terminal, you can also use the handy
Aside from opening applications from a terminal, you can also use the handy
+dmenu+ which is opened by pressing +Mod1+v+ by default. Just type the name
(or a part of it) of the application which you want to open. It has to be in
your +$PATH+ for that to work.
(or a part of it) of the application which you want to open. The application
typed has to be in your +$PATH+ for this to work.
Furthermore, if you have applications you open very frequently, you can also
create a keybinding for it. See the section "Configuring i3" for details.
Additionally, if you have applications you open very frequently, you can
create a keybinding for starting the application directly. See the section
"Configuring i3" for details.
=== Closing windows
If an application does not provide a mechanism to close (most applications
If an application does not provide a mechanism for closing (most applications
provide a menu, the escape key or a shortcut like +Control+W+ to close), you
can press +Mod1+Shift+q+ to kill a window. For applications which support
the WM_DELETE protocol, this will correctly close the application (saving
any modifications or doing other cleanup). If the application doesnt support
it, your X server will kill the window and the behaviour depends on the
application.
the WM_DELETE protocol your X server will kill the window and the behaviour
depends on the application.
=== Using workspaces
@ -96,13 +119,13 @@ another workspace, press +Mod1+num+ where +num+ is the number of the workspace
you want to use. If the workspace does not exist yet, it will be created.
A common paradigm is to put the web browser on one workspace, communication
applications (+mutt+, +irssi+, ...) on another one and the ones with which you
work on the third one. Of course, there is no need to follow this approach.
applications (+mutt+, +irssi+, ...) on another one, and the ones with which you
work, on the third one. Of course, there is no need to follow this approach.
If you have multiple screens, a workspace will be created on each screen. If
you open a new workspace, it will be bound to the screen you created it on.
When you switch to a workspace on another screen, i3 will set focus to this
screen.
If you have multiple screens, a workspace will be created on each screen at
startup. If you open a new workspace, it will be bound to the screen you
created it on. When you switch to a workspace on another screen, i3 will set
focus to that screen.
=== Moving windows to workspaces
@ -113,20 +136,22 @@ it does not yet exist.
=== Resizing columns/rows
To resize columns or rows just grab the border between the two columns/rows
To resize columns or rows, just grab the border between the two columns/rows
and move it to the wanted size. Please keep in mind that each cell of the table
holds a +container+ and thus you cannot horizontally resize single windows.
holds a +container+ and thus you cannot horizontally resize single windows. If
you need applications with different horizontal sizes, place them in seperate
cells one above the other.
See <<resizingconfig>> for how to configure i3 to be able to resize
columns/rows with your keyboard.
=== Restarting i3 inplace
To restart i3 inplace (and thus get it into a clean state if it has a bug, to
reload your configuration or even to upgrade to a newer version of i3) you
can use +Mod1+Shift+r+. Be aware, though, that this kills your current layout
and all the windows you have opened will be put in a default container in only
one cell. Saving the layout will be implemented in a later version.
To restart i3 inplace (and thus get into a clean state if there is a bug, or
to upgrade to a newer version of i3) you can use +Mod1+Shift+r+. Be aware,
though, that this kills your current layout and all the windows you have opened
will be put in a default container in only one cell. Saving layouts will be
implemented in a later version.
=== Exiting i3
@ -135,7 +160,7 @@ To cleanly exit i3 without killing your X server, you can use +Mod1+Shift+e+.
=== Snapping
Snapping is a mechanism to increase/decrease the colspan/rowspan of a container.
Colspan/rowspan is the amount of columns/rows a specific cell of the table
Colspan/rowspan is the number of columns/rows a specific cell of the table
consumes. This is easier explained by giving an example, so take the following
layout:
@ -146,47 +171,67 @@ by pressing +Mod1+Control+k+ (or snap container 2 rightwards).
=== Floating
Floating is the opposite of tiling mode. The position and size of a window
are then not managed by i3, but by you. Using this mode violates the tiling
Floating mode is the opposite of tiling mode. The position and size of a window
are not managed by i3, but by you. Using this mode violates the tiling
paradigm but can be useful for some corner cases like "Save as" dialog
windows or toolbar windows (GIMP or similar).
windows, or toolbar windows (GIMP or similar).
You can enable floating for a window by pressing +Mod1+Shift+Space+. By
dragging the windows titlebar with your mouse, you can move the window
around. By grabbing the borders and moving them you can resize the window.
You can enable floating mode for a window by pressing +Mod1+Shift+Space+. By
dragging the windows titlebar with your mouse you can move the window
around. By grabbing the borders and moving them you can resize the window. You
can also do that by using the <<floating_modifier>>.
Bindings for doing this with your keyboard will follow.
For resizing floating windows with your keyboard, see <<resizingconfig>>.
Floating clients are always on top of tiling clients.
Floating windows are always on top of tiling windows.
== Configuring i3
This is where the real fun begins ;-). Most things are very dependant on your
ideal working environment, so we cant make reasonable defaults for them.
ideal working environment so we cant make reasonable defaults for them.
While not using a programming language for the configuration, i3 stays
quite flexible regarding to the things you usually want your window manager
quite flexible in regards to the things you usually want your window manager
to do.
For example, you can configure bindings to jump to specific windows,
you can set specific applications to start on a specific workspace, you can
automatically start applications, you can change the colors of i3 or bind
your keys to do useful stuff.
you can set specific applications to start on specific workspaces, you can
automatically start applications, you can change the colors of i3, and you
can bind your keys to do useful things.
To change the configuration of i3, copy +/etc/i3/config+ to +~/.i3/config+
and edit it with a text editor.
To change the configuration of i3, copy +/etc/i3/config+ to +\~/.i3/config+
(or +~/.config/i3/config+ if you like the XDG directory scheme) and edit it
with a text editor.
=== General configuration
=== Comments
terminal::
Specifies the terminal emulator program you prefer. It will be started
by default when you press Mod1+Enter, but you can overwrite this. Refer
to it as +$terminal+ to keep things modular.
font::
Specifies the default font you want i3 to use. Use an X core font
descriptor here, like
+-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1+. You can
use +xfontsel(1)+ to pick one.
It is possible and recommended to use comments in your configuration file to
properly document your setup for later reference. Comments are started with
a # and can only be used at the beginning of a line:
*Examples*:
-------------------
# This is a comment
-------------------
=== Fonts
i3 uses X core fonts (not Xft) for rendering window titles and the internal
workspace bar. You can use +xfontsel(1)+ to generate such a font description.
To see special characters (Unicode), you need to use a font which supports
the ISO-10646 encoding.
*Syntax*:
------------------------------
font <X core font description>
------------------------------
*Examples*:
--------------------------------------------------------------
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
--------------------------------------------------------------
[[keybindings]]
=== Keyboard bindings
@ -194,19 +239,19 @@ 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+.
* 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 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
* Keycodes 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.
different keyboard layout (when using +xmodmap+).
My recommendation is: If you often switch keyboard layouts because you try to
learn a different one, but you want to keep your bindings at the same place,
use keycodes. If you dont switch layouts and like a clean and simple config
file, use keysyms.
My recommendation is: If you often switch keyboard layouts but you want to keep
your bindings in the same physical location on the keyboard, use keycodes.
If you dont switch layouts, and want a clean and simple config file, use
keysyms.
*Syntax*:
----------------------------------
@ -217,10 +262,10 @@ bind [Modifiers+]keycode command
*Examples*:
--------------------------------
# Fullscreen
bind Mod1+f f
bindsym Mod1+f f
# Restart
bind Mod1+Shift+r restart
bindsym Mod1+Shift+r restart
# Notebook-specific hotkeys
bind 214 exec /home/michael/toggle_beamer.sh
@ -238,14 +283,20 @@ umlauts or special characters 'and' having some comfortably reachable key
bindings. For example, when typing, capslock+1 or capslock+2 for switching
workspaces is totally convenient. Try it :-).
[[floating_modifier]]
=== The floating modifier
To move floating windows with your mouse, you can either grab their titlebar
or configure the so called floating modifier which you can then press and
click anywhere in the window itself. The most common setup is to configure
it as the same one you use for managing windows (Mod1 for example). Afterwards,
you can press Mod1, click into a window using your left mouse button and drag
it to the position you want it at.
click anywhere in the window itself to move it. The most common setup is to
use the same key you use for managing windows (Mod1 for example). Then
you can press Mod1, click into a window using your left mouse button, and drag
it to the position you want.
When holding the floating modifier, you can resize a floating window by
pressing the right mouse button on it and moving around while holding it. If
you hold the shift button as well, the resize will be proportional.
*Syntax*:
--------------------------------
@ -259,8 +310,7 @@ floating_modifier Mod1
=== Layout mode for new containers
This option is only available when using the new lexer/parser (pass +-l+ to i3
when starting). It determines in which mode new containers will start. See also
This option determines in which mode new containers will start. See also
<<stack-limit>>.
*Syntax*:
@ -276,8 +326,7 @@ new_container tabbed
=== Border style for new windows
This option is only available when using the new lexer/parser (pass +-l+ to i3
when starting). It determines which border new windows will have.
This option determines which border style new windows will have.
*Syntax*:
---------------------------------------------
@ -291,10 +340,10 @@ new_window bp
=== Variables
As you learned in the previous section about keyboard bindings, you will have
As you learned in the section about keyboard bindings, you will have
to configure lots of bindings containing modifier keys. If you want to save
yourself some typing and have the possibility to change the modifier you want
to use later, variables can be handy.
yourself some typing and be able to change the modifier you use later,
variables can be handy.
*Syntax*:
--------------
@ -307,20 +356,23 @@ set $m Mod1
bindsym $m+Shift+r restart
------------------------
Variables are directly replaced in the file when parsing, there is no fancy
Variables are directly replaced in the file when parsing. There is no fancy
handling and there are absolutely no plans to change this. If you need a more
dynamic configuration, you should create a little script, like when configuring
wmii.
dynamic configuration you should create a little script which generates a
configuration file and run it before starting i3 (for example in your
+.xsession+ file).
=== Automatically putting clients on specific workspaces
It is recommended that you match on window classes whereever possible because
some applications first create their window and then care about setting the
correct title. Firefox with Vimperator comes to mind, as the window starts up
being named Firefox and only when Vimperator is loaded, the title changes. As
i3 will get the title as soon as the application maps the window (mapping means
actually displaying it on the screen), youd need to have to match on Firefox
in this case.
[[assign_workspace]]
It is recommended that you match on window classes wherever possible because
some applications first create their window, and then worry about setting the
correct title. Firefox with Vimperator comes to mind. The window starts up
being named Firefox, and only when Vimperator is loaded does the title change.
As i3 will get the title as soon as the application maps the window (mapping
means actually displaying it on the screen), youd need to have to match on
'Firefox' in this case.
You can 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
@ -341,11 +393,14 @@ assign "gecko" → ~4
assign "xv/MPlayer" → ~
----------------------
=== Automatically starting applications on startup
Note that the arrow is not required, it just looks good :-). If you decide to
use it, it has to be a UTF-8 encoded arrow, not "->" or something like that.
=== Automatically starting applications on i3 startup
By using the +exec+ keyword outside a keybinding, you can configure which
commands will be performed by i3 on the first start (not when reloading inplace
however). The commands will be run in order.
commands will be performed by i3 on initial startup (not when restarting i3
in-place however). These commands will be run in order.
*Syntax*:
------------
@ -357,35 +412,29 @@ exec command
exec sudo i3status | dzen2 -dock
--------------------------------
[[workspace_screen]]
=== Automatically putting workspaces on specific screens
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).
If you assign clients to workspaces, it might be handy to put the
workspaces on specific screens. Also, the assignment of workspaces to screens
will determine which workspace 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*:
----------------------------------
workspace <number> screen <screen>
workspace <number> output <output>
----------------------------------
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.
The 'output' is the name of the RandR output you attach your screen to. On a
laptop, you might have VGA1 and LVDS1 as output names. You can see the
available outputs by running +xrandr --current+.
*Examples*:
---------------------------
workspace 1 screen 0
workspace 5 screen 1
workspace 1 screen 1280
workspace 2 screen x800
workspace 3 screen 1280x800
workspace 1 output LVDS1
workspace 5 output VGA1
---------------------------
=== Named workspaces
@ -396,10 +445,10 @@ them names (of course UTF-8 is supported):
*Syntax*:
---------------------------------------
workspace <number> <name>
workspace <number> screen <screen> name
workspace <number> output <output> name
---------------------------------------
For more details about the screen-part of this command, see above.
For more details about the 'output' part of this command, see above.
*Examples*:
--------------------------
@ -436,7 +485,7 @@ bar.unfocused::
bar.urgent::
A workspace which has at least one client with an activated urgency hint.
Colors are in HTML hex format, see below.
Colors are in HTML hex format (#rrggbb), see the following example:
*Examples*:
--------------------------------------
@ -444,19 +493,64 @@ Colors are in HTML hex format, see below.
client.focused #2F343A #900000 #FFFFFF
--------------------------------------
Note that for the window decorations, the color around the child window is the
background color, and the border color is only the two thin lines at the top of
the window.
=== 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+.
i3 uses unix sockets to provide an IPC interface. This allows third-party
programs to get information from i3, such as the current workspaces
(to display a workspace bar), and to control i3.
To enable it, you have to configure a path where the unix socket will be
stored. The default path is +~/.i3/ipc.sock+.
*Examples*:
----------------------------
ipc-socket /tmp/i3-ipc.sock
ipc-socket ~/.i3/ipc.sock
----------------------------
You can then use the i3-msg command to perform any command listed in the next
section.
You can then use the +i3-msg+ application to perform any command listed in
the next section.
=== Disable focus follows mouse
If you have a setup where your mouse usually is in your way (like a touchpad
on your laptop which you do not want to disable completely), you might want
to disable 'focus follows mouse' and control focus only by using your keyboard.
The mouse will still be useful inside the currently active window (for example
to click on links in your browser window).
*Syntax*:
----------------------------
focus_follows_mouse <yes|no>
----------------------------
*Examples*:
----------------------
focus_follows_mouse no
----------------------
=== Internal workspace bar
The internal workspace bar (the thing at the bottom of your screen) is very
simple -- it does not provide a way to display custom text and it does not
offer advanced customization features. This is intended because we do not
want to duplicate functionality of tools like +dzen2+, +xmobar+ and so on
(they render bars, we manage windows). Instead, there is an option which will
turn off the internal bar completely, so that you can use a separate program to
display it (see +i3-wsbar+, a sample implementation of such a program):
*Syntax*:
----------------------
workspace_bar <yes|no>
----------------------
*Examples*:
----------------
workspace_bar no
----------------
== List of commands
@ -464,7 +558,8 @@ section.
To change the layout of the current container to stacking, use +s+, for default
use +d+ and for tabbed, use +T+. To make the current client (!) fullscreen,
use +f+, to make it floating (or tiling again) use +t+:
use +f+, to make it span all outputs, use +fg+, to make it floating (or
tiling again) use +t+:
*Examples*:
--------------
@ -475,17 +570,20 @@ bindsym Mod1+w T
# Toggle fullscreen
bindsym Mod1+f f
# Toggle global fullscreen
bindsym Mod1+Shift+f fg
# Toggle floating/tiling
bindsym Mod1+t t
--------------
=== Focussing/Moving/Snapping clients/containers/screens
=== Focusing/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+.
left, down, up, right (respectively). 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
The same principle applies for moving and snapping: just prefix the command
with +m+ when moving and with +s+ when snapping:
*Examples*:
@ -519,9 +617,9 @@ 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.
You can also 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*:
-------------------------
@ -542,8 +640,7 @@ bindsym Mod1+p pw
=== Resizing columns/rows
If you want to resize columns/rows using your keyboard, you can use the
+resize+ command, I recommend using it a +mode+ (you need to use the new
lexer/parser for that, so pass +-l+ to i3 when starting):
+resize+ command, I recommend using it inside a so called +mode+:
.Example: Configuration file, defining a mode for resizing
----------------------------------------------------------------------
@ -568,15 +665,18 @@ mode "resize" {
bind 36 mode default
}
# Enter resize mode
bindsym Mod1+r mode resize
----------------------------------------------------------------------
=== Jumping to specific windows
Especially when in a multi-monitor environment, you want to quickly jump to a specific
window, for example while currently working on workspace 3 you may want to jump to
your mailclient to mail your boss that youve achieved some important goal. Instead
of figuring out how to navigate to your mailclient, it would be more convenient to
have a shortcut.
Often when in a multi-monitor environment, you want to quickly jump to a
specific window. For example, while working on workspace 3 you may want to
jump to your mail client to email your boss that youve achieved some
important goal. Instead of figuring out how to navigate to your mailclient,
it would be more convenient to have a shortcut.
*Syntax*:
----------------------------------------------------
@ -584,8 +684,9 @@ 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.
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*:
--------------------------------------
@ -595,19 +696,19 @@ bindsym Mod1+a jump "urxvt/VIM"
=== VIM-like marks (mark/goto)
[[vim_like_marks]]
This feature is like the jump feature: It allows you to directly jump to a
specific window (this means switching to the appropriate workspace and setting
focus to the windows). However, you can directly mark a specific window with
an arbitrary label and use it afterwards, that is, you do not need to ensure
that your windows have unique classes or titles and you do not need to change
your configuration file.
an arbitrary label and use it afterwards. You do not need to ensure that your
windows have unique classes or titles, and you do not need to change your
configuration file.
As the command needs to include the label with which you want to mark the
window, you cannot simply bind it to a key (or, you could bind it to a key and
only use the set of labels for which you created bindings). +i3-input+ is a
tool created for this purpose: It lets you input a command and sends the
command to i3. It can also prefix this command and display a custom prompt for
the input dialog.
window, you cannot simply bind it to a key. +i3-input+ is a tool created
for this purpose: It lets you input a command and sends the command to i3. It
can also prefix this command and display a custom prompt for the input dialog.
*Syntax*:
-----------------
@ -624,18 +725,21 @@ bindsym Mod1+m exec i3-input -p 'mark ' -l 1 -P 'Mark: '
bindsym Mod1+g exec i3-input -p 'goto ' -l 1 -P 'Goto: '
---------------------------------------
Alternatively, if you do not want to mess with +i3-input+, you could create
seperate bindings for a specific set of labels and then only use those labels.
=== 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.
This mechanism can be thought of as the opposite of the +jump+ command.
It travels the focus stack and jumps to the window which had focus previously.
*Syntax*:
--------------
focus [number] | floating | tilling | ft
focus [number] | floating | tiling | ft
--------------
Where +number+ by default is 1 meaning that the next client in the focus stack will
be selected.
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:
@ -644,14 +748,14 @@ floating::
tiling::
The next tiling window is selected.
ft::
If the current window is floating, the next tiling window will be selected
and vice-versa.
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. There also is +bt+ which will toggle
and +bb+ to make the client borderless. There is also +bt+ which will toggle
the different border styles.
*Examples*:
@ -665,12 +769,12 @@ bindsym Mod1+u bb
=== Changing the stack-limit of a container
If you have a single container with a lot of windows inside (say, more than
If you have a single container with a lot of windows inside it (say, more than
10), the default layout of a stacking container can get a little unhandy.
Depending on your screens size, you might end up only using half of the
titlebars of each window in the container.
Depending on your screens size, you might end up seeing only half of the
titlebars for each window in the container.
Using the +stack-limit+ command, you can limit the amount of rows or columns
Using the +stack-limit+ command, you can limit the number of rows or columns
in a stacking container. i3 will create columns or rows (depending on what
you limited) automatically as needed.
@ -696,9 +800,9 @@ You can make i3 reload its configuration file with +reload+. You can also
restart i3 inplace with the +restart+ command to get it out of some weird state
(if that should ever happen) or to perform an upgrade without having to restart
your X session. However, your layout is not preserved at the moment, meaning
that all open windows will be in a single container in default layout. To exit
i3 properly, you can use the +exit+ command, however you dont need to (e.g.,
simply killing your X session is fine aswell).
that all open windows will end up in a single container in default layout
after the restart. To exit i3 properly, you can use the +exit+ command,
however you dont need to (simply killing your X session is fine as well).
*Examples*:
----------------------------
@ -706,3 +810,174 @@ bindsym Mod1+Shift+r restart
bindsym Mod1+Shift+w reload
bindsym Mod1+Shift+e exit
----------------------------
[[multi_monitor]]
== Multiple monitors
As you can see in the goal list on the website, i3 was specifically developed
with support for multiple monitors in mind. This section will explain how to
handle multiple monitors.
When you have only one monitor, things are simple. You usually start with
workspace 1 on your monitor and open new ones as you need them.
When you have more than one monitor, each monitor will get an initial
workspace. The first monitor gets 1, the second gets 2 and a possible third
would get 3. When you switch to a workspace on a different monitor, i3 will
switch to that monitor and then switch to the workspace. This way, you dont
need shortcuts to switch to a specific monitor, and you dont need to remember
where you put which workspace. New workspaces will be opened on the currently
active monitor. It is not possible to have a monitor without a workspace.
The idea of making workspaces global is based on the observation that most
users have a very limited set of workspaces on their additional monitors.
They are often used for a specific task (browser, shell) or for monitoring
several things (mail, IRC, syslog, …). Thus, using one workspace on one monitor
and "the rest" on the other monitors often makes sense. However, as you can
create an unlimited number of workspaces in i3 and tie them to specific
screens, you can have the "traditional" approach of having X workspaces per
screen by changing your configuration (using modes, for example).
=== Configuring your monitors
To help you get going if you have never used multiple monitors before, here is
a short overview of the xrandr options which will probably be of interest to
you. It is always useful to get an overview of the current screen configuration.
Just run "xrandr" and you will get an output like the following:
-------------------------------------------------------------------------------
$ xrandr
Screen 0: minimum 320 x 200, current 1280 x 800, maximum 8192 x 8192
VGA1 disconnected (normal left inverted right x axis y axis)
LVDS1 connected 1280x800+0+0 (normal left inverted right x axis y axis) 261mm x 163mm
1280x800 60.0*+ 50.0
1024x768 85.0 75.0 70.1 60.0
832x624 74.6
800x600 85.1 72.2 75.0 60.3 56.2
640x480 85.0 72.8 75.0 59.9
720x400 85.0
640x400 85.1
640x350 85.1
--------------------------------------------------------------------------------------
Several things are important here: You can see that +LVDS1+ is connected (of
course, it is the internal flat panel) but +VGA1+ is not. If you have a monitor
connected to one of the ports but xrandr still says "disconnected", you should
check your cable, monitor or graphics driver.
The maximum resolution you can see at the end of the first line is the maximum
combined resolution of your monitors. By default, it is usually too low and has
to be increased by editing +/etc/X11/xorg.conf+.
So, say you connected VGA1 and want to use it as an additional screen:
-------------------------------------------
xrandr --output VGA1 --auto --left-of LVDS1
-------------------------------------------
This command makes xrandr try to find the native resolution of the device
connected to +VGA1+ and configures it to the left of your internal flat panel.
When running "xrandr" again, the output looks like this:
-------------------------------------------------------------------------------
$ xrandr
Screen 0: minimum 320 x 200, current 2560 x 1024, maximum 8192 x 8192
VGA1 connected 1280x1024+0+0 (normal left inverted right x axis y axis) 338mm x 270mm
1280x1024 60.0*+ 75.0
1280x960 60.0
1152x864 75.0
1024x768 75.1 70.1 60.0
832x624 74.6
800x600 72.2 75.0 60.3 56.2
640x480 72.8 75.0 66.7 60.0
720x400 70.1
LVDS1 connected 1280x800+1280+0 (normal left inverted right x axis y axis) 261mm x 163mm
1280x800 60.0*+ 50.0
1024x768 85.0 75.0 70.1 60.0
832x624 74.6
800x600 85.1 72.2 75.0 60.3 56.2
640x480 85.0 72.8 75.0 59.9
720x400 85.0
640x400 85.1
640x350 85.1
-------------------------------------------------------------------------------
Please note that i3 uses exactly the same API as xrandr does, so it will see
only what you can see in xrandr.
See also <<presentations>> for more examples of multi-monitor setups.
=== Interesting configuration for multi-monitor environments
There are several things to configure in i3 which may be interesting if you
have more than one monitor:
1. You can specify which workspace should be put on which screen. This
allows you to have a different set of workspaces when starting than just
1 for the first monitor, 2 for the second and so on. See
<<workspace_screen>>.
2. If you want some applications to generally open on the bigger screen
(MPlayer, Firefox, …), you can assign them to a specific workspace, see
<<assign_workspace>>.
3. If you have many workspaces on many monitors, it might get hard to keep
track of which window you put where. Thus, you can use vim-like marks to
quickly switch between windows. See <<vim_like_marks>>.
== i3 and the rest of your software world
=== Displaying a status line
A very common thing amongst users of exotic window managers is a status line at
some corner of the screen. It is an often superior replacement to the widget
approach you have in the task bar of a traditional desktop environment.
If you dont already have your favorite way of generating such a status line
(self-written scripts, conky, …), then i3status is the recommended tool for
this task. It was written in C with the goal of using as few syscalls as
possible to reduce the time your CPU is woken up from sleep states.
Regardless of which application you use to generate the status line, you
want to make sure that the application does one of the following things:
1. Register as a dock window using EWMH hints. This will make i3 position the
window above the workspace bar but below every other client. This is the
recommended way, but in case of dzen2, for example, you need to check out
the source of dzen2 from subversion, as the -dock option is not present
in the released versions.
2. Overlay the internal workspace bar. This method will not waste any space
on the workspace bar, however, it is rather hackish. Just configure
the output window to be over the workspace bar (say -x 200 and -y 780 if
your screen is 800 px height).
The planned solution for this problem is to make the workspace bar optional
and switch to a third party application completely (dzen2 for example)
which will then contain the workspace bar.
=== Giving presentations (multi-monitor)
When giving a presentation, you typically want the audience to see what you see
on your screen and then go through a series of slides (if the presentation is
simple). For more complex presentations, you might want to have some notes
which only you can see on your screen, while the audience can only see the
slides.
[[presentations]]
==== Case 1: everybody gets the same output
This is the simple case. You connect your computer to the video projector,
turn on both (computer and video projector) and configure your X server to
clone the internal flat panel of your computer to the video output:
-----------------------------------------------------
xrandr --output VGA1 --mode 1024x768 --same-as LVDS1
-----------------------------------------------------
i3 will then use the lowest common subset of screen resolutions, the rest of
your screen will be left untouched (it will show the X background). So, in
our example, this would be 1024x768 (my notebook has 1280x800).
==== Case 2: you can see more than your audience
This case is a bit harder. First of all, you should configure the VGA output
somewhere near your internal flat panel, say right of it:
-----------------------------------------------------
xrandr --output VGA1 --mode 1024x768 --right-of LVDS1
-----------------------------------------------------
Now, i3 will put a new workspace (depending on your settings) on the new screen
and you are in multi-monitor mode (see <<multi_monitor>>).
Because i3 is not a compositing window manager, there is no ability to
display a window on two screens at the same time. Instead, your presentation
software needs to do this job (that is, open a window on each screen).

View File

@ -18,8 +18,8 @@ all: ${FILES}
install: all
echo "INSTALL"
$(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin
$(INSTALL) -m 0755 i3-input $(DESTDIR)/usr/bin/
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
$(INSTALL) -m 0755 i3-input $(DESTDIR)$(PREFIX)/bin/
clean:
rm -f *.o

View File

@ -8,7 +8,7 @@
char *convert_ucs_to_utf8(char *input);
char *convert_utf8_to_ucs2(char *input, int *real_strlen);
uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
uint32_t get_mode_switch_mask(xcb_connection_t *conn);
uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode);
int connect_ipc(char *socket_path);
void ipc_send_message(int sockfd, uint32_t message_size,
uint32_t message_type, uint8_t *payload);

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -22,6 +22,7 @@
#include <err.h>
#include <stdint.h>
#include <getopt.h>
#include <glob.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
@ -37,6 +38,7 @@
static int sockfd;
static xcb_key_symbols_t *symbols;
static int modeswitchmask;
static int numlockmask;
static bool modeswitch_active = false;
static xcb_window_t win;
static xcb_pixmap_t pixmap;
@ -50,6 +52,21 @@ static char *prompt;
static int prompt_len;
static int limit;
/*
* This function resolves ~ in pathnames (and more, see glob(3)).
*
*/
static char *glob_path(const char *path) {
static glob_t globbuf;
if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
errx(EXIT_FAILURE, "glob() failed");
char *result = strdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
if (result == NULL)
err(EXIT_FAILURE, "malloc() failed");
globfree(&globbuf);
return result;
}
/*
* Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for
* rendering it (UCS-2) or sending it to i3 (UTF-8).
@ -119,6 +136,9 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t
static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) {
printf("releasing %d, state raw = %d\n", event->detail, event->state);
/* fix state */
event->state &= ~numlockmask;
xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
if (sym == XK_Mode_switch) {
printf("Mode switch disabled\n");
@ -163,6 +183,11 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
if (modeswitch_active)
event->state |= modeswitchmask;
/* Apparantly, after activating numlock once, the numlock modifier
* stays turned on (use xev(1) to verify). So, to resolve useful
* keysyms, we remove the numlock flag from the event state */
event->state &= ~numlockmask;
xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
if (sym == XK_Mode_switch) {
printf("Mode switch enabled\n");
@ -232,7 +257,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
}
int main(int argc, char *argv[]) {
char *socket_path = "/tmp/i3-ipc.sock";
char *socket_path = glob_path("~/.i3/ipc.sock");
char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
int o, option_index = 0;
@ -251,7 +276,7 @@ int main(int argc, char *argv[]) {
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
switch (o) {
case 's':
socket_path = strdup(optarg);
socket_path = glob_path(optarg);
break;
case 'v':
printf("i3-input " I3_VERSION);
@ -290,7 +315,8 @@ int main(int argc, char *argv[]) {
xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
xcb_event_set_expose_handler(&evenths, handle_expose, NULL);
modeswitchmask = get_mode_switch_mask(conn);
modeswitchmask = get_mod_mask(conn, XK_Mode_switch);
numlockmask = get_mod_mask(conn, XK_Num_Lock);
symbols = xcb_key_symbols_alloc(conn);
uint32_t font_id = get_font_id(conn, pattern, &font_height);
@ -306,11 +332,33 @@ int main(int argc, char *argv[]) {
xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8);
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
/* Set input focus (we have override_redirect=1, so the wm will not do
* this for us) */
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
/* Create graphics context */
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
/* Grab the keyboard to get all input */
xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
xcb_flush(conn);
/* Try (repeatedly, if necessary) to grab the keyboard. We might not
* get the keyboard at the first attempt because of the keybinding
* still being active when started via a wms keybinding. */
xcb_grab_keyboard_cookie_t cookie;
xcb_grab_keyboard_reply_t *reply = NULL;
int count = 0;
while ((reply == NULL || reply->status != XCB_GRAB_STATUS_SUCCESS) && (count++ < 500)) {
cookie = xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
reply = xcb_grab_keyboard_reply(conn, cookie, NULL);
usleep(1000);
}
if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
exit(-1);
}
xcb_flush(conn);

View File

@ -53,12 +53,12 @@ uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
* keycode).
*
*/
uint32_t get_mode_switch_mask(xcb_connection_t *conn) {
uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode) {
xcb_key_symbols_t *symbols = xcb_key_symbols_alloc(conn);
xcb_get_modifier_mapping_reply_t *modmap_r;
xcb_keycode_t *modmap, kc;
xcb_keycode_t *modeswitchcodes = xcb_key_symbols_get_keycode(symbols, XK_Mode_switch);
xcb_keycode_t *modeswitchcodes = xcb_key_symbols_get_keycode(symbols, keycode);
if (modeswitchcodes == NULL)
return 0;
@ -66,7 +66,7 @@ uint32_t get_mode_switch_mask(xcb_connection_t *conn) {
modmap = xcb_get_modifier_mapping_keycodes(modmap_r);
for (int i = 0; i < 8; i++)
for(int j = 0; j < modmap_r->keycodes_per_modifier; j++) {
for (int j = 0; j < modmap_r->keycodes_per_modifier; j++) {
kc = modmap[i * modmap_r->keycodes_per_modifier + j];
for (xcb_keycode_t *ktest = modeswitchcodes; *ktest; ktest++) {
if (*ktest != kc)

View File

@ -3,6 +3,8 @@ TOPDIR=..
include $(TOPDIR)/common.mk
CFLAGS += -I$(TOPDIR)/include
# 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)
@ -18,8 +20,8 @@ all: ${FILES}
install: all
echo "INSTALL"
$(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin
$(INSTALL) -m 0755 i3-msg $(DESTDIR)/usr/bin/
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
$(INSTALL) -m 0755 i3-msg $(DESTDIR)$(PREFIX)/bin/
clean:
rm -f *.o

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -16,6 +16,7 @@
*/
#include <ev.h>
#include <stdio.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
@ -26,6 +27,24 @@
#include <err.h>
#include <stdint.h>
#include <getopt.h>
#include <glob.h>
#include <i3/ipc.h>
/*
* This function resolves ~ in pathnames (and more, see glob(3)).
*
*/
static char *glob_path(const char *path) {
static glob_t globbuf;
if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
errx(EXIT_FAILURE, "glob() failed");
char *result = strdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
if (result == NULL)
err(EXIT_FAILURE, "malloc() failed");
globfree(&globbuf);
return result;
}
/*
* Formats a message (payload) of the given size and type and sends it to i3 via
@ -34,12 +53,12 @@
*/
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;
int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
char msg[buffer_size];
char *walk = msg;
strcpy(walk, "i3-ipc");
walk += strlen("i3-ipc");
strcpy(walk, I3_IPC_MAGIC);
walk += strlen(I3_IPC_MAGIC);
memcpy(walk, &message_size, sizeof(uint32_t));
walk += sizeof(uint32_t);
memcpy(walk, &message_type, sizeof(uint32_t));
@ -58,26 +77,84 @@ static void ipc_send_message(int sockfd, uint32_t message_size,
}
}
static void ipc_recv_message(int sockfd, uint32_t message_type,
uint32_t *reply_length, uint8_t **reply) {
/* Read the message header first */
uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t);
char msg[to_read];
char *walk = msg;
uint32_t read_bytes = 0;
while (read_bytes < to_read) {
int n = read(sockfd, msg + read_bytes, to_read);
if (n == -1)
err(EXIT_FAILURE, "read() failed");
if (n == 0)
errx(EXIT_FAILURE, "received EOF instead of reply");
read_bytes += n;
to_read -= n;
}
if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0)
errx(EXIT_FAILURE, "invalid magic in reply");
walk += strlen(I3_IPC_MAGIC);
*reply_length = *((uint32_t*)walk);
walk += sizeof(uint32_t);
if (*((uint32_t*)walk) != message_type)
errx(EXIT_FAILURE, "unexpected reply type (got %d, expected %d)", *((uint32_t*)walk), message_type);
walk += sizeof(uint32_t);
*reply = malloc(*reply_length);
if ((*reply) == NULL)
err(EXIT_FAILURE, "malloc() failed");
to_read = *reply_length;
read_bytes = 0;
while (read_bytes < to_read) {
int n = read(sockfd, *reply + read_bytes, to_read);
if (n == -1)
err(EXIT_FAILURE, "read() failed");
read_bytes += n;
to_read -= n;
}
}
int main(int argc, char *argv[]) {
char *socket_path = "/tmp/i3-ipc.sock";
char *socket_path = glob_path("~/.i3/ipc.sock");
int o, option_index = 0;
int message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
char *payload = "";
bool quiet = false;
static struct option long_options[] = {
{"socket", required_argument, 0, 's'},
{"type", required_argument, 0, 't'},
{"version", no_argument, 0, 'v'},
{"quiet", no_argument, 0, 'q'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
char *options_string = "s:t:vh";
char *options_string = "s:t:vhq";
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
if (o == 's') {
socket_path = strdup(optarg);
break;
socket_path = glob_path(optarg);
} else if (o == 't') {
printf("currently only commands are implemented\n");
if (strcasecmp(optarg, "command") == 0)
message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
else if (strcasecmp(optarg, "get_workspaces") == 0)
message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES;
else {
printf("Unknown message type\n");
printf("Known types: command, get_workspaces\n");
exit(EXIT_FAILURE);
}
} else if (o == 'q') {
quiet = true;
} else if (o == 'v') {
printf("i3-msg " I3_VERSION);
return 0;
@ -88,11 +165,8 @@ int main(int argc, char *argv[]) {
}
}
if (optind >= argc) {
fprintf(stderr, "Error: missing message\n");
fprintf(stderr, "i3-msg [-s <socket>] [-t <type>] <message>\n");
return 1;
}
if (optind < argc)
payload = argv[optind];
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd == -1)
@ -105,7 +179,16 @@ int main(int argc, char *argv[]) {
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]);
ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload);
if (quiet)
return 0;
uint32_t reply_length;
uint8_t *reply;
ipc_recv_message(sockfd, message_type, &reply_length, &reply);
printf("%.*s", reply_length, reply);
free(reply);
close(sockfd);

244
i3-wsbar Executable file
View File

@ -0,0 +1,244 @@
#!/usr/bin/env perl
# vim:ts=4:sw=4:expandtab:ft=perl
# © 2010 Michael Stapelberg, see LICENSE for license information
use strict;
use warnings;
use Getopt::Long;
use Pod::Usage;
use IPC::Run qw(start pump);
use AnyEvent::I3;
use AnyEvent;
use v5.10;
my $stdin;
my $i3 = i3;
my ($workspaces, $outputs) = ([], {});
my $last_line = "";
my $command = "";
my $input_on = "";
my $output_on = "";
my $show_all = 0;
my $result = GetOptions(
'command=s' => \$command,
'input-on=s' => \$input_on,
'output-on=s' => \$output_on,
'show-all' => \$show_all,
'help' => sub { pod2usage(1); exit 0 },
);
if ($command eq '') {
say "i3-wsbar is only useful in combination with dzen2.";
say "Please specify -c (command)";
exit 1;
}
my @input_on = split(/,/, $input_on);
my @output_on = split(/,/, $output_on);
# Disable buffering
$| = 1;
# Wait a short amount of time and try to connect to i3 again
sub reconnect {
my $timer;
my $c = sub {
$timer = AnyEvent->timer(
after => 0.01,
cb => sub { $i3->connect->cb(\&connected) }
);
};
$c->();
}
# Connection attempt succeeded or failed
sub connected {
my ($cv) = @_;
if (!$cv->recv) {
reconnect();
return;
}
$i3->subscribe({
workspace => \&ws_change,
output => \&output_change,
_error => sub { reconnect() }
});
ws_change();
output_change();
}
# Called when a ws changes
sub ws_change {
# Request the current workspaces and update the output afterwards
$i3->get_workspaces->cb(
sub {
my ($cv) = @_;
$workspaces = $cv->recv;
update_output();
});
}
# Called when the reply to the GET_OUTPUTS message arrives
# Compares old outputs with new outputs and starts/kills
# $command for each output (if specified)
sub got_outputs {
my $reply = shift->recv;
my %old = %{$outputs};
my %new = map { ($_->{name}, $_) } grep { $_->{active} } @{$reply};
# If no command was given, we do not need to compare outputs
if ($command eq '') {
update_output();
return;
}
# Handle new outputs
for my $name (keys %new) {
next if @output_on and !($name ~~ @output_on);
if (defined($old{$name})) {
# Check if the mode changed (by reversing the hashes so
# that we can check for equality using the smartmatch op)
my %oldrect = reverse %{$old{$name}->{rect}};
my %newrect = reverse %{$new{$name}->{rect}};
next if (%oldrect ~~ %newrect);
# On mode changes, we re-start the command
$outputs->{$name}->{cmd}->finish;
delete $outputs->{$name};
}
my $x = $new{$name}->{rect}->{x};
my $launch = $command;
$launch =~ s/([^%])%x/$1$x/g;
$launch =~ s/%%x/%x/g;
$new{$name}->{cmd_input} = '';
my @cmd = ('/bin/sh', '-c', $launch);
$new{$name}->{cmd} = start \@cmd, \$new{$name}->{cmd_input};
$outputs->{$name} = $new{$name};
}
# Handle old outputs
for my $name (keys %old) {
next if defined($new{$name});
$outputs->{$name}->{cmd}->finish;
delete $outputs->{$name};
}
update_output();
}
sub output_change {
$i3->get_outputs->cb(\&got_outputs)
}
sub update_output {
my $dzen_bg = "#111111";
my $out;
for my $name (keys %{$outputs}) {
my $width = $outputs->{$name}->{rect}->{width};
$out = qq|^pa(;2)|;
for my $ws (@{$workspaces}) {
next if $ws->{output} ne $name and !$show_all;
my ($bg, $fg) = qw(333333 888888);
($bg, $fg) = qw(4c7899 ffffff) if $ws->{visible};
($bg, $fg) = qw(900000 ffffff) if $ws->{urgent};
my $cmd = q|i3-msg "| . $ws->{num} . q|"|;
my $name = $ws->{name};
# Begin the clickable area
$out .= qq|^ca(1,$cmd)|;
# Draw the rest of the bar in the background color, but
# dont move the "cursor"
$out .= qq|^p(_LOCK_X)^fg(#$bg)^r(${width}x17)^p(_UNLOCK_X)|;
# Draw the name of the workspace without overwriting the
# background color
$out .= qq|^p(+3)^fg(#$fg)^ib(1)$name^ib(0)^p(+5)|;
# Draw the rest of the bar in the normal background color
# without moving the "cursor"
$out .= qq|^p(_LOCK_X)^fg($dzen_bg)^r(${width}x17)^p(_UNLOCK_X)|;
# End the clickable area
$out .= qq|^ca()|;
# Move to the next rect, reset Y coordinate
$out .= qq|^p(2)^pa(;2)|;
}
$out .= qq|^p(_LOCK_X)^fg($dzen_bg)^r(${width}x17)^p(_UNLOCK_X)^fg(white)|;
$out .= qq|^p(+5)|;
$out .= $last_line if (!@input_on or $name ~~ @input_on);
$out .= "\n";
$outputs->{$name}->{cmd_input} = $out;
pump $outputs->{$name}->{cmd} while length $outputs->{$name}->{cmd_input};
}
}
$i3->connect->cb(\&connected);
$stdin = AnyEvent->io(
fh => \*STDIN,
poll => 'r',
cb => sub {
chomp (my $line = <STDIN>);
$last_line = $line;
update_output();
});
# let AnyEvent do the rest ("endless loop")
AnyEvent->condvar->recv
__END__
=head1 NAME
i3-wsbar - sample implementation of a standalone workspace bar
=head1 SYNOPSIS
i3-wsbar -c <dzen2-commandline> [options]
=head1 OPTIONS
=over 4
=item B<--command> <command>
This command (at the moment only dzen2 is supported) will be started for each
output. C<%x> will be replaced with the X coordinate of the output.
Example:
--command "dzen2 -dock -x %x"
=item B<--input-on> <list-of-RandR-outputs>
Specifies on which outputs the contents of stdin should be appended to the
workspace bar.
Example:
--input-on "LVDS1"
=item B<--output-on> <list-of-RandR-outputs>
Specifies for which outputs i3-wsbar should start C<command>.
=item B<--show-all>
If enabled, all workspaces are shown (not only those of the current output).
Handy to use with C<--output-on>.
=back
=cut

View File

@ -1,11 +1,6 @@
# This configuration uses Mod1 and Mod3. Make sure they are mapped properly using xev(1)
# and xmodmap(1). Usually, Mod1 is Alt (Alt_L) and Mod3 is Windows (Super_L)
# Tell i3 about your preferred terminal. You can refer to this as $terminal
# later. It is recommended to set this option to allow i3 to open a terminal
# containing the introduction on first start.
terminal /usr/bin/urxvt
# ISO 10646 = Unicode
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
@ -112,7 +107,7 @@ bind Mod1+36 exec /usr/bin/urxvt
bind Mod1+Shift+24 kill
# Mod1+v starts dmenu and launches the selected application
# for now, we dont have an own launcher
# for now, we dont have a launcher of our own.
bind Mod1+55 exec /usr/bin/dmenu_run
# Mod1+Shift+e exits i3
@ -121,6 +116,10 @@ bind Mod1+Shift+26 exit
# Mod1+Shift+r restarts i3 inplace
bind Mod1+Shift+27 restart
# The IPC interface allows programs like an external workspace bar
# (i3-wsbar) or i3-msg (can be used to "remote-control" i3) to work.
ipc-socket ~/.i3/ipc.sock
#############################################################
# DELETE THE FOLLOWING LINES TO DISABLE THE WELCOME MESSAGE #
#############################################################

View File

@ -1,6 +1,6 @@
1.) Welcome to i3!
This message provides you with an overview of the default keybindings to use i3.
This message provides an overview of the default keybindings to use i3.
Please also make sure to have a look at the man page and the user's guide:
http://i3.zekjur.net/docs/userguide.html
@ -8,7 +8,8 @@ http://i3.zekjur.net/docs/userguide.html
2.) Configuration Files
/etc/i3/config is the default configuration. It is recommended to copy it and
afterwards edit it to suit your needs (you can especially disable this message):
afterwards edit it to suit your needs (in particular, you may want to disable
this message):
cp /etc/i3/config ~/.i3/config
@ -17,7 +18,7 @@ afterwards edit it to suit your needs (you can especially disable this message):
The following explanation is related to the QWERTY layout, but as the default
configuration uses keycodes instead of keysymbols for binding, you still have
to press the same keys, regardless of your keyboard layout.
to press the same physical keys, regardless of your keyboard layout.
The Mod1 key is usually bound to the "Alt" key on your keyboard.
@ -28,15 +29,15 @@ The directional keys are j(left), k(down), l(up) and ;(right). You can also use
the arrow keys on your keyboard, if you prefer them.
Mod1+<directional key> moves the focus to the window in the given direction
Mod1+Shift+<directional key> moves the window to the given direction,
Mod1+Shift+<directional key> moves the window to the given direction
Mod1+<number> opens the corresponding workspace
Mod1+Shift+<number> moves a window to the wished workspace
Mod1+Shift+<number> moves a window to the selected workspace
Mod1+h sets the mode of a container to stacking
Mod1+e sets the mode back to default
Mod1+f toggles fullscreen mode for the current window
Mod1+Shift+Space toggles floating mode for the current window
Mod1+Shift+q closes a window
Mod1+Shift+r restarts i3 in-place (you will lose your layout, though)
Mod1+Shift+r restarts i3 in-place (at this time, you will lose your layout)
Mod1+Shift+e exits i3
If you have any questions, please don't hesitate to ask!

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -51,7 +51,13 @@ bool client_matches_class_name(Client *client, char *to_class, char *to_title,
* and when moving a fullscreen client to another screen.
*
*/
void client_enter_fullscreen(xcb_connection_t *conn, Client *client);
void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global);
/**
* Leaves fullscreen mode for the given client. This is called by toggle_fullscreen.
*
*/
void client_leave_fullscreen(xcb_connection_t *conn, Client *client);
/**
* Leaves fullscreen mode for the current client. This is called by toggle_fullscreen.
@ -68,6 +74,12 @@ void client_leave_fullscreen(xcb_connection_t *conn, Client *client);
*/
void client_toggle_fullscreen(xcb_connection_t *conn, Client *client);
/**
* Like client_toggle_fullscreen(), but putting it in global fullscreen-mode.
*
*/
void client_toggle_fullscreen_global(xcb_connection_t *conn, Client *client);
/**
* Sets the position of the given client in the X stack to the highest (tiling
* layer is always on the same position, so this doesnt matter) below the
@ -118,12 +130,26 @@ void client_map(xcb_connection_t *conn, Client *client);
*/
void client_mark(xcb_connection_t *conn, Client *client, const char *mark);
/**
* Returns the minimum height of a specific window. The height is calculated
* by using 2 pixels (for the client window itself), possibly padding this to
* comply with the clients base_height and then adding the decoration height.
*
*/
uint32_t client_min_height(Client *client);
/**
* See client_min_height.
*
*/
uint32_t client_min_width(Client *client);
/**
* Pretty-prints the clients information into the logfile.
*
*/
#define CLIENT_LOG(client) do { \
LOG("Window: frame 0x%08x, child 0x%08x\n", client->frame, client->child); \
DLOG("Window: frame 0x%08x, child 0x%08x\n", client->frame, client->child); \
} while (0)
#endif

View File

@ -3,12 +3,14 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* include/config.h: Contains all structs/variables for
* the configurable part of i3
* include/config.h: Contains all structs/variables for the configurable
* part of i3 as well as functions handling the configuration file (calling
* the parser (src/cfgparse.y) with the correct path, switching key bindings
* mode).
*
*/
@ -21,9 +23,23 @@
typedef struct Config Config;
extern Config config;
extern bool config_use_lexer;
extern SLIST_HEAD(modes_head, Mode) modes;
/**
* Used during the config file lexing/parsing to keep the state of the lexer
* in order to provide useful error messages in yyerror().
*
*/
struct context {
int line_number;
char *line_copy;
const char *filename;
/* These are the same as in YYLTYPE */
int first_column;
int last_column;
};
/**
* 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).
@ -76,6 +92,18 @@ struct Config {
int container_stack_limit;
int container_stack_limit_value;
/** By default, focus follows mouse. If the user explicitly wants to
* turn this off (and instead rely only on the keyboard for changing
* focus), we allow him to do this with this relatively special option.
* It is not planned to add any different focus models. */
bool disable_focus_follows_mouse;
/** By default, a workspace bar is drawn at the bottom of the screen.
* If you want to have a more fancy bar, it is recommended to replace
* the whole bar by dzen2, for example using the i3-wsbar script which
* comes with i3. Thus, you can turn it off entirely. */
bool disable_workspace_bar;
const char *default_border;
/** The modifier which needs to be pressed in combination with your mouse
@ -96,6 +124,18 @@ struct Config {
} bar;
};
/**
* This function resolves ~ in pathnames.
*
*/
char *glob_path(const char *path);
/**
* Checks if the given path exists by calling stat().
*
*/
bool path_exists(const char *path);
/**
* Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
*
@ -105,6 +145,12 @@ struct Config {
*/
void load_configuration(xcb_connection_t *conn, const char *override_configfile, bool reload);
/**
* Translates keysymbols to keycodes for all bindings which use keysyms.
*
*/
void translate_keysyms();
/**
* Ungrabs all keys, to be called before re-grabbing the keys because of a
* mapping_notify event or a configuration file reload
@ -116,7 +162,7 @@ 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);
void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch);
/**
* Switches the key bindings to the given mode, if the mode exists
@ -124,4 +170,14 @@ void grab_all_keys(xcb_connection_t *conn);
*/
void switch_mode(xcb_connection_t *conn, const char *new_mode);
/**
* Returns a pointer to the Binding with the specified modifiers and keycode
* or NULL if no such binding exists.
*
*/
Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode);
/* prototype for src/cfgparse.y */
void parse_file(const char *f);
#endif

26
include/container.h Normal file
View File

@ -0,0 +1,26 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include "data.h"
#ifndef _CONTAINER_H
#define _CONTAINER_H
/**
* Returns the mode of the given container (or MODE_DEFAULT if a NULL pointer
* was passed in order to save a few explicit checks in other places). If
* for_frame was set to true, the special case of having exactly one client
* in a container is handled so that MODE_DEFAULT is returned. For some parts
* of the rendering, this is interesting, other parts need the real mode.
*
*/
int container_mode(Container *con, bool for_frame);
#endif

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -11,6 +11,7 @@
*
*/
#include <xcb/xcb.h>
#include <xcb/randr.h>
#include <xcb/xcb_atom.h>
#include <stdbool.h>
@ -25,11 +26,12 @@
*
* Lets start from the biggest to the smallest:
*
* - An i3Screen is a virtual screen (Xinerama). This can be a single one,
* though two monitors might be connected, if youre running clone
* mode. There can also be multiple of them.
* - An Output is a physical output on your graphics driver. Outputs which
* are currently in use have (output->active == true). Each output has a
* position and a mode. An output usually corresponds to one connected
* screen (except if you are running multiple screens in clone mode).
*
* - Each i3Screen contains Workspaces. The concept is known from various
* - Each Output contains Workspaces. The concept is known from various
* other window managers. Basically, a workspace is a specific set of
* windows, usually grouped thematically (irc, www, work, ). You can switch
* between these.
@ -54,7 +56,7 @@ typedef struct Client Client;
typedef struct Binding Binding;
typedef struct Workspace Workspace;
typedef struct Rect Rect;
typedef struct Screen i3Screen;
typedef struct xoutput Output;
/******************************************************************************
* Helper types
@ -75,6 +77,14 @@ enum {
/**
* Stores a rectangle, for example the size of a window, the child window etc.
* It needs to be packed so that the compiler will not add any padding bytes.
* (it is used in src/ewmh.c for example)
*
* Note that x and y can contain signed values in some cases (for example when
* used for the coordinates of a window, which can be set outside of the
* visible area, but not when specifying the position of a workspace for the
* _NET_WM_WORKAREA hint). Not declaring x/y as int32_t saves us a lot of
* typecasts.
*
* Note that x and y can contain signed values in some cases (for example when
* used for the coordinates of a window, which can be set outside of the
@ -84,9 +94,11 @@ enum {
*
*/
struct Rect {
uint32_t x, y;
uint32_t width, height;
};
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
} __attribute__((packed));
/**
* Defines a position in the table
@ -171,6 +183,9 @@ struct Workspace {
/** Number of this workspace, starting from 0 */
int num;
/** Name of the workspace (in UTF-8) */
char *utf8_name;
/** Name of the workspace (in UCS-2) */
char *name;
@ -200,12 +215,8 @@ struct Workspace {
/** 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 name of the RandR output this screen should be on */
char *preferred_output;
/** True if any client on this workspace has its urgent flag set */
bool urgent;
@ -224,8 +235,8 @@ struct Workspace {
* appended) */
TAILQ_HEAD(floating_clients_head, Client) floating_clients;
/** Backpointer to the screen this workspace is on */
i3Screen *screen;
/** Backpointer to the output this workspace is on */
Output *output;
/** This is a two-dimensional dynamic array of
* Container-pointers. Ive always wanted to be a three-star
@ -492,14 +503,26 @@ struct Container {
};
/**
* This is a virtual screen (Xinerama). This can be a single one, though two
* monitors might be connected, if youre running clone mode. There can also
* be multiple of them.
* An Output is a physical output on your graphics driver. Outputs which
* are currently in use have (output->active == true). Each output has a
* position and a mode. An output usually corresponds to one connected
* screen (except if you are running multiple screens in clone mode).
*
*/
struct Screen {
/** Virtual screen number */
int num;
struct xoutput {
/** Output id, so that we can requery the output directly later */
xcb_randr_output_t id;
/** Name of the output */
char *name;
/** Whether the output is currently active (has a CRTC attached with a
* valid mode) */
bool active;
/** Internal flags, necessary for querying RandR screens (happens in
* two stages) */
bool changed;
bool to_be_disabled;
/** Current workspace selected on this virtual screen */
Workspace *current_workspace;
@ -515,7 +538,7 @@ struct Screen {
* _NET_WM_WINDOW_TYPE_DOCK */
SLIST_HEAD(dock_clients_head, Client) dock_clients;
TAILQ_ENTRY(Screen) screens;
TAILQ_ENTRY(xoutput) outputs;
};
#endif

42
include/ewmh.h Normal file
View File

@ -0,0 +1,42 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#ifndef _EWMH_C
#define _EWMH_C
/**
* Updates _NET_CURRENT_DESKTOP with the current desktop number.
*
* EWMH: The index of the current desktop. This is always an integer between 0
* and _NET_NUMBER_OF_DESKTOPS - 1.
*
*/
void ewmh_update_current_desktop();
/**
* Updates _NET_ACTIVE_WINDOW with the currently focused window.
*
* EWMH: The window ID of the currently active window or None if no window has
* the focus.
*
*/
void ewmh_update_active_window(xcb_window_t window);
/**
* Updates the workarea for each desktop.
*
* EWMH: Contains a geometry for each desktop. These geometries specify an area
* that is completely contained within the viewport. Work area SHOULD be used by
* desktop applications to place desktop icons appropriately.
*
*/
void ewmh_update_workarea();
#endif

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -12,10 +12,19 @@
#define _FLOATING_H
/** Callback for dragging */
typedef void(*callback_t)(Rect*, uint32_t, uint32_t);
typedef void(*callback_t)(xcb_connection_t*, Client*, Rect*, uint32_t, uint32_t, void*);
/** Macro to create a callback function for dragging */
#define DRAGGING_CB(name) \
static void name(xcb_connection_t *conn, Client *client, \
Rect *old_rect, uint32_t new_x, uint32_t new_y, \
void *extra)
/** On which border was the dragging initiated? */
typedef enum { BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM} border_t;
typedef enum { BORDER_LEFT = (1 << 0),
BORDER_RIGHT = (1 << 1),
BORDER_TOP = (1 << 2),
BORDER_BOTTOM = (1 << 3)} border_t;
/**
* Enters floating mode for the given client. Correctly takes care of the
@ -56,13 +65,13 @@ void floating_drag_window(xcb_connection_t *conn, Client *client,
xcb_button_press_event_t *event);
/**
* Called when the user right-clicked on the titlebar of a floating window to
* resize it.
* Called when the user clicked on a floating window while holding the
* floating_modifier and the right mouse button.
* Calls the drag_pointer function with the resize_window callback
*
*/
void floating_resize_window(xcb_connection_t *conn, Client *client,
xcb_button_press_event_t *event);
bool proportional, xcb_button_press_event_t *event);
/**
* Changes focus in the given direction for floating clients.
@ -97,6 +106,7 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace);
*
*/
void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
xcb_window_t confine_to, border_t border, callback_t callback);
xcb_window_t confine_to, border_t border, callback_t callback,
void *extra);
#endif

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -11,13 +11,7 @@
#ifndef _HANDLERS_H
#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.
*
*/
int handle_key_release(void *ignored, xcb_connection_t *conn,
xcb_key_release_event_t *event);
#include <xcb/randr.h>
/**
* There was a key press. We compare this key code with our bindings table and
@ -74,6 +68,14 @@ int handle_map_request(void *prophs, xcb_connection_t *conn,
*/
int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event);
/**
* Gets triggered upon a RandR screen change event, that is when the user
* changes the screen configuration in any way (mode, position, )
*
*/
int handle_screen_change(void *prophs, xcb_connection_t *conn,
xcb_generic_event_t *e);
/**
* Configure requests are received when the application wants to resize
* windows on their own.
@ -92,6 +94,18 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn,
*/
int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event);
/**
* A destroy notify event is sent when the window is not unmapped, but
* immediately destroyed (for example when starting a window and immediately
* killing the program which started it).
*
* We just pass on the event to the unmap notify handler (by copying the
* important fields in the event data structure).
*
*/
int handle_destroy_notify_event(void *data, xcb_connection_t *conn,
xcb_destroy_notify_event_t *event);
/**
* Called when a window changes its title
*

View File

@ -21,19 +21,20 @@
#ifndef _I3_H
#define _I3_H
#define NUM_ATOMS 18
#define NUM_ATOMS 21
extern xcb_connection_t *global_conn;
extern xcb_key_symbols_t *keysyms;
extern char **start_argv;
extern Display *xkbdpy;
extern int xkb_current_group;
extern TAILQ_HEAD(bindings_head, Binding) *bindings;
extern TAILQ_HEAD(autostarts_head, Autostart) autostarts;
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 bool xkb_supported;
extern xcb_atom_t atoms[NUM_ATOMS];
extern xcb_window_t root;

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -15,10 +15,53 @@
#ifndef _I3_IPC_H
#define _I3_IPC_H
/*
* Messages from clients to i3
*
*/
/** Never change this, only on major IPC breakage (dont do that) */
#define I3_IPC_MAGIC "i3-ipc"
/** The payload of the message will be interpreted as a command */
#define I3_IPC_MESSAGE_TYPE_COMMAND 0
#define I3_IPC_MESSAGE_TYPE_COMMAND 0
/** Requests the current workspaces from i3 */
#define I3_IPC_MESSAGE_TYPE_GET_WORKSPACES 1
/** Subscribe to the specified events */
#define I3_IPC_MESSAGE_TYPE_SUBSCRIBE 2
/** Requests the current outputs from i3 */
#define I3_IPC_MESSAGE_TYPE_GET_OUTPUTS 3
/*
* Messages from i3 to clients
*
*/
/** Command reply type */
#define I3_IPC_REPLY_TYPE_COMMAND 0
/** Workspaces reply type */
#define I3_IPC_REPLY_TYPE_WORKSPACES 1
/** Subscription reply type */
#define I3_IPC_REPLY_TYPE_SUBSCRIBE 2
/** Outputs reply type */
#define I3_IPC_REPLY_TYPE_OUTPUTS 3
/*
* Events from i3 to clients. Events have the first bit set high.
*
*/
#define I3_IPC_EVENT_MASK (1 << 31)
/* The workspace event will be triggered upon changes in the workspace list */
#define I3_IPC_EVENT_WORKSPACE (I3_IPC_EVENT_MASK | 0)
/* The output event will be triggered upon changes in the output list */
#define I3_IPC_EVENT_OUTPUT (I3_IPC_EVENT_MASK | 1)
#endif

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -16,6 +16,34 @@
#include "i3/ipc.h"
typedef struct ipc_client {
int fd;
/* The events which this client wants to receive */
int num_events;
char **events;
TAILQ_ENTRY(ipc_client) clients;
} ipc_client;
/*
* Callback type for the different message types.
*
* 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.
*
*/
typedef void(*handler_t)(int, uint8_t*, int, uint32_t, uint32_t);
/* Macro to declare a callback */
#define IPC_HANDLER(name) \
static void handle_ ## name (int fd, uint8_t *message, \
int size, uint32_t message_size, \
uint32_t 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
@ -32,4 +60,18 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents);
*/
int ipc_create_socket(const char *filename);
/**
* Sends the specified event to all IPC clients which are currently connected
* and subscribed to this kind of event.
*
*/
void ipc_send_event(const char *event, uint32_t message_type, const char *payload);
/**
* Calls shutdown() on each socket and closes it. This function to be called
* when exiting or restarting only!
*
*/
void ipc_shutdown();
#endif

View File

@ -79,7 +79,7 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace,
* Renders the given workspace on the given screen
*
*/
void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws);
void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws);
/**
* Renders the whole layout, that is: Go through each screen, each workspace,

66
include/log.h Normal file
View File

@ -0,0 +1,66 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#ifndef _LOG_H
#define _LOG_H
#include <stdarg.h>
#include <stdbool.h>
/** ##__VA_ARGS__ means: leave out __VA_ARGS__ completely if it is empty, that
is, delete the preceding comma */
#define LOG(fmt, ...) verboselog(fmt, ##__VA_ARGS__)
#define ELOG(fmt, ...) errorlog("ERROR: " fmt, ##__VA_ARGS__)
#define DLOG(fmt, ...) debuglog(LOGLEVEL, "%s:%s:%d - " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__)
extern char *loglevels[];
/**
* Enables the given loglevel.
*
*/
void add_loglevel(const char *level);
/**
* Set verbosity of i3. If verbose is set to true, informative messages will
* be printed to stdout. If verbose is set to false, only errors will be
* printed.
*
*/
void set_verbosity(bool _verbose);
/**
* Logs the given message to stdout while prefixing the current time to it,
* but only if the corresponding debug loglevel was activated.
*
*/
void debuglog(int lev, char *fmt, ...);
/**
* Logs the given message to stdout while prefixing the current time to it.
*
*/
void errorlog(char *fmt, ...);
/**
* Logs the given message to stdout while prefixing the current time to it,
* but only if verbose mode is activated.
*
*/
void verboselog(char *fmt, ...);
/**
* Logs the given message to stdout while prefixing the current time to it.
* This is to be called by LOG() which includes filename/linenumber
*
*/
void slog(char *fmt, va_list args);
#endif

View File

@ -23,6 +23,16 @@
void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t
*prophs, xcb_window_t root);
/**
* Restores the geometry of each window by reparenting it to the root window
* at the position of its frame.
*
* This is to be called *only* before exiting/restarting i3 because of evil
* side-effects which are to be expected when continuing to run i3.
*
*/
void restore_geometry(xcb_connection_t *conn);
/**
* Do some sanity checks and then reparent the window.
*

75
include/randr.h Normal file
View File

@ -0,0 +1,75 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include "data.h"
#include <xcb/randr.h>
#ifndef _RANDR_H
#define _RANDR_H
TAILQ_HEAD(outputs_head, xoutput);
extern struct outputs_head outputs;
/**
* We have just established a connection to the X server and need the initial
* XRandR information to setup workspaces for each screen.
*
*/
void initialize_randr(xcb_connection_t *conn, int *event_base);
/**
* Disables RandR support by creating exactly one output with the size of the
* X11 screen.
*
*/
void disable_randr(xcb_connection_t *conn);
/**
* Initializes the specified output, assigning the specified workspace to it.
*
*/
void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace);
/**
* (Re-)queries the outputs via RandR and stores them in the list of outputs.
*
*/
void randr_query_outputs(xcb_connection_t *conn);
/**
* Returns the first output which is active.
*
*/
Output *get_first_output();
/**
* Returns the output with the given name if it is active (!) or NULL.
*
*/
Output *get_output_by_name(const char *name);
/**
* Returns the active (!) output which contains the coordinates x, y or NULL
* if there is no output which contains these coordinates.
*
*/
Output *get_output_containing(int x, int y);
/**
* Gets the output which is the last one in the given direction, for example
* the output on the most bottom when direction == D_DOWN, the output most
* right when direction == D_RIGHT and so on.
*
* This function always returns a output.
*
*/
Output *get_output_most(direction_t direction, Output *current);
#endif

21
include/sighandler.h Normal file
View File

@ -0,0 +1,21 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2010 Michael Stapelberg and contributors
* © 2009-2010 Jan-Erik Rediger
*
* See file LICENSE for license information.
*
*/
#ifndef _SIGHANDLER_H
#define _SIGHANDLER_H
/**
* Setup signal handlers to safely handle SIGSEGV and SIGFPE
*
*/
void setup_signal_handler();
#endif

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -34,10 +34,6 @@
} \
while (0)
/** ##__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);
extern struct keyvalue_table_head by_parent;
extern struct keyvalue_table_head by_child;
@ -46,11 +42,11 @@ int min(int a, int b);
int max(int a, int b);
/**
* Logs the given message to stdout while prefixing the current time to it.
* This is to be called by LOG() which includes filename/linenumber
* Updates *destination with new_value and returns true if it was changed or false
* if it was the same
*
*/
void slog(char *fmt, ...);
bool update_if_necessary(uint32_t *destination, const uint32_t new_value);
/**
* Safe-wrapper around malloc which exits if malloc returns NULL (meaning that
@ -161,6 +157,13 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode);
Client *get_matching_client(xcb_connection_t *conn,
const char *window_classtitle, Client *specific);
/*
* Restart i3 in-place
* appends -a to argument list to disable autostart
*
*/
void i3_restart();
#if defined(__OpenBSD__)
/* OpenBSD does not provide memmem(), so we provide FreeBSDs implementation */
void *memmem(const void *l, size_t l_len, const void *s, size_t s_len);

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -11,7 +11,7 @@
#include <xcb/xcb.h>
#include "data.h"
#include "xinerama.h"
#include "randr.h"
#ifndef _WORKSPACE_H
#define _WORKSPACE_H
@ -44,6 +44,17 @@ bool workspace_is_visible(Workspace *ws);
/** Switches to the given workspace */
void workspace_show(xcb_connection_t *conn, int workspace);
/**
* Assigns the given workspace to the given screen by correctly updating its
* state and reconfiguring all the clients on this workspace.
*
* This is called when initializing a screen and when re-assigning it to a
* different screen which just got available (if you configured it to be on
* screen 1 and you just plugged in screen 1).
*
*/
void workspace_assign_to(Workspace *ws, Output *screen, bool hide_it);
/**
* 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
@ -51,14 +62,14 @@ void workspace_show(xcb_connection_t *conn, int workspace);
* the screen is not attached at the moment.
*
*/
void workspace_initialize(Workspace *ws, i3Screen *screen);
void workspace_initialize(Workspace *ws, Output *screen, bool recheck);
/**
* 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 *get_first_workspace_for_output(Output *screen);
/**
* Unmaps all clients (and stack windows) of the given workspace.

View File

@ -61,7 +61,10 @@ enum { _NET_SUPPORTED = 0,
WM_DELETE_WINDOW,
UTF8_STRING,
WM_STATE,
WM_CLIENT_LEADER
WM_CLIENT_LEADER,
_NET_CURRENT_DESKTOP,
_NET_ACTIVE_WINDOW,
_NET_WORKAREA
};
extern unsigned int xcb_numlock_mask;
@ -161,4 +164,10 @@ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap)
int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *text,
int length);
/**
* Configures the given window to have the size/position specified by given rect
*
*/
void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r);
#endif

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -13,16 +13,6 @@
#ifndef _XINERAMA_H
#define _XINERAMA_H
TAILQ_HEAD(screens_head, Screen);
extern struct screens_head *virtual_screens;
/**
* 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.
@ -30,33 +20,4 @@ bool screens_are_equal(i3Screen *screen1, i3Screen *screen2);
*/
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.
*
*/
void xinerama_requery_screens(xcb_connection_t *conn);
/**
* Looks in virtual_screens for the i3Screen whose start coordinates are x, y
*
*/
i3Screen *get_screen_at(int x, int y, struct screens_head *screenlist);
/**
* Looks in virtual_screens for the i3Screen which contains coordinates x, y
*
*/
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.
*
* This function always returns a screen.
*
*/
i3Screen *get_screen_most(direction_t direction, i3Screen *current);
#endif

View File

@ -1,6 +1,18 @@
all:
a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3.man
a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-msg.man
a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-input.man
A2M:=a2x -f manpage --asciidoc-opts="-f asciidoc.conf"
all: i3.1 i3-msg.1 i3-input.1 i3-wsbar.1
%.1: %.man asciidoc.conf
${A2M} $<
i3-wsbar.1: ../i3-wsbar
pod2man $^ > $@
clean:
rm -f i3.{1,html,xml} i3-msg.{1,html,xml} i3-input.{1,html,xml}
for file in "i3 i3-msg i3-input"; \
do \
rm -f $${file}.1 $${file}.html $${file}.xml; \
done
distclean: clean
rm -f *.1

View File

@ -7,7 +7,7 @@ template::[header-declarations]
<refentrytitle>{mantitle}</refentrytitle>
<manvolnum>{manvolnum}</manvolnum>
<refmiscinfo class="source">i3</refmiscinfo>
<refmiscinfo class="version">delta</refmiscinfo>
<refmiscinfo class="version">epsilon</refmiscinfo>
<refmiscinfo class="manual">i3 Manual</refmiscinfo>
</refmeta>
<refnamediv>

View File

@ -5,7 +5,7 @@ v3.delta, November 2009
== NAME
i3-input - interactively take a command for i3
i3-input - interactively take a command for i3 window manager
== SYNOPSIS
@ -13,8 +13,9 @@ i3-input [-s <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-v]
== DESCRIPTION
i3-input is a tool to take commands (or parts of a command) and then send it
to i3. This is useful for example for the mark/goto command.
i3-input is a tool to take commands (or parts of a command) composed by
the user, and send it/them to i3. This is useful, for example, for the
mark/goto command.
== EXAMPLE

View File

@ -5,7 +5,7 @@ v3.delta, November 2009
== NAME
i3-msg - send messages to i3
i3-msg - send messages to i3 window manager
== SYNOPSIS

View File

@ -1,7 +1,7 @@
i3(1)
=====
Michael Stapelberg <michael+i3@stapelberg.de>
v3.delta, November 2009
v3.epsilon, March 2010
== NAME
@ -9,65 +9,88 @@ i3 - an improved dynamic, tiling window manager
== SYNOPSIS
i3 [-c configfile] [-a]
i3 [-a] [-c configfile] [-C] [-d <loglevel>] [-v] [-V]
== OPTIONS
-c::
Specifies an alternate configuration file path
-a::
Disables autostart.
-c::
Specifies an alternate configuration file path.
-C::
Check the configuration file for validity and exit.
-d::
Specifies the debug loglevel. To see the most output, use -d all.
-v::
Display version number (and date of the last commit).
-V::
Be verbose.
== DESCRIPTION
=== INTRODUCTION
i3 was created because wmii, our favorite window manager at the time, didnt
provide some features we wanted (Xinerama done right, for example), had some
bugs, didnt progress since quite some time and wasnt easy to hack at all
provide some features we wanted (multi-monitor done right, for example), had
some bugs, didnt progress since quite some time and wasnt easy to hack at all
(source code comments/documentation completely lacking). Still, we think the
wmii developers and contributors did a great job. Thank you for inspiring us to
create i3.
Please be aware that i3 is primarily targeted at advanced users and developers.
=== IMPORTANT NOTE TO nVidia BINARY DRIVER USERS
If you are using the nVidia binary graphics driver (also known as 'blob')
you need to use the +--force-xinerama+ flag (in your .xsession) when starting
i3, like so:
----------------------------------------------
exec i3 --force-xinerama -V >>~/.i3/i3log 2>&1
----------------------------------------------
See also docs/multi-monitor for the full explanation.
=== TERMINOLOGY
Client::
A client is X11-speak for a window.
Table::
Your workspace is managed using a table. You can move windows around and create new columns
(move a client to the right) or rows (move it to the bottom) implicitly.
Your workspace is managed using a table. You can move windows around and create
new columns (move a client to the right) or rows (move it to the bottom)
implicitly.
+
By "snapping" a client in a specific direction, you increase its colspan/rowspan.
Container::
A container contains a variable number of clients. Each cell of the table is a container.
A container contains a variable number of clients. Each cell of the table is a
container.
+
Containers can be used in various modes. The default mode is called "default" and just
resizes each client equally so that it fits.
Containers can be used in various modes. The default mode is called "default"
and just resizes each client equally so that it fits.
Workspace::
A workspace is a set of clients (technically speaking, its just a table). Other window
managers call this "Virtual Desktops".
A workspace is a set of clients (technically speaking, its just a table).
Other window managers call this "Virtual Desktops".
+
In i3, each workspace is assigned to a specific virtual screen. By default, screen 1
has workspace 1, screen 2 has workspace 2 and so on… However, when you create a new
workspace (by simply switching to it), itll be assigned the screen you are currently
on.
In i3, each workspace is assigned to a specific virtual screen. By default,
screen 1 has workspace 1, screen 2 has workspace 2 and so on… However, when you
create a new workspace (by simply switching to it), itll be assigned the
screen you are currently on.
Virtual Screen::
Using Xinerama, you can have an X11 screen spanning multiple real monitors. Furthermore,
you can set them up in cloning mode or with positions (monitor 1 is left of monitor 2).
Output::
Using XRandR, you can have an X11 screen spanning multiple real monitors.
Furthermore, you can set them up in cloning mode or with positions (monitor 1
is left of monitor 2).
+
A virtual screen is the result of your Xinerama setup. For example, if you have attached
two real monitors (lets say your laptop screen and a video projector) and enabled cloning, i3
will use one virtual screen with the size of the smallest screen you have attached (so
that you can see all your windows on each screen all the time).
If you have two monitors attached, one configured to be left of the other, i3 will use
two virtual screens.
i3 uses the RandR API to query which outputs are available and which screens
are connected to these outputs.
== KEYBINDINGS
@ -114,31 +137,34 @@ Mod1+t::
Select the first tiling window if the current window is floating and vice-versa.
Mod1+Shift+q::
Kills the current window. This is equivalent to "clicking on the close button", meaning a polite
request to the application to close this window. For example, Firefox will save its session
upon such a request. If the application does not support that, the window will be killed and
it depends on the application what happens.
Kills the current window. This is equivalent to "clicking on the close button",
meaning a polite request to the application to close this window. For example,
Firefox will save its session upon such a request. If the application does not
support that, the window will be killed and it depends on the application what
happens.
Mod1+Shift+r::
Restarts i3 in place (without losing any windows, but the layout).
Restarts i3 in place (without losing any windows, but at this time, the layout
and placement of windows is not retained).
Mod1+Shift+e::
Exits i3.
== FILES
=== ~/.i3/config
=== \~/.i3/config (or ~/.config/i3/config)
When starting, i3 looks for ~/.i3/config and loads the configuration. If ~/.i3/config is not found,
i3 tries /etc/i3/config. You can specify a custom path using the -c option.
When starting, i3 looks for configuration files in the following order:
At the moment, you can specify only the path to your favorite terminal emulator, the font and keybindings.
1. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set)
2. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set)
3. ~/.i3/config
4. /etc/i3/config
At the moment, you have to bind to keycodes (find them out via xev(1)).
You can specify a custom path using the -c option.
.Sample configuration
-------------------------------------------------------------
terminal /usr/bin/urxvt
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# Start terminal (Mod1+Enter)
@ -265,22 +291,25 @@ ulimit -c unlimited
# Start i3 and log to ~/.i3/logfile
echo "Starting at $(date)" >> ~/.i3/logfile
exec /usr/bin/i3 >> ~/.i3/logfile
exec /usr/bin/i3 -V -d all >> ~/.i3/logfile
-------------------------------------------------------------
== TODO
There is still lot of work to do. Please check our bugtracker for up-to-date information
about tasks which are still not finished.
There is still lot of work to do. Please check our bugtracker for up-to-date
information about tasks which are still not finished.
== SEE ALSO
You should have a copy of the userguide (featuring nice screenshots/graphics which is why this
is not integrated into this manpage), the debugging guide and the "how to hack" guide. If you
are building from source, run +make -C docs+.
You should have a copy of the userguide (featuring nice screenshots/graphics
which is why this is not integrated into this manpage), the debugging guide,
and the "how to hack" guide. If you are building from source, run:
+make -C docs+
You can also access these documents online at http://i3.zekjur.net/
i3-input(1), i3-msg(1), i3-wsbar(1)
== AUTHOR
Michael Stapelberg and contributors

View File

@ -1,5 +1,7 @@
%option nounput
%option noinput
%option noyy_top_state
%option stack
%{
/*
@ -13,20 +15,59 @@
#include "data.h"
#include "config.h"
#include "log.h"
#include "util.h"
int yycolumn = 1;
#define YY_DECL int yylex (struct context *context)
#define YY_USER_ACTION { \
context->first_column = yycolumn; \
context->last_column = yycolumn+yyleng-1; \
yycolumn += yyleng; \
}
%}
%Start BIND_COND
%Start BINDSYM_COND
%Start BIND_AWS_COND
%Start BINDSYM_AWS_COND
%Start BIND_A2WS_COND
%Start ASSIGN_COND
%Start COLOR_COND
%Start SCREEN_COND
%Start SCREEN_AWS_COND
EOL (\r?\n)
%s BIND_COND
%s BINDSYM_COND
%s BIND_AWS_COND
%s BINDSYM_AWS_COND
%s BIND_A2WS_COND
%s ASSIGN_COND
%s COLOR_COND
%s OUTPUT_COND
%s OUTPUT_AWS_COND
%x BUFFER_LINE
%%
{
/* This is called when a new line is lexed. We only want the
* first line to match to go into state BUFFER_LINE */
if (context->line_number == 0) {
context->line_number = 1;
BEGIN(INITIAL);
yy_push_state(BUFFER_LINE);
}
}
<BUFFER_LINE>^[^\r\n]*/{EOL}? {
/* save whole line */
context->line_copy = strdup(yytext);
yyless(0);
yy_pop_state();
yy_set_bol(true);
yycolumn = 1;
}
<BIND_A2WS_COND>[^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; }
<OUTPUT_AWS_COND>[a-zA-Z0-9_-]+ { yylval.string = strdup(yytext); return OUTPUT; }
^[ \t]*#[^\n]* { return TOKCOMMENT; }
<COLOR_COND>[0-9a-fA-F]+ { yylval.string = strdup(yytext); return HEX; }
[0-9]+ { yylval.number = atoi(yytext); return NUMBER; }
@ -35,7 +76,14 @@ bind { BEGIN(BIND_COND); return TOKBIND; }
bindsym { BEGIN(BINDSYM_COND); return TOKBINDSYM; }
floating_modifier { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; }
workspace { BEGIN(INITIAL); return TOKWORKSPACE; }
screen { BEGIN(SCREEN_COND); return TOKSCREEN; }
output { BEGIN(OUTPUT_COND); return TOKOUTPUT; }
screen {
/* for compatibility until v3.φ */
ELOG("Assignments to screens are DEPRECATED and will not work. " \
"Please replace them with assignments to outputs.\n");
BEGIN(OUTPUT_COND);
return TOKOUTPUT;
}
terminal { BEGIN(BIND_AWS_COND); return TOKTERMINAL; }
font { BEGIN(BIND_AWS_COND); return TOKFONT; }
assign { BEGIN(ASSIGN_COND); return TOKASSIGN; }
@ -44,6 +92,8 @@ ipc-socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; }
ipc_socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; }
new_container { return TOKNEWCONTAINER; }
new_window { return TOKNEWWINDOW; }
focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; }
workspace_bar { return TOKWORKSPACEBAR; }
default { yylval.number = MODE_DEFAULT; return TOKCONTAINERMODE; }
stacking { yylval.number = MODE_STACK; return TOKCONTAINERMODE; }
tabbed { yylval.number = MODE_TABBED; return TOKCONTAINERMODE; }
@ -65,16 +115,21 @@ Mod4 { yylval.number = BIND_MOD4; return MODIFIER; }
Mod5 { yylval.number = BIND_MOD5; return MODIFIER; }
Mode_switch { yylval.number = BIND_MODE_SWITCH; return MODIFIER; }
control { return TOKCONTROL; }
ctrl { return TOKCONTROL; }
shift { return TOKSHIFT; }
→ { return TOKARROW; }
\n /* ignore end of line */;
<SCREEN_AWS_COND>x { return (int)yytext[0]; }
{EOL} {
FREE(context->line_copy);
context->line_number++;
BEGIN(INITIAL);
yy_push_state(BUFFER_LINE);
}
<BIND_COND>[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; }
<BINDSYM_COND>[ \t]+ { BEGIN(BINDSYM_AWS_COND); return WHITESPACE; }
<BIND_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
<BINDSYM_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
<SCREEN_COND>[ \t]+ { BEGIN(SCREEN_AWS_COND); return WHITESPACE; }
<SCREEN_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
<OUTPUT_COND>[ \t]+ { BEGIN(OUTPUT_AWS_COND); return WHITESPACE; }
<OUTPUT_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
[ \t]+ { return WHITESPACE; }
\"[^\"]+\" {
/* if ASSIGN_COND then */
@ -89,4 +144,11 @@ shift { return TOKSHIFT; }
<BINDSYM_AWS_COND>[a-zA-Z0-9_]+ { yylval.string = strdup(yytext); return WORD; }
[a-zA-Z]+ { yylval.string = strdup(yytext); return WORD; }
. { return (int)yytext[0]; }
<<EOF>> {
while (yy_start_stack_ptr > 0)
yy_pop_state();
yyterminate();
}
%%

View File

@ -21,20 +21,35 @@
#include "table.h"
#include "workspace.h"
#include "xcb.h"
#include "log.h"
typedef struct yy_buffer_state *YY_BUFFER_STATE;
extern int yylex(void);
extern int yylex(struct context *context);
extern int yyparse(void);
extern FILE *yyin;
YY_BUFFER_STATE yy_scan_string(const char *);
static struct bindings_head *current_bindings;
static struct context *context;
int yydebug = 1;
/* We dont need yydebug for now, as we got decent error messages using
* yyerror(). Should you ever want to extend the parser, it might be handy
* to just comment it in again, so it stays here. */
//int yydebug = 1;
void yyerror(const char *str) {
fprintf(stderr,"error: %s\n",str);
void yyerror(const char *error_message) {
ELOG("\n");
ELOG("CONFIG: %s\n", error_message);
ELOG("CONFIG: in file \"%s\", line %d:\n",
context->filename, context->line_number);
ELOG("CONFIG: %s\n", context->line_copy);
ELOG("CONFIG: ");
for (int c = 1; c <= context->last_column; c++)
if (c >= context->first_column)
printf("^");
else printf(" ");
printf("\n");
ELOG("\n");
}
int yywrap() {
@ -55,7 +70,7 @@ void parse_file(const char *f) {
if (fstat(fd, &stbuf) == -1)
die("Could not fstat file: %s\n", strerror(errno));
buf = smalloc(stbuf.st_size * sizeof(char));
buf = scalloc((stbuf.st_size + 1) * sizeof(char));
while (read_bytes < stbuf.st_size) {
if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0)
die("Could not read(): %s\n", strerror(errno));
@ -95,7 +110,7 @@ void parse_file(const char *f) {
new->key = sstrdup(v_key);
new->value = sstrdup(v_value);
SLIST_INSERT_HEAD(&variables, new, variables);
LOG("Got new variable %s = %s\n", v_key, v_value);
DLOG("Got new variable %s = %s\n", v_key, v_value);
continue;
}
}
@ -149,18 +164,33 @@ void parse_file(const char *f) {
yy_scan_string(new);
context = scalloc(sizeof(struct context));
context->filename = f;
if (yyparse() != 0) {
fprintf(stderr, "Could not parse configfile\n");
exit(1);
}
FREE(context->line_copy);
free(context);
free(new);
free(buf);
while (!SLIST_EMPTY(&variables)) {
current = SLIST_FIRST(&variables);
FREE(current->key);
FREE(current->value);
SLIST_REMOVE_HEAD(&variables, variables);
FREE(current);
}
}
%}
%expect 1
%error-verbose
%lex-param { struct context *context }
%union {
int number;
@ -170,40 +200,44 @@ void parse_file(const char *f) {
struct Binding *binding;
}
%token <number>NUMBER
%token <string>WORD
%token <string>STR
%token <string>STR_NG
%token <string>HEX
%token <number>NUMBER "<number>"
%token <string>WORD "<word>"
%token <string>STR "<string>"
%token <string>STR_NG "<string (non-greedy)>"
%token <string>HEX "<hex>"
%token <string>OUTPUT "<RandR output>"
%token TOKBIND
%token TOKTERMINAL
%token TOKCOMMENT
%token TOKFONT
%token TOKBINDSYM
%token MODIFIER
%token TOKCONTROL
%token TOKSHIFT
%token WHITESPACE
%token TOKFLOATING_MODIFIER
%token QUOTEDSTRING
%token TOKWORKSPACE
%token TOKSCREEN
%token TOKASSIGN
%token TOKCOMMENT "<comment>"
%token TOKFONT "font"
%token TOKBINDSYM "bindsym"
%token MODIFIER "<modifier>"
%token TOKCONTROL "control"
%token TOKSHIFT "shift"
%token WHITESPACE "<whitespace>"
%token TOKFLOATING_MODIFIER "floating_modifier"
%token QUOTEDSTRING "<quoted string>"
%token TOKWORKSPACE "workspace"
%token TOKOUTPUT "output"
%token TOKASSIGN "assign"
%token TOKSET
%token TOKIPCSOCKET
%token TOKEXEC
%token TOKIPCSOCKET "ipc_socket"
%token TOKEXEC "exec"
%token TOKCOLOR
%token TOKARROW
%token TOKMODE
%token TOKNEWCONTAINER
%token TOKNEWWINDOW
%token TOKCONTAINERMODE
%token TOKSTACKLIMIT
%token TOKARROW "→"
%token TOKMODE "mode"
%token TOKNEWCONTAINER "new_container"
%token TOKNEWWINDOW "new_window"
%token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse"
%token TOKWORKSPACEBAR "workspace_bar"
%token TOKCONTAINERMODE "default/stacking/tabbed"
%token TOKSTACKLIMIT "stack-limit"
%%
lines: /* empty */
| lines WHITESPACE line
| lines error
| lines line
;
@ -213,6 +247,8 @@ line:
| floating_modifier
| new_container
| new_window
| focus_follows_mouse
| workspace_bar
| workspace
| assign
| ipcsocket
@ -251,7 +287,7 @@ bind:
new->keycode = $<number>2;
new->mods = $<number>1;
new->command = sstrdup($<string>4);
new->command = $<string>4;
$<binding>$ = new;
}
@ -263,9 +299,9 @@ bindsym:
printf("\tFound symbolic mod%d with key %s and command %s\n", $<number>1, $<string>2, $<string>4);
Binding *new = scalloc(sizeof(Binding));
new->symbol = sstrdup($<string>2);
new->symbol = $<string>2;
new->mods = $<number>1;
new->command = sstrdup($<string>4);
new->command = $<string>4;
$<binding>$ = new;
}
@ -295,7 +331,7 @@ mode:
}
struct Mode *mode = scalloc(sizeof(struct Mode));
mode->name = strdup($<string>3);
mode->name = $<string>3;
mode->bindings = current_bindings;
current_bindings = NULL;
SLIST_INSERT_HEAD(&modes, mode, modes);
@ -325,7 +361,7 @@ modeline:
floating_modifier:
TOKFLOATING_MODIFIER WHITESPACE binding_modifiers
{
LOG("floating modifier = %d\n", $<number>3);
DLOG("floating modifier = %d\n", $<number>3);
config.floating_modifier = $<number>3;
}
;
@ -333,7 +369,7 @@ floating_modifier:
new_container:
TOKNEWCONTAINER WHITESPACE TOKCONTAINERMODE
{
LOG("new containers will be in mode %d\n", $<number>3);
DLOG("new containers will be in mode %d\n", $<number>3);
config.container_mode = $<number>3;
/* We also need to change the layout of the already existing
@ -355,7 +391,7 @@ new_container:
}
| TOKNEWCONTAINER WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER
{
LOG("stack-limit %d with val %d\n", $<number>5, $<number>7);
DLOG("stack-limit %d with val %d\n", $<number>5, $<number>7);
config.container_stack_limit = $<number>5;
config.container_stack_limit_value = $<number>7;
@ -374,32 +410,69 @@ new_container:
new_window:
TOKNEWWINDOW WHITESPACE WORD
{
LOG("new windows should start in mode %s\n", $<string>3);
config.default_border = strdup($<string>3);
DLOG("new windows should start in mode %s\n", $<string>3);
config.default_border = sstrdup($<string>3);
}
;
bool:
NUMBER
{
$<number>$ = ($<number>1 == 1);
}
| WORD
{
DLOG("checking word \"%s\"\n", $<string>1);
$<number>$ = (strcasecmp($<string>1, "yes") == 0 ||
strcasecmp($<string>1, "true") == 0 ||
strcasecmp($<string>1, "on") == 0 ||
strcasecmp($<string>1, "enable") == 0 ||
strcasecmp($<string>1, "active") == 0);
}
;
focus_follows_mouse:
TOKFOCUSFOLLOWSMOUSE WHITESPACE bool
{
DLOG("focus follows mouse = %d\n", $<number>3);
config.disable_focus_follows_mouse = !($<number>3);
}
;
workspace_bar:
TOKWORKSPACEBAR WHITESPACE bool
{
DLOG("workspace bar = %d\n", $<number>3);
config.disable_workspace_bar = !($<number>3);
}
;
workspace:
TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKSCREEN WHITESPACE screen optional_workspace_name
TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKOUTPUT WHITESPACE OUTPUT optional_workspace_name
{
int ws_num = $<number>3;
if (ws_num < 1) {
LOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
} else {
Workspace *ws = workspace_get(ws_num - 1);
ws->preferred_screen = sstrdup($<string>7);
if ($<string>8 != NULL)
ws->preferred_output = $<string>7;
if ($<string>8 != NULL) {
workspace_set_name(ws, $<string>8);
free($<string>8);
}
}
}
| TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name
{
int ws_num = $<number>3;
if (ws_num < 1) {
LOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
} else {
if ($<string>5 != NULL)
DLOG("workspace name to: %s\n", $<string>5);
if ($<string>5 != NULL) {
workspace_set_name(workspace_get(ws_num - 1), $<string>5);
free($<string>5);
}
}
}
;
@ -415,13 +488,6 @@ workspace_name:
| WORD { $<string>$ = $<string>1; }
;
screen:
NUMBER { asprintf(&$<string>$, "%d", $<number>1); }
| NUMBER 'x' { asprintf(&$<string>$, "%d", $<number>1); }
| NUMBER 'x' NUMBER { asprintf(&$<string>$, "%dx%d", $<number>1, $<number>3); }
| 'x' NUMBER { asprintf(&$<string>$, "x%d", $<number>2); }
;
assign:
TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target
{
@ -430,7 +496,7 @@ assign:
struct Assignment *new = $<assignment>6;
printf(" to %d\n", new->workspace);
printf(" floating = %d\n", new->floating);
new->windowclass_title = strdup($<string>3);
new->windowclass_title = $<string>3;
TAILQ_INSERT_TAIL(&assignments, new, assignments);
}
;
@ -471,7 +537,7 @@ optional_arrow:
ipcsocket:
TOKIPCSOCKET WHITESPACE STR
{
config.ipc_socket_path = sstrdup($<string>3);
config.ipc_socket_path = $<string>3;
}
;
@ -479,7 +545,7 @@ exec:
TOKEXEC WHITESPACE STR
{
struct Autostart *new = smalloc(sizeof(struct Autostart));
new->command = sstrdup($<string>3);
new->command = $<string>3;
TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
}
;
@ -487,15 +553,15 @@ exec:
terminal:
TOKTERMINAL WHITESPACE STR
{
config.terminal = sstrdup($<string>3);
printf("terminal %s\n", config.terminal);
ELOG("The terminal option is DEPRECATED and has no effect. "
"Please remove it from your configuration file.\n");
}
;
font:
TOKFONT WHITESPACE STR
{
config.font = sstrdup($<string>3);
config.font = $<string>3;
printf("font %s\n", config.font);
}
;

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -36,6 +36,8 @@
#include "commands.h"
#include "floating.h"
#include "resize.h"
#include "log.h"
#include "randr.h"
static struct Stack_Window *get_stack_window(xcb_window_t window_id) {
struct Stack_Window *current;
@ -97,18 +99,18 @@ static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event
int wrap = ceil((float)num_clients / container->stack_limit_value);
int clicked_column = (event->event_x / (stack_win->rect.width / container->stack_limit_value));
int clicked_row = (event->event_y / decoration_height);
LOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
destination = (wrap * clicked_column) + clicked_row;
} else {
int width = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value));
int clicked_column = (event->event_x / width);
int clicked_row = (event->event_y / decoration_height);
LOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
destination = (container->stack_limit_value * clicked_column) + clicked_row;
}
}
LOG("Click on stack_win for client %d\n", destination);
DLOG("Click on stack_win for client %d\n", destination);
CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients)
if (c++ == destination) {
set_focus(conn, client, true);
@ -124,26 +126,26 @@ static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event
*
*/
static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *event) {
i3Screen *screen;
TAILQ_FOREACH(screen, virtual_screens, screens) {
if (screen->bar != event->event)
Output *output;
TAILQ_FOREACH(output, &outputs, outputs) {
if (output->bar != event->event)
continue;
LOG("Click on a bar\n");
DLOG("Click on a bar\n");
/* Check if the button was one of button4 or button5 (scroll up / scroll down) */
if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) {
Workspace *ws = c_ws;
if (event->detail == XCB_BUTTON_INDEX_5) {
while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) {
if (ws->screen == screen) {
if (ws->output == output) {
workspace_show(conn, ws->num + 1);
return true;
}
}
} else {
while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) {
if (ws->screen == screen) {
if (ws->output == output) {
workspace_show(conn, ws->num + 1);
return true;
}
@ -152,13 +154,13 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e
return true;
}
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 */
/* Because workspaces can be on different outputs, we need to loop
through all of them and decide to count it based on its ->output */
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen != screen)
if (ws->output != output)
continue;
LOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n",
DLOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n",
ws->num, drawn, ws->text_width);
if (event->event_x > (drawn + 1) &&
event->event_x <= (drawn + 1 + ws->text_width + 5 + 5)) {
@ -201,7 +203,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client,
Workspace *ws = con->workspace;
int first = 0, second = 0;
LOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n",
DLOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n",
to_right, to_left, to_top, to_bottom);
if (to_right < to_left &&
@ -209,7 +211,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client,
to_right < to_bottom) {
/* …right border */
first = con->col + (con->colspan - 1);
LOG("column %d\n", first);
DLOG("column %d\n", first);
if (!cell_exists(ws, first, con->row) ||
(first == (ws->cols-1)))
@ -251,7 +253,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client,
}
int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) {
LOG("Button %d pressed\n", event->state);
DLOG("Button %d pressed\n", event->state);
/* This was either a focus for a clients parent (= titlebar)… */
Client *client = table_get(&by_child, event->event);
bool border_click = false;
@ -263,23 +265,28 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
* to move around the client if it was floating. if not, we just process
* as usual. */
if (config.floating_modifier != 0 &&
(event->state & config.floating_modifier) != 0) {
(event->state & config.floating_modifier) == config.floating_modifier) {
if (client == NULL) {
LOG("Not handling, floating_modifier was pressed and no client found\n");
DLOG("Not handling, floating_modifier was pressed and no client found\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);
return 1;
}
if (client->fullscreen) {
LOG("Not handling, client is in fullscreen mode\n");
DLOG("Not handling, client is in fullscreen mode\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);
return 1;
}
if (client_is_floating(client)) {
LOG("button %d pressed\n", event->detail);
DLOG("button %d pressed\n", event->detail);
if (event->detail == 1) {
LOG("left mouse button, dragging\n");
DLOG("left mouse button, dragging\n");
floating_drag_window(conn, client, event);
} else if (event->detail == 3) {
LOG("right mouse button\n");
floating_resize_window(conn, client, event);
bool proportional = (event->state & BIND_SHIFT);
DLOG("right mouse button\n");
floating_resize_window(conn, client, proportional, event);
}
return 1;
}
@ -301,7 +308,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
if (button_press_bar(conn, event))
return 1;
LOG("Could not handle this button press\n");
DLOG("Could not handle this button press\n");
return 1;
}
@ -309,19 +316,19 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
set_focus(conn, client, true);
/* Lets see if this was on the borders (= resize). If not, were done */
LOG("press button on x=%d, y=%d\n", event->event_x, event->event_y);
DLOG("press button on x=%d, y=%d\n", event->event_x, event->event_y);
resize_orientation_t orientation = O_VERTICAL;
Container *con = client->container;
int first, second;
if (client->dock) {
LOG("dock. done.\n");
DLOG("dock. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);
return 1;
}
LOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width);
DLOG("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
@ -331,12 +338,12 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
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");
DLOG("Fixing border_click = false because of click in child\n");
border_click = false;
}
if (!border_click) {
LOG("client. done.\n");
DLOG("client. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
/* Floating clients should be raised on click */
if (client_is_floating(client))
@ -348,7 +355,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
/* Dont handle events inside the titlebar, only borders are interesting */
i3Font *font = load_font(conn, config.font);
if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) {
LOG("click on titlebar\n");
DLOG("click on titlebar\n");
/* Floating clients can be dragged by grabbing their titlebar */
if (client_is_floating(client)) {
@ -392,7 +399,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
} else if (event->event_x > 2) {
/* …right border */
first = con->col + (con->colspan - 1);
LOG("column %d\n", first);
DLOG("column %d\n", first);
if (!cell_exists(ws, first, con->row) ||
(first == (ws->cols-1)))

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -13,6 +13,7 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <limits.h>
#include <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
@ -26,6 +27,8 @@
#include "client.h"
#include "table.h"
#include "workspace.h"
#include "config.h"
#include "log.h"
/*
* Removes the given client from the container, either because it will be inserted into another
@ -43,7 +46,7 @@ void client_remove_from_container(xcb_connection_t *conn, Client *client, Contai
if (CIRCLEQ_EMPTY(&(container->clients)) &&
(container->mode == MODE_STACK ||
container->mode == MODE_TABBED)) {
LOG("Unmapping stack window\n");
DLOG("Unmapping stack window\n");
struct Stack_Window *stack_win = &(container->stack_win);
stack_win->rect.height = 0;
xcb_unmap_window(conn, stack_win->window);
@ -150,44 +153,82 @@ bool client_matches_class_name(Client *client, char *to_class, char *to_title,
* and when moving a fullscreen client to another screen.
*
*/
void client_enter_fullscreen(xcb_connection_t *conn, Client *client) {
Workspace *workspace = client->workspace;
void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global) {
Workspace *workspace;
Output *output;
Rect r;
if (workspace->fullscreen_client != NULL) {
LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
return;
if (global) {
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
continue;
if (output->current_workspace->fullscreen_client == NULL)
continue;
LOG("Not entering global fullscreen mode, there already "
"is a fullscreen client on output %s.\n", output->name);
return;
}
r = (Rect) { UINT_MAX, UINT_MAX, 0,0 };
Output *output;
/* Set fullscreen_client for each active workspace.
* Expand the rectangle to contain all outputs. */
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
continue;
output->current_workspace->fullscreen_client = client;
/* Temporarily abuse width/heigth as coordinates of the lower right corner */
if (r.x > output->rect.x)
r.x = output->rect.x;
if (r.y > output->rect.y)
r.y = output->rect.y;
if (r.x + r.width < output->rect.x + output->rect.width)
r.width = output->rect.x + output->rect.width;
if (r.y + r.height < output->rect.y + output->rect.height)
r.height = output->rect.y + output->rect.height;
}
/* Putting them back to their original meaning */
r.height -= r.x;
r.width -= r.y;
LOG("Entering global fullscreen mode...\n");
} else {
workspace = client->workspace;
if (workspace->fullscreen_client != NULL && workspace->fullscreen_client != client) {
LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
return;
}
workspace->fullscreen_client = client;
r = workspace->rect;
LOG("Entering fullscreen mode...\n");
}
client->fullscreen = true;
workspace->fullscreen_client = client;
LOG("Entering fullscreen mode...\n");
/* We just entered fullscreen mode, lets configure the window */
uint32_t mask = XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT;
uint32_t values[4] = {workspace->rect.x,
workspace->rect.y,
workspace->rect.width,
workspace->rect.height};
DLOG("child itself will be at %dx%d with size %dx%d\n",
r.x, r.y, r.width, r.height);
LOG("child itself will be at %dx%d with size %dx%d\n",
values[0], values[1], values[2], values[3]);
xcb_configure_window(conn, client->frame, mask, values);
xcb_set_window_rect(conn, client->frame, r);
/* Childs coordinates are relative to the parent (=frame) */
values[0] = 0;
values[1] = 0;
xcb_configure_window(conn, client->child, mask, values);
r.x = 0;
r.y = 0;
xcb_set_window_rect(conn, client->child, r);
/* Raise the window */
values[0] = XCB_STACK_MODE_ABOVE;
uint32_t values[] = { XCB_STACK_MODE_ABOVE };
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
Rect child_rect = workspace->rect;
child_rect.x = child_rect.y = 0;
fake_configure_notify(conn, child_rect, client->child);
fake_configure_notify(conn, r, client->child);
xcb_flush(conn);
}
@ -234,34 +275,26 @@ void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) {
/* dock clients cannot enter fullscreen mode */
assert(!client->dock);
Workspace *workspace = client->workspace;
if (!client->fullscreen) {
client_enter_fullscreen(conn, client, false);
} else {
client_leave_fullscreen(conn, client);
}
}
/*
* Like client_toggle_fullscreen(), but putting it in global fullscreen-mode.
*
*/
void client_toggle_fullscreen_global(xcb_connection_t *conn, Client *client) {
/* dock clients cannot enter fullscreen mode */
assert(!client->dock);
if (!client->fullscreen) {
client_enter_fullscreen(conn, client);
return;
}
LOG("leaving fullscreen mode\n");
client->fullscreen = false;
workspace->fullscreen_client = NULL;
if (client_is_floating(client)) {
/* For floating clients its enough if we just reconfigure that window (in fact,
* re-rendering the layout will not update the client.) */
reposition_client(conn, client);
resize_client(conn, client);
/* redecorate_window flushes */
redecorate_window(conn, client);
client_enter_fullscreen(conn, client, true);
} else {
client_set_below_floating(conn, client);
/* Because the coordinates of the window havent changed, it would not be
re-configured if we dont set the following flag */
client->force_reconfigure = true;
/* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */
render_layout(conn);
client_leave_fullscreen(conn, client);
}
xcb_flush(conn);
}
/*
@ -277,14 +310,14 @@ void client_set_below_floating(xcb_connection_t *conn, Client *client) {
if (first_floating == TAILQ_END(&(ws->floating_clients)))
return;
LOG("Setting below floating\n");
DLOG("Setting below floating\n");
uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
if (client->workspace->fullscreen_client == NULL)
return;
LOG("(and below fullscreen)\n");
DLOG("(and below fullscreen)\n");
/* Ensure that the window is still below the fullscreen window */
values[0] = client->workspace->fullscreen_client->frame;
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
@ -407,3 +440,38 @@ void client_mark(xcb_connection_t *conn, Client *client, const char *mark) {
break;
}
}
/*
* Returns the minimum height of a specific window. The height is calculated
* by using 2 pixels (for the client window itself), possibly padding this to
* comply with the clients base_height and then adding the decoration height.
*
*/
uint32_t client_min_height(Client *client) {
uint32_t height = max(2, client->base_height);
i3Font *font = load_font(global_conn, config.font);
if (client->titlebar_position == TITLEBAR_OFF && client->borderless)
return height;
if (client->titlebar_position == TITLEBAR_OFF && !client->borderless)
return height + 2;
return height + font->height + 2 + 2;
}
/*
* See client_min_height.
*
*/
uint32_t client_min_width(Client *client) {
uint32_t width = max(2, client->base_width);
if (client->titlebar_position == TITLEBAR_OFF && client->borderless)
return width;
if (client->titlebar_position == TITLEBAR_OFF && !client->borderless)
return width + 2;
return width + 2 + 2;
}

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -22,7 +22,7 @@
#include "table.h"
#include "layout.h"
#include "i3.h"
#include "xinerama.h"
#include "randr.h"
#include "client.h"
#include "floating.h"
#include "xcb.h"
@ -30,6 +30,10 @@
#include "workspace.h"
#include "commands.h"
#include "resize.h"
#include "log.h"
#include "sighandler.h"
#include "manage.h"
#include "ipc.h"
bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
/* If this container is empty, were done */
@ -45,7 +49,7 @@ bool focus_window_in_container(xcb_connection_t *conn, Container *container, dir
else if (direction == D_DOWN) {
if ((candidate = CIRCLEQ_NEXT_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL)
candidate = CIRCLEQ_FIRST(&(container->clients));
} else LOG("Direction not implemented!\n");
} else ELOG("Direction not implemented!\n");
/* If we could not switch, the container contains exactly one client. We return false */
if (candidate == container->currently_focused)
@ -69,16 +73,16 @@ static void jump_to_mark(xcb_connection_t *conn, const char *mark) {
if (current->mark == NULL || strcmp(current->mark, mark) != 0)
continue;
workspace_show(conn, current->workspace->num + 1);
set_focus(conn, current, true);
workspace_show(conn, current->workspace->num + 1);
return;
}
LOG("No window with this mark found\n");
ELOG("No window with this mark found\n");
}
static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) {
LOG("focusing direction %d\n", direction);
DLOG("focusing direction %d\n", direction);
int new_row = current_row,
new_col = current_col;
@ -86,19 +90,20 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
Workspace *t_ws = c_ws;
/* Makes sure new_col and new_row are within bounds of the new workspace */
void check_colrow_boundaries() {
if (new_col >= t_ws->cols)
new_col = (t_ws->cols - 1);
if (new_row >= t_ws->rows)
new_row = (t_ws->rows - 1);
}
#define CHECK_COLROW_BOUNDARIES \
do { \
if (new_col >= t_ws->cols) \
new_col = (t_ws->cols - 1); \
if (new_row >= t_ws->rows) \
new_row = (t_ws->rows - 1); \
} while (0)
/* There always is a container. If not, current_col or current_row is wrong */
assert(container != NULL);
if (container->workspace->fullscreen_client != NULL) {
LOG("You're in fullscreen mode. Won't switch focus\n");
return;
LOG("You're in fullscreen mode. Forcing focus to operate on whole screens\n");
thing = THING_SCREEN;
}
/* For focusing screens, situation is different: we get the rect
@ -106,7 +111,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
* right/left/bottom/top and just switch to the workspace on
* the target screen. */
if (thing == THING_SCREEN) {
i3Screen *cs = c_ws->screen;
Output *cs = c_ws->output;
assert(cs != NULL);
Rect bounds = cs->rect;
@ -118,20 +123,20 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
bounds.y -= bounds.height;
else bounds.y += bounds.height;
i3Screen *target = get_screen_containing(bounds.x, bounds.y);
Output *target = get_output_containing(bounds.x, bounds.y);
if (target == NULL) {
LOG("Target screen NULL\n");
DLOG("Target output NULL\n");
/* Wrap around if the target screen is out of bounds */
if (direction == D_RIGHT)
target = get_screen_most(D_LEFT, cs);
target = get_output_most(D_LEFT, cs);
else if (direction == D_LEFT)
target = get_screen_most(D_RIGHT, cs);
target = get_output_most(D_RIGHT, cs);
else if (direction == D_UP)
target = get_screen_most(D_DOWN, cs);
else target = get_screen_most(D_UP, cs);
target = get_output_most(D_DOWN, cs);
else target = get_output_most(D_UP, cs);
}
LOG("Switching to ws %d\n", target->current_workspace + 1);
DLOG("Switching to ws %d\n", target->current_workspace + 1);
workspace_show(conn, target->current_workspace->num + 1);
return;
}
@ -159,31 +164,48 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
}
} else {
/* Lets see if there is a screen down/up there to which we can switch */
LOG("container is at %d with height %d\n", container->y, container->height);
i3Screen *screen;
DLOG("container is at %d with height %d\n", container->y, container->height);
Output *output;
int destination_y = (direction == D_UP ? (container->y - 1) : (container->y + container->height + 1));
if ((screen = get_screen_containing(container->x, destination_y)) == NULL) {
LOG("Wrapping screen around vertically\n");
if ((output = get_output_containing(container->x, destination_y)) == NULL) {
DLOG("Wrapping screen around vertically\n");
/* No screen found? Then wrap */
screen = get_screen_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->screen);
output = get_output_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->output);
}
t_ws = screen->current_workspace;
t_ws = output->current_workspace;
new_row = (direction == D_UP ? (t_ws->rows - 1) : 0);
}
check_colrow_boundaries();
CHECK_COLROW_BOUNDARIES;
LOG("new_col = %d, new_row = %d\n", new_col, new_row);
DLOG("new_col = %d, new_row = %d\n", new_col, new_row);
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
LOG("Cell empty, checking for colspanned client above...\n");
DLOG("Cell empty, checking for colspanned client above...\n");
for (int cols = 0; cols < new_col; cols += t_ws->table[cols][new_row]->colspan) {
if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1)))
continue;
new_col = cols;
DLOG("Fixed it to new col %d\n", new_col);
break;
}
}
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
DLOG("Cell still empty, checking for full cols above spanned width...\n");
DLOG("new_col = %d\n", new_col);
DLOG("colspan = %d\n", container->colspan);
for (int cols = new_col;
cols < container->col + container->colspan;
cols += t_ws->table[cols][new_row]->colspan) {
DLOG("candidate: new_row = %d, cols = %d\n", new_row, cols);
if (t_ws->table[cols][new_row]->currently_focused == NULL)
continue;
new_col = cols;
DLOG("Fixed it to new col %d\n", new_col);
break;
}
LOG("Fixed it to new col %d\n", new_col);
}
} else if (direction == D_LEFT || direction == D_RIGHT) {
if (direction == D_RIGHT && cell_exists(t_ws, current_col+1, current_row))
@ -202,37 +224,55 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
}
} else {
/* Lets see if there is a screen left/right here to which we can switch */
LOG("container is at %d with width %d\n", container->x, container->width);
i3Screen *screen;
DLOG("container is at %d with width %d\n", container->x, container->width);
Output *output;
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), container->workspace->screen);
if ((output = get_output_containing(destination_x, container->y)) == NULL) {
DLOG("Wrapping screen around horizontally\n");
output = get_output_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->output);
}
t_ws = screen->current_workspace;
t_ws = output->current_workspace;
new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0);
}
check_colrow_boundaries();
CHECK_COLROW_BOUNDARIES;
LOG("new_col = %d, new_row = %d\n", new_col, new_row);
DLOG("new_col = %d, new_row = %d\n", new_col, new_row);
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
LOG("Cell empty, checking for rowspanned client above...\n");
DLOG("Cell empty, checking for rowspanned client above...\n");
for (int rows = 0; rows < new_row; rows += t_ws->table[new_col][rows]->rowspan) {
if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1)))
continue;
new_row = rows;
DLOG("Fixed it to new row %d\n", new_row);
break;
}
LOG("Fixed it to new row %d\n", new_row);
}
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
DLOG("Cell still empty, checking for full cols near full spanned height...\n");
DLOG("new_row = %d\n", new_row);
DLOG("rowspan = %d\n", container->rowspan);
for (int rows = new_row;
rows < container->row + container->rowspan;
rows += t_ws->table[new_col][rows]->rowspan) {
DLOG("candidate: new_col = %d, rows = %d\n", new_col, rows);
if (t_ws->table[new_col][rows]->currently_focused == NULL)
continue;
new_row = rows;
DLOG("Fixed it to new col %d\n", new_row);
break;
}
}
} else {
LOG("direction unhandled\n");
ELOG("direction unhandled\n");
return;
}
check_colrow_boundaries();
CHECK_COLROW_BOUNDARIES;
if (t_ws->table[new_col][new_row]->currently_focused != NULL)
set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true);
@ -254,7 +294,7 @@ static bool move_current_window_in_container(xcb_connection_t *conn, Client *cli
if (other == CIRCLEQ_END(&(client->container->clients)))
return false;
LOG("i can do that\n");
DLOG("i can do that\n");
/* We can move the client inside its current container */
CIRCLEQ_REMOVE(&(client->container->clients), client, clients);
if (direction == D_UP)
@ -357,7 +397,7 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
/* Fix colspan/rowspan if itd overlap */
fix_colrowspan(conn, workspace);
render_workspace(conn, workspace->screen, workspace);
render_workspace(conn, workspace->output, workspace);
xcb_flush(conn);
set_focus(conn, current_client, true);
@ -411,7 +451,7 @@ static void move_current_container(xcb_connection_t *conn, direction_t direction
return;
}
LOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row);
DLOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row);
/* Swap the containers */
int col = new->col;
@ -453,7 +493,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
/* Snap to the left is actually a move to the left and then a snap right */
if (!cell_exists(container->workspace, container->col - 1, container->row) ||
CUR_TABLE[container->col-1][container->row]->currently_focused != NULL) {
LOG("cannot snap to left - the cell is already used\n");
ELOG("cannot snap to left - the cell is already used\n");
return;
}
@ -466,18 +506,18 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
for (int i = 0; i < container->rowspan; i++)
if (!cell_exists(container->workspace, new_col, container->row + i) ||
CUR_TABLE[new_col][container->row + i]->currently_focused != NULL) {
LOG("cannot snap to right - the cell is already used\n");
ELOG("cannot snap to right - the cell is already used\n");
return;
}
/* Check if there are other cells with rowspan, which are in our way.
* If so, reduce their rowspan. */
for (int i = container->row-1; i >= 0; i--) {
LOG("we got cell %d, %d with rowspan %d\n",
DLOG("we got cell %d, %d with rowspan %d\n",
new_col, i, CUR_TABLE[new_col][i]->rowspan);
while ((CUR_TABLE[new_col][i]->rowspan-1) >= (container->row - i))
CUR_TABLE[new_col][i]->rowspan--;
LOG("new rowspan = %d\n", CUR_TABLE[new_col][i]->rowspan);
DLOG("new rowspan = %d\n", CUR_TABLE[new_col][i]->rowspan);
}
container->colspan++;
@ -486,7 +526,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
case D_UP:
if (!cell_exists(container->workspace, container->col, container->row - 1) ||
CUR_TABLE[container->col][container->row-1]->currently_focused != NULL) {
LOG("cannot snap to top - the cell is already used\n");
ELOG("cannot snap to top - the cell is already used\n");
return;
}
@ -494,21 +534,21 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
snap_current_container(conn, D_DOWN);
return;
case D_DOWN: {
LOG("snapping down\n");
DLOG("snapping down\n");
int new_row = container->row + container->rowspan;
for (int i = 0; i < container->colspan; i++)
if (!cell_exists(container->workspace, container->col + i, new_row) ||
CUR_TABLE[container->col + i][new_row]->currently_focused != NULL) {
LOG("cannot snap down - the cell is already used\n");
ELOG("cannot snap down - the cell is already used\n");
return;
}
for (int i = container->col-1; i >= 0; i--) {
LOG("we got cell %d, %d with colspan %d\n",
DLOG("we got cell %d, %d with colspan %d\n",
i, new_row, CUR_TABLE[i][new_row]->colspan);
while ((CUR_TABLE[i][new_row]->colspan-1) >= (container->col - i))
CUR_TABLE[i][new_row]->colspan--;
LOG("new colspan = %d\n", CUR_TABLE[i][new_row]->colspan);
DLOG("new colspan = %d\n", CUR_TABLE[i][new_row]->colspan);
}
@ -530,12 +570,12 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl
LOG("moving floating\n");
workspace_initialize(t_ws, c_ws->screen);
workspace_initialize(t_ws, c_ws->output, false);
/* Check if there is already a fullscreen client on the destination workspace and
* stop moving if so. */
if (client->fullscreen && (t_ws->fullscreen_client != NULL)) {
LOG("Not moving: Fullscreen client already existing on destination workspace.\n");
ELOG("Not moving: Fullscreen client already existing on destination workspace.\n");
return;
}
@ -543,21 +583,26 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl
/* If were moving it to an invisible screen, we need to unmap it */
if (!workspace_is_visible(t_ws)) {
LOG("This workspace is not visible, unmapping\n");
DLOG("This workspace is not visible, unmapping\n");
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
* coordinates. */
LOG("before x = %d, y = %d\n", client->rect.x, client->rect.y);
DLOG("before x = %d, y = %d\n", client->rect.x, client->rect.y);
uint32_t relative_x = client->rect.x - old_ws->rect.x,
relative_y = client->rect.y - old_ws->rect.y;
LOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y);
client->rect.x = t_ws->rect.x + relative_x;
client->rect.y = t_ws->rect.y + relative_y;
LOG("after x = %d, y = %d\n", client->rect.x, client->rect.y);
reposition_client(conn, client);
xcb_flush(conn);
DLOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y);
if (client->fullscreen) {
client_enter_fullscreen(conn, client, false);
memcpy(&(client->rect), &(t_ws->rect), sizeof(Rect));
} else {
client->rect.x = t_ws->rect.x + relative_x;
client->rect.y = t_ws->rect.y + relative_y;
DLOG("after x = %d, y = %d\n", client->rect.x, client->rect.y);
reposition_client(conn, client);
xcb_flush(conn);
}
}
/* Configure the window above all tiling windows (or below a fullscreen
@ -576,12 +621,14 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl
}
}
LOG("done\n");
DLOG("done\n");
render_layout(conn);
if (workspace_is_visible(t_ws))
if (workspace_is_visible(t_ws)) {
client_warp_pointer_into(conn, client);
set_focus(conn, client, true);
}
}
/*
@ -600,18 +647,18 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
Client *current_client = container->currently_focused;
if (current_client == NULL) {
LOG("No currently focused client in current container.\n");
ELOG("No currently focused client in current container.\n");
return;
}
Client *to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients);
if (to_focus == NULL)
to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients);
workspace_initialize(t_ws, container->workspace->screen);
workspace_initialize(t_ws, container->workspace->output, false);
/* Check if there is already a fullscreen client on the destination workspace and
* stop moving if so. */
if (current_client->fullscreen && (t_ws->fullscreen_client != NULL)) {
LOG("Not moving: Fullscreen client already existing on destination workspace.\n");
ELOG("Not moving: Fullscreen client already existing on destination workspace.\n");
return;
}
@ -627,7 +674,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients);
SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients);
LOG("Moved.\n");
DLOG("Moved.\n");
current_client->container = to_container;
current_client->workspace = to_container->workspace;
@ -636,12 +683,12 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
/* If were moving it to an invisible screen, we need to unmap it */
if (!workspace_is_visible(to_container->workspace)) {
LOG("This workspace is not visible, unmapping\n");
DLOG("This workspace is not visible, unmapping\n");
client_unmap(conn, current_client);
} else {
if (current_client->fullscreen) {
LOG("Calling client_enter_fullscreen again\n");
client_enter_fullscreen(conn, current_client);
DLOG("Calling client_enter_fullscreen again\n");
client_enter_fullscreen(conn, current_client, false);
}
}
@ -650,8 +697,10 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
render_layout(conn);
if (workspace_is_visible(to_container->workspace))
if (workspace_is_visible(to_container->workspace)) {
client_warp_pointer_into(conn, current_client);
set_focus(conn, current_client, true);
}
}
/*
@ -672,11 +721,12 @@ static void jump_to_window(xcb_connection_t *conn, const char *arguments) {
if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) {
free(classtitle);
LOG("No matching client found.\n");
ELOG("No matching client found.\n");
return;
}
free(classtitle);
workspace_show(conn, client->workspace->num + 1);
set_focus(conn, client, true);
}
@ -694,7 +744,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
/* No match? Either no arguments were specified, or no numbers */
if (result < 1) {
LOG("At least one valid argument required\n");
ELOG("At least one valid argument required\n");
return;
}
@ -704,7 +754,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
if (result < 3)
return;
LOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows);
DLOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows);
/* Move to row/col */
if (row >= c_ws->rows)
@ -712,7 +762,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
if (col >= c_ws->cols)
col = c_ws->cols - 1;
LOG("Jumping to col %d, row %d\n", col, row);
DLOG("Jumping to col %d, row %d\n", col, row);
if (c_ws->table[col][row]->currently_focused != NULL)
set_focus(conn, c_ws->table[col][row]->currently_focused, true);
}
@ -741,7 +791,7 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
} else if (strcasecmp(arguments, "ft") == 0) {
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused == SLIST_END(&(c_ws->focus_stack))) {
LOG("Cannot select the next floating/tiling client because there is no client at all\n");
ELOG("Cannot select the next floating/tiling client because there is no client at all\n");
return;
}
@ -749,17 +799,17 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
} else {
/* …or a number was specified */
if (sscanf(arguments, "%u", &times) != 1) {
LOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments);
ELOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments);
times = 1;
}
SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) {
if (++count < times) {
LOG("Skipping\n");
DLOG("Skipping\n");
continue;
}
LOG("Focussing\n");
DLOG("Focussing\n");
set_focus(conn, current, true);
break;
}
@ -774,29 +824,6 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
}
}
/*
* Goes through the list of arguments (for exec()) and checks if the given argument
* is present. If not, it copies the arguments (because we cannot realloc it) and
* appends the given argument.
*
*/
static char **append_argument(char **original, char *argument) {
int num_args;
for (num_args = 0; original[num_args] != NULL; num_args++) {
LOG("original argument: \"%s\"\n", original[num_args]);
/* If the argument is already present we return the original pointer */
if (strcmp(original[num_args], argument) == 0)
return original;
}
/* Copy the original array */
char **result = smalloc((num_args+2) * sizeof(char*));
memcpy(result, original, num_args * sizeof(char*));
result[num_args] = argument;
result[num_args+1] = NULL;
return result;
}
/*
* Switch to next or previous existing workspace
*
@ -805,16 +832,32 @@ static void next_previous_workspace(xcb_connection_t *conn, int direction) {
Workspace *ws = c_ws;
if (direction == 'n') {
while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) {
if (ws->screen == NULL)
while (1) {
ws = TAILQ_NEXT(ws, workspaces);
if (ws == TAILQ_END(workspaces))
ws = TAILQ_FIRST(workspaces);
if (ws == c_ws)
return;
if (ws->output == NULL)
continue;
workspace_show(conn, ws->num + 1);
return;
}
} else if (direction == 'p') {
while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) {
if (ws->screen == NULL)
while (1) {
ws = TAILQ_PREV(ws, workspaces_head, workspaces);
if (ws == TAILQ_END(workspaces))
ws = TAILQ_LAST(workspaces, workspaces_head);
if (ws == c_ws)
return;
if (ws->output == NULL)
continue;
workspace_show(conn, ws->num + 1);
@ -827,7 +870,34 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c
int first, second;
resize_orientation_t orientation = O_VERTICAL;
Container *con = last_focused->container;
Workspace *ws = con->workspace;
Workspace *ws = last_focused->workspace;
if (client_is_floating(last_focused)) {
DLOG("Resizing a floating client\n");
if (STARTS_WITH(command, "left")) {
command += strlen("left");
last_focused->rect.width += atoi(command);
last_focused->rect.x -= atoi(command);
} else if (STARTS_WITH(command, "right")) {
command += strlen("right");
last_focused->rect.width += atoi(command);
} else if (STARTS_WITH(command, "top")) {
command += strlen("top");
last_focused->rect.height += atoi(command);
last_focused->rect.y -= atoi(command);
} else if (STARTS_WITH(command, "bottom")) {
command += strlen("bottom");
last_focused->rect.height += atoi(command);
} else {
ELOG("Syntax: resize <left|right|top|bottom> [+|-]<pixels>\n");
return;
}
/* resize_client flushes */
resize_client(conn, last_focused);
return;
}
if (STARTS_WITH(command, "left")) {
if (con->col == 0)
@ -837,7 +907,7 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c
command += strlen("left");
} else if (STARTS_WITH(command, "right")) {
first = con->col + (con->colspan - 1);
LOG("column %d\n", first);
DLOG("column %d\n", first);
if (!cell_exists(ws, first, con->row) ||
(first == (ws->cols-1)))
@ -862,7 +932,7 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c
orientation = O_HORIZONTAL;
command += strlen("bottom");
} else {
LOG("Syntax: resize <left|right|top|bottom> [+|-]<pixels>\n");
ELOG("Syntax: resize <left|right|top|bottom> [+|-]<pixels>\n");
return;
}
@ -898,14 +968,14 @@ void parse_command(xcb_connection_t *conn, const char *command) {
if (STARTS_WITH(command, "mark")) {
if (last_focused == NULL) {
LOG("There is no window to mark\n");
ELOG("There is no window to mark\n");
return;
}
const char *rest = command + strlen("mark");
while (*rest == ' ')
rest++;
if (*rest == '\0') {
LOG("interactive mark starting\n");
DLOG("interactive mark starting\n");
start_application("i3-input -p 'mark ' -l 1 -P 'Mark: '");
} else {
LOG("mark with \"%s\"\n", rest);
@ -919,7 +989,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
while (*rest == ' ')
rest++;
if (*rest == '\0') {
LOG("interactive go to mark starting\n");
DLOG("interactive go to mark starting\n");
start_application("i3-input -p 'goto ' -l 1 -P 'Goto: '");
} else {
LOG("go to \"%s\"\n", rest);
@ -930,7 +1000,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
if (STARTS_WITH(command, "stack-limit ")) {
if (last_focused == NULL || client_is_floating(last_focused)) {
LOG("No container focused\n");
ELOG("No container focused\n");
return;
}
const char *rest = command + strlen("stack-limit ");
@ -941,7 +1011,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
last_focused->container->stack_limit = STACK_LIMIT_COLS;
rest += strlen("cols ");
} else {
LOG("Syntax: stack-limit <cols|rows> <limit>\n");
ELOG("Syntax: stack-limit <cols|rows> <limit>\n");
return;
}
@ -969,28 +1039,28 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* Is it an <exit>? */
if (STARTS_WITH(command, "exit")) {
LOG("User issued exit-command, exiting without error.\n");
restore_geometry(global_conn);
ipc_shutdown();
exit(EXIT_SUCCESS);
}
/* Is it a <reload>? */
if (STARTS_WITH(command, "reload")) {
load_configuration(conn, NULL, true);
render_layout(conn);
/* Send an IPC event just in case the ws names have changed */
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}");
return;
}
/* Is it <restart>? Then restart in place. */
if (STARTS_WITH(command, "restart")) {
LOG("restarting \"%s\"...\n", start_argv[0]);
/* make sure -a is in the argument list or append it */
start_argv = append_argument(start_argv, "-a");
execvp(start_argv[0], start_argv);
/* not reached */
i3_restart();
}
if (STARTS_WITH(command, "kill")) {
if (last_focused == NULL) {
LOG("There is no window to kill\n");
ELOG("There is no window to kill\n");
return;
}
@ -1015,25 +1085,28 @@ void parse_command(xcb_connection_t *conn, const char *command) {
return;
}
/* Is it 'f' for fullscreen? */
/* Is it 'f' for fullscreen, or 'fg' for fullscreen_global? */
if (command[0] == 'f') {
if (last_focused == NULL)
return;
client_toggle_fullscreen(conn, last_focused);
if (command[1] == 'g')
client_toggle_fullscreen_global(conn, last_focused);
else
client_toggle_fullscreen(conn, last_focused);
return;
}
/* Is it just 's' for stacking or 'd' for default? */
if ((command[0] == 's' || command[0] == 'd' || command[0] == 'T') && (command[1] == '\0')) {
if (last_focused != NULL && client_is_floating(last_focused)) {
LOG("not switching, this is a floating client\n");
ELOG("not switching, this is a floating client\n");
return;
}
LOG("Switching mode for current container\n");
int new_mode = MODE_DEFAULT;
if (command[0] == 's')
if (command[0] == 's' && CUR_CELL->mode != MODE_STACK)
new_mode = MODE_STACK;
if (command[0] == 'T')
if (command[0] == 'T' && CUR_CELL->mode != MODE_TABBED)
new_mode = MODE_TABBED;
switch_layout_mode(conn, CUR_CELL, new_mode);
return;
@ -1043,7 +1116,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* or even 'bt' (toggle border: 'bp' -> 'bb' -> 'bn' ) */
if (command[0] == 'b') {
if (last_focused == NULL) {
LOG("No window focused, cannot change border type\n");
ELOG("No window focused, cannot change border type\n");
return;
}
@ -1084,7 +1157,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
with = WITH_SCREEN;
command++;
} else {
LOG("not yet implemented.\n");
ELOG("not yet implemented.\n");
return;
}
}
@ -1097,7 +1170,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
return;
}
if (last_focused == NULL) {
LOG("Cannot toggle tiling/floating: workspace empty\n");
ELOG("Cannot toggle tiling/floating: workspace empty\n");
return;
}
@ -1113,7 +1186,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* Fix colspan/rowspan if itd overlap */
fix_colrowspan(conn, ws);
render_workspace(conn, ws->screen, ws);
render_workspace(conn, ws->output, ws);
/* Re-focus the client because cleanup_table sets the focus to the last
* focused client inside a container only. */
@ -1134,7 +1207,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
direction_t direction;
int times = strtol(command, &rest, 10);
if (rest == NULL) {
LOG("Invalid command (\"%s\")\n", command);
ELOG("Invalid command (\"%s\")\n", command);
return;
}
@ -1152,7 +1225,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
int workspace = strtol(rest, &rest, 10);
if (rest == NULL) {
LOG("Invalid command (\"%s\")\n", command);
ELOG("Invalid command (\"%s\")\n", command);
return;
}
@ -1164,13 +1237,13 @@ void parse_command(xcb_connection_t *conn, const char *command) {
}
if (last_focused == NULL) {
LOG("Not performing (no window found)\n");
ELOG("Not performing (no window found)\n");
return;
}
if (client_is_floating(last_focused) &&
(action != ACTION_FOCUS && action != ACTION_MOVE)) {
LOG("Not performing (floating)\n");
ELOG("Not performing (floating)\n");
return;
}
@ -1185,7 +1258,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
else if (*rest == 'l')
direction = D_RIGHT;
else {
LOG("unknown direction: %c\n", *rest);
ELOG("unknown direction: %c\n", *rest);
return;
}
rest++;
@ -1208,7 +1281,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* TODO: this should swap the screens contents
* (e.g. all workspaces) with the next/previous/
* screen */
LOG("Not yet implemented\n");
ELOG("Not yet implemented\n");
continue;
}
if (client_is_floating(last_focused)) {
@ -1223,7 +1296,7 @@ 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");
ELOG("You cannot snap a screen (it makes no sense).\n");
continue;
}
snap_current_container(conn, direction);

View File

@ -3,16 +3,23 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* src/config.c: Contains all functions handling the configuration file (calling
* the parser (src/cfgparse.y) with the correct path, switching key bindings
* mode).
*
*/
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <glob.h>
#include <wordexp.h>
#include <unistd.h>
/* We need Xlib for XStringToKeysym */
#include <X11/Xlib.h>
@ -25,48 +32,44 @@
#include "xcb.h"
#include "table.h"
#include "workspace.h"
/* prototype for src/cfgparse.y, will be cleaned up as soon as we completely
* switched to the new scanner/parser. */
void parse_file(const char *f);
#include "log.h"
Config config;
struct modes_head modes;
bool config_use_lexer = false;
/*
* This function resolves ~ in pathnames.
*
*/
static char *glob_path(const char *path) {
char *glob_path(const char *path) {
static glob_t globbuf;
if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
die("glob() failed");
char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
globfree(&globbuf);
/* If the file does not exist yet, we still may need to resolve tilde,
* so call wordexp */
if (strcmp(result, path) == 0) {
wordexp_t we;
wordexp(path, &we, WRDE_NOCMD);
if (we.we_wordc > 0) {
free(result);
result = sstrdup(we.we_wordv[0]);
}
wordfree(&we);
}
return result;
}
/*
* This function does a very simple replacement of each instance of key with value.
* Checks if the given path exists by calling stat().
*
*/
static void replace_variable(char *buffer, const char *key, const char *value) {
char *pos;
/* 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;
while ((pos = strcasestr(buffer, key)) != NULL && c++ < 100) {
char *rest = pos + strlen(key);
*pos = '\0';
char *replaced;
asprintf(&replaced, "%s%s%s", buffer, value, rest);
/* Hm, this is a bit ugly, but sizeof(buffer) = 4, as its just a pointer.
* So we need to hard-code the dimensions here. */
strncpy(buffer, replaced, 1026);
free(replaced);
}
bool path_exists(const char *path) {
struct stat buf;
return (stat(path, &buf) == 0);
}
/**
@ -75,57 +78,88 @@ static void replace_variable(char *buffer, const char *key, const char *value) {
*
*/
void ungrab_all_keys(xcb_connection_t *conn) {
LOG("Ungrabbing all keys\n");
DLOG("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);
DLOG("Grabbing %d\n", keycode);
/* Grab the key in all combinations */
#define GRAB_KEY(modifier) \
do { \
xcb_grab_key(conn, 0, root, modifier, keycode, \
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \
} while (0)
int mods = bind->mods;
if ((bind->mods & BIND_MODE_SWITCH) != 0) {
mods &= ~BIND_MODE_SWITCH;
if (mods == 0)
mods = XCB_MOD_MASK_ANY;
}
GRAB_KEY(mods);
GRAB_KEY(mods | xcb_numlock_mask);
GRAB_KEY(mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
}
/*
* Grab the bound keys (tell X to send us keypress events for those keycodes)
* Returns a pointer to the Binding with the specified modifiers and keycode
* or NULL if no such binding exists.
*
*/
void grab_all_keys(xcb_connection_t *conn) {
Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode) {
Binding *bind;
TAILQ_FOREACH(bind, bindings, bindings) {
/* First compare the modifiers */
if (bind->mods != modifiers)
continue;
/* If a symbol was specified by the user, we need to look in
* the array of translated keycodes for the events keycode */
if (bind->symbol != NULL) {
if (memmem(bind->translated_to,
bind->number_keycodes * sizeof(xcb_keycode_t),
&keycode, sizeof(xcb_keycode_t)) != NULL)
break;
} else {
/* This case is easier: The user specified a keycode */
if (bind->keycode == keycode)
break;
}
}
return (bind == TAILQ_END(bindings) ? NULL : bind);
}
/*
* Translates keysymbols to keycodes for all bindings which use keysyms.
*
*/
void translate_keysyms() {
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);
if (bind->keycode > 0)
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);
ELOG("Could not translate string to key symbol: \"%s\"\n", bind->symbol);
continue;
}
#ifdef OLD_XCB_KEYSYMS_API
bind->number_keycodes = 1;
xcb_keycode_t code = xcb_key_symbols_get_keycode(keysyms, keysym);
LOG("Translated symbol \"%s\" to 1 keycode (%d)\n", bind->symbol, code);
grab_keycode_for_binding(conn, bind, code);
DLOG("Translated symbol \"%s\" to 1 keycode (%d)\n", bind->symbol, code);
grab_keycode_for_binding(global_conn, bind, code);
bind->translated_to = smalloc(sizeof(xcb_keycode_t));
memcpy(bind->translated_to, &code, sizeof(xcb_keycode_t));
#else
uint32_t last_keycode = 0;
xcb_keycode_t *keycodes = xcb_key_symbols_get_keycode(keysyms, keysym);
if (keycodes == NULL) {
LOG("Could not translate symbol \"%s\"\n", bind->symbol);
DLOG("Could not translate symbol \"%s\"\n", bind->symbol);
continue;
}
@ -136,11 +170,10 @@ void grab_all_keys(xcb_connection_t *conn) {
* 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);
DLOG("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);
@ -148,6 +181,29 @@ void grab_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, bool bind_mode_switch) {
Binding *bind;
TAILQ_FOREACH(bind, bindings, bindings) {
if ((bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) == 0) ||
(!bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) != 0))
continue;
/* The easy case: the user specified a keycode directly. */
if (bind->keycode > 0) {
grab_keycode_for_binding(conn, bind, bind->keycode);
continue;
}
xcb_keycode_t *walk = bind->translated_to;
for (int i = 0; i < bind->number_keycodes; i++)
grab_keycode_for_binding(conn, bind, *walk);
}
}
/*
* Switches the key bindings to the given mode, if the mode exists
*
@ -163,18 +219,89 @@ void switch_mode(xcb_connection_t *conn, const char *new_mode) {
ungrab_all_keys(conn);
bindings = mode->bindings;
grab_all_keys(conn);
translate_keysyms();
grab_all_keys(conn, false);
return;
}
LOG("ERROR: Mode not found\n");
ELOG("ERROR: Mode not found\n");
}
/*
* Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
* Get the path of the first configuration file found. Checks the XDG folders
* first ($XDG_CONFIG_HOME, $XDG_CONFIG_DIRS), then the traditional paths.
*
* If you specify override_configpath, only this path is used to look for a
* configuration file.
*/
static char *get_config_path() {
/* 1: check for $XDG_CONFIG_HOME/i3/config */
char *xdg_config_home, *xdg_config_dirs, *config_path;
if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
xdg_config_home = "~/.config";
xdg_config_home = glob_path(xdg_config_home);
if (asprintf(&config_path, "%s/i3/config", xdg_config_home) == -1)
die("asprintf() failed");
free(xdg_config_home);
if (path_exists(config_path))
return config_path;
free(config_path);
/* 2: check for $XDG_CONFIG_DIRS/i3/config */
if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL)
xdg_config_dirs = "/etc/xdg";
char *buf = strdup(xdg_config_dirs);
char *tok = strtok(buf, ":");
while (tok != NULL) {
tok = glob_path(tok);
if (asprintf(&config_path, "%s/i3/config", tok) == -1)
die("asprintf() failed");
free(tok);
if (path_exists(config_path)) {
free(buf);
return config_path;
}
free(config_path);
tok = strtok(NULL, ":");
}
free(buf);
/* 3: check traditional paths */
config_path = glob_path("~/.i3/config");
if (path_exists(config_path))
return config_path;
config_path = strdup("/etc/i3/config");
if (!path_exists(config_path))
die("Neither $XDG_CONFIG_HOME/i3/config, nor "
"$XDG_CONFIG_DIRS/i3/config, nor ~/.i3/config nor "
"/etc/i3/config exist.");
return config_path;
}
/*
* Finds the configuration file to use (either the one specified by
* override_configpath), the users one or the system default) and calls
* parse_file().
*
*/
static void parse_configuration(const char *override_configpath) {
if (override_configpath != NULL) {
parse_file(override_configpath);
return;
}
char *path = get_config_path();
DLOG("Parsing configfile %s\n", path);
parse_file(path);
free(path);
}
/*
* (Re-)loads the configuration file (sets useful defaults before).
*
*/
void load_configuration(xcb_connection_t *conn, const char *override_configpath, bool reload) {
@ -208,6 +335,11 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
TAILQ_REMOVE(&assignments, assign, assignments);
FREE(assign);
}
/* Clear workspace names */
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces)
workspace_set_name(ws, NULL);
}
SLIST_INIT(&modes);
@ -220,388 +352,36 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
bindings = default_mode->bindings;
SLIST_HEAD(variables_head, Variable) variables;
#define OPTION_STRING(name) \
if (strcasecmp(key, #name) == 0) { \
config.name = sstrdup(value); \
continue; \
}
#define REQUIRED_OPTION(name) \
if (config.name == NULL) \
die("You did not specify required configuration option " #name "\n");
#define OPTION_COLORTRIPLE(opt, name) \
if (strcasecmp(key, opt) == 0) { \
char border[8], background[8], text[8]; \
memset(border, 0, sizeof(border)); \
memset(background, 0, sizeof(background)); \
memset(text, 0, sizeof(text)); \
border[0] = background[0] = text[0] = '#'; \
if (sscanf(value, "#%06[0-9a-fA-F] #%06[0-9a-fA-F] #%06[0-9a-fA-F]", \
border + 1, background + 1, text + 1) != 3 || \
strlen(border) != 7 || \
strlen(background) != 7 || \
strlen(text) != 7) \
die("invalid color code line: %s\n", value); \
config.name.border = get_colorpixel(conn, border); \
config.name.background = get_colorpixel(conn, background); \
config.name.text = get_colorpixel(conn, text); \
continue; \
}
/* Clear the old config or initialize the data structure */
memset(&config, 0, sizeof(config));
SLIST_INIT(&variables);
/* Initialize default colors */
config.client.focused.border = get_colorpixel(conn, "#4c7899");
config.client.focused.background = get_colorpixel(conn, "#285577");
config.client.focused.text = get_colorpixel(conn, "#ffffff");
#define INIT_COLOR(x, cborder, cbackground, ctext) \
do { \
x.border = get_colorpixel(conn, cborder); \
x.background = get_colorpixel(conn, cbackground); \
x.text = get_colorpixel(conn, ctext); \
} while (0)
config.client.focused_inactive.border = get_colorpixel(conn, "#333333");
config.client.focused_inactive.background = get_colorpixel(conn, "#5f676a");
config.client.focused_inactive.text = get_colorpixel(conn, "#ffffff");
INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff");
INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff");
INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888");
INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff");
INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff");
INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888");
INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff");
config.client.unfocused.border = get_colorpixel(conn, "#333333");
config.client.unfocused.background = get_colorpixel(conn, "#222222");
config.client.unfocused.text = get_colorpixel(conn, "#888888");
parse_configuration(override_configpath);
config.client.urgent.border = get_colorpixel(conn, "#2f343a");
config.client.urgent.background = get_colorpixel(conn, "#900000");
config.client.urgent.text = get_colorpixel(conn, "#ffffff");
config.bar.focused.border = get_colorpixel(conn, "#4c7899");
config.bar.focused.background = get_colorpixel(conn, "#285577");
config.bar.focused.text = get_colorpixel(conn, "#ffffff");
config.bar.unfocused.border = get_colorpixel(conn, "#333333");
config.bar.unfocused.background = get_colorpixel(conn, "#222222");
config.bar.unfocused.text = get_colorpixel(conn, "#888888");
config.bar.urgent.border = get_colorpixel(conn, "#2f343a");
config.bar.urgent.background = get_colorpixel(conn, "#900000");
config.bar.urgent.text = get_colorpixel(conn, "#ffffff");
if (config_use_lexer) {
/* Yes, this will be cleaned up soon. */
if (override_configpath != NULL) {
parse_file(override_configpath);
} else {
FILE *handle;
char *globbed = glob_path("~/.i3/config");
if ((handle = fopen(globbed, "r")) == NULL) {
if ((handle = fopen("/etc/i3/config", "r")) == NULL) {
die("Neither \"%s\" nor /etc/i3/config could be opened\n", globbed);
} else {
parse_file("/etc/i3/config");
}
} else {
parse_file(globbed);
}
}
if (reload)
grab_all_keys(conn);
} else {
FILE *handle;
if (override_configpath != NULL) {
if ((handle = fopen(override_configpath, "r")) == NULL)
die("Could not open configfile \"%s\".\n", override_configpath);
} else {
/* We first check for ~/.i3/config, then for /etc/i3/config */
char *globbed = glob_path("~/.i3/config");
if ((handle = fopen(globbed, "r")) == NULL)
if ((handle = fopen("/etc/i3/config", "r")) == NULL)
die("Neither \"%s\" nor /etc/i3/config could be opened\n", globbed);
free(globbed);
}
char key[512], value[512], buffer[1026];
while (!feof(handle)) {
if (fgets(buffer, 1024, handle) == NULL) {
/* fgets returns NULL on EOF and on error, so see which one it is. */
if (feof(handle))
break;
die("Could not read configuration file\n");
}
if (config.terminal != NULL)
replace_variable(buffer, "$terminal", config.terminal);
/* Replace all custom variables */
struct Variable *current;
SLIST_FOREACH(current, &variables, variables)
replace_variable(buffer, current->key, current->value);
/* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
key[0] == '#' || strlen(key) < 3)
continue;
OPTION_STRING(terminal);
OPTION_STRING(font);
/* Colors */
OPTION_COLORTRIPLE("client.focused", client.focused);
OPTION_COLORTRIPLE("client.focused_inactive", client.focused_inactive);
OPTION_COLORTRIPLE("client.unfocused", client.unfocused);
OPTION_COLORTRIPLE("client.urgent", client.urgent);
OPTION_COLORTRIPLE("bar.focused", bar.focused);
OPTION_COLORTRIPLE("bar.unfocused", bar.unfocused);
OPTION_COLORTRIPLE("bar.urgent", bar.urgent);
/* exec-lines (autostart) */
if (strcasecmp(key, "exec") == 0) {
struct Autostart *new = smalloc(sizeof(struct Autostart));
new->command = sstrdup(value);
TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
continue;
}
/* key bindings */
if (strcasecmp(key, "bind") == 0 || strcasecmp(key, "bindsym") == 0) {
#define CHECK_MODIFIER(name) \
if (strncasecmp(walk, #name, strlen(#name)) == 0) { \
modifiers |= BIND_##name; \
walk += strlen(#name) + 1; \
continue; \
}
char *walk = value, *rest;
uint32_t modifiers = 0;
while (*walk != '\0') {
/* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */
CHECK_MODIFIER(SHIFT);
CHECK_MODIFIER(CONTROL);
CHECK_MODIFIER(MODE_SWITCH);
CHECK_MODIFIER(MOD1);
CHECK_MODIFIER(MOD2);
CHECK_MODIFIER(MOD3);
CHECK_MODIFIER(MOD4);
CHECK_MODIFIER(MOD5);
/* No modifier found? Then were done with this step */
break;
}
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");
#if defined(__OpenBSD__)
size_t len = strlen(sym);
if (len > (rest - sym))
len = (rest - sym);
new->symbol = smalloc(len + 1);
memcpy(new->symbol, sym, len+1);
new->symbol[len]='\0';
#else
new->symbol = strndup(sym, (rest - sym));
#endif
}
rest++;
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);
continue;
}
if (strcasecmp(key, "floating_modifier") == 0) {
char *walk = value;
uint32_t modifiers = 0;
while (*walk != '\0') {
/* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */
CHECK_MODIFIER(SHIFT);
CHECK_MODIFIER(CONTROL);
CHECK_MODIFIER(MODE_SWITCH);
CHECK_MODIFIER(MOD1);
CHECK_MODIFIER(MOD2);
CHECK_MODIFIER(MOD3);
CHECK_MODIFIER(MOD4);
CHECK_MODIFIER(MOD5);
/* No modifier found? Then were done with this step */
break;
}
LOG("Floating modifiers = %d\n", modifiers);
config.floating_modifier = modifiers;
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);
workspace_get(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);
if (*name != '\0')
workspace_set_name(workspace_get(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;
char *target;
char *end;
/* If the window class/title is quoted we skip quotes */
if (value[0] == '"') {
class_title = sstrdup(value+1);
end = strchr(class_title, '"');
} else {
class_title = sstrdup(value);
/* If it is not quoted, we terminate it at the first space */
end = strchr(class_title, ' ');
}
if (end == NULL)
die("Malformed assignment, couldn't find terminating quote\n");
*end = '\0';
/* 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] == ' ')
value[strlen(value)-1] = '\0';
/* The target is the last argument separated by a space */
if ((target = strrchr(value, ' ')) == NULL)
die("Malformed assignment, couldn't find target (\"%s\")\n", value);
target++;
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 (strchr(target, '~') != NULL)
new->floating = ASSIGN_FLOATING_ONLY;
while (*target == '~')
target++;
if (atoi(target) >= 1) {
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;
}
/* set a custom variable */
if (strcasecmp(key, "set") == 0) {
if (value[0] != '$')
die("Malformed variable assignment, name has to start with $\n");
/* get key/value for this variable */
char *v_key = value, *v_value;
if ((v_value = strstr(value, " ")) == NULL)
die("Malformed variable assignment, need a value\n");
*(v_value++) = '\0';
struct Variable *new = scalloc(sizeof(struct Variable));
new->key = sstrdup(v_key);
new->value = sstrdup(v_value);
SLIST_INSERT_HEAD(&variables, new, variables);
LOG("Got new variable %s = %s\n", v_key, v_value);
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);
while (!SLIST_EMPTY(&variables)) {
struct Variable *v = SLIST_FIRST(&variables);
SLIST_REMOVE_HEAD(&variables, variables);
free(v->key);
free(v->value);
free(v);
}
if (reload) {
translate_keysyms();
grab_all_keys(conn, false);
}
REQUIRED_OPTION(terminal);
REQUIRED_OPTION(font);
/* Set an empty name for every workspace which got no name */
@ -618,6 +398,4 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
workspace_set_name(ws, NULL);
}
return;
}

43
src/container.c Normal file
View File

@ -0,0 +1,43 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include "data.h"
#include "log.h"
/*
* Returns the mode of the given container (or MODE_DEFAULT if a NULL pointer
* was passed in order to save a few explicit checks in other places). If
* for_frame was set to true, the special case of having exactly one client
* in a container is handled so that MODE_DEFAULT is returned. For some parts
* of the rendering, this is interesting, other parts need the real mode.
*
*/
int container_mode(Container *con, bool for_frame) {
int num_clients = 0;
Client *client;
if (con == NULL || con->mode == MODE_DEFAULT)
return MODE_DEFAULT;
if (!for_frame)
return con->mode;
CIRCLEQ_FOREACH(client, &(con->clients), clients)
num_clients++;
/* If the container contains only one client, mode is irrelevant */
if (num_clients == 1) {
DLOG("mode to default\n");
return MODE_DEFAULT;
}
return con->mode;
}

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -14,6 +14,8 @@
#include <stdio.h>
#include <xcb/xcb.h>
#include "log.h"
static const char *labelError[] = {
"Success",
"BadRequest",
@ -219,19 +221,21 @@ int format_event(xcb_generic_event_t *e) {
switch(e->response_type) {
case 0:
printf("Error %s on seqnum %d (%s).\n",
DLOG("Error %s on seqnum %d (%s).\n",
labelError[*((uint8_t *) e + 1)],
seqnum,
labelRequest[*((uint8_t *) e + 10)]);
break;
default:
printf("Event %s following seqnum %d%s.\n",
if (e->response_type > sizeof(labelEvent) / sizeof(char*))
break;
DLOG("Event %s following seqnum %d%s.\n",
labelEvent[e->response_type],
seqnum,
labelSendEvent[sendEvent]);
break;
case XCB_KEYMAP_NOTIFY:
printf("Event %s%s.\n",
DLOG("Event %s%s.\n",
labelEvent[e->response_type],
labelSendEvent[sendEvent]);
break;

103
src/ewmh.c Normal file
View File

@ -0,0 +1,103 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* ewmh.c: Functions to get/set certain EWMH properties easily.
*
*/
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include "data.h"
#include "table.h"
#include "i3.h"
#include "xcb.h"
#include "util.h"
#include "log.h"
/*
* Updates _NET_CURRENT_DESKTOP with the current desktop number.
*
* EWMH: The index of the current desktop. This is always an integer between 0
* and _NET_NUMBER_OF_DESKTOPS - 1.
*
*/
void ewmh_update_current_desktop() {
uint32_t current_desktop = c_ws->num;
xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root,
atoms[_NET_CURRENT_DESKTOP], CARDINAL, 32, 1,
&current_desktop);
}
/*
* Updates _NET_ACTIVE_WINDOW with the currently focused window.
*
* EWMH: The window ID of the currently active window or None if no window has
* the focus.
*
*/
void ewmh_update_active_window(xcb_window_t window) {
xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root,
atoms[_NET_ACTIVE_WINDOW], WINDOW, 32, 1, &window);
}
/*
* Updates the workarea for each desktop.
*
* EWMH: Contains a geometry for each desktop. These geometries specify an area
* that is completely contained within the viewport. Work area SHOULD be used by
* desktop applications to place desktop icons appropriately.
*
*/
void ewmh_update_workarea() {
Workspace *ws;
int num_workspaces = 0, count = 0;
Rect last_rect = {0, 0, 0, 0};
/* Get the number of workspaces */
TAILQ_FOREACH(ws, workspaces, workspaces) {
/* Check if we need to initialize last_rect. The case that the
* first workspace is all-zero may happen when the user
* assigned workspace 2 for his first screen, for example. Thus
* we need an initialized last_rect in the very first run of
* the following loop. */
if (last_rect.width == 0 && last_rect.height == 0 &&
ws->rect.width != 0 && ws->rect.height != 0) {
memcpy(&last_rect, &(ws->rect), sizeof(Rect));
}
num_workspaces++;
}
DLOG("Got %d workspaces\n", num_workspaces);
uint8_t *workarea = smalloc(sizeof(Rect) * num_workspaces);
TAILQ_FOREACH(ws, workspaces, workspaces) {
DLOG("storing %d: %dx%d with %d x %d\n", count, ws->rect.x,
ws->rect.y, ws->rect.width, ws->rect.height);
/* If a workspace is not yet initialized and thus its
* dimensions are zero, we will instead put the dimensions
* of the last workspace in the list. For example firefox
* intersects all workspaces and does not cope so well with
* an all-zero workspace. */
if (ws->rect.width == 0 || ws->rect.height == 0) {
DLOG("re-using last_rect (%dx%d, %d, %d)\n",
last_rect.x, last_rect.y, last_rect.width,
last_rect.height);
memcpy(workarea + (sizeof(Rect) * count++), &last_rect, sizeof(Rect));
continue;
}
memcpy(workarea + (sizeof(Rect) * count++), &(ws->rect), sizeof(Rect));
memcpy(&last_rect, &(ws->rect), sizeof(Rect));
}
xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root,
atoms[_NET_WORKAREA], CARDINAL, 32,
num_workspaces * (sizeof(Rect) / sizeof(uint32_t)),
workarea);
free(workarea);
xcb_flush(global_conn);
}

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -27,6 +27,7 @@
#include "client.h"
#include "floating.h"
#include "workspace.h"
#include "log.h"
/*
* Toggles floating mode for the given client.
@ -42,12 +43,12 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
i3Font *font = load_font(conn, config.font);
if (client->dock) {
LOG("Not putting dock client into floating mode\n");
DLOG("Not putting dock client into floating mode\n");
return;
}
if (con == NULL) {
LOG("This client is already in floating (container == NULL), re-inserting\n");
DLOG("This client is already in floating (container == NULL), re-inserting\n");
Client *next_tiling;
Workspace *ws = client->workspace;
SLIST_FOREACH(next_tiling, &(ws->focus_stack), focus_clients)
@ -62,7 +63,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
/* Remove the client from the list of floating clients */
TAILQ_REMOVE(&(ws->floating_clients), client, floating_clients);
LOG("destination container = %p\n", con);
DLOG("destination container = %p\n", con);
Client *old_focused = con->currently_focused;
/* Preserve position/size */
memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect));
@ -74,7 +75,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients);
else CIRCLEQ_INSERT_TAIL(&(con->clients), client, clients);
LOG("Re-inserted the client into the matrix.\n");
DLOG("Re-inserted the window.\n");
con->currently_focused = client;
client_set_below_floating(conn, client);
@ -85,7 +86,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
return;
}
LOG("Entering floating for client %08x\n", client->child);
DLOG("Entering floating for client %08x\n", client->child);
/* Remove the client of its container */
client_remove_from_container(conn, client, con, false);
@ -95,7 +96,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
if (con->currently_focused == client) {
LOG("Need to re-adjust currently_focused\n");
DLOG("Need to re-adjust currently_focused\n");
/* Get the next client in the focus stack for this particular container */
con->currently_focused = get_last_focused_client(conn, con, NULL);
}
@ -118,11 +119,11 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
client->rect.width = client->child_rect.width + 2 + 2;
client->rect.height = client->child_rect.height + (font->height + 2 + 2) + 2;
LOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
DLOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
client->floating_rect.width, client->floating_rect.height);
} else {
/* If the client was already in floating before we restore the old position / size */
LOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
DLOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
client->floating_rect.width, client->floating_rect.height);
memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect));
}
@ -161,6 +162,69 @@ void floating_assign_to_workspace(Client *client, Workspace *new_workspace) {
client->workspace->fullscreen_client = client;
}
/*
* This is an ugly data structure which we need because there is no standard
* way of having nested functions (only available as a gcc extension at the
* moment, clang doesnt support it) or blocks (only available as a clang
* extension and only on Mac OS X systems at the moment).
*
*/
struct resize_callback_params {
border_t border;
xcb_button_press_event_t *event;
};
DRAGGING_CB(resize_callback) {
struct resize_callback_params *params = extra;
xcb_button_press_event_t *event = params->event;
switch (params->border) {
case BORDER_RIGHT: {
int new_width = old_rect->width + (new_x - event->root_x);
if ((new_width < 0) ||
(new_width < client_min_width(client) && client->rect.width >= new_width))
return;
client->rect.width = new_width;
break;
}
case BORDER_BOTTOM: {
int new_height = old_rect->height + (new_y - event->root_y);
if ((new_height < 0) ||
(new_height < client_min_height(client) && client->rect.height >= new_height))
return;
client->rect.height = old_rect->height + (new_y - event->root_y);
break;
}
case BORDER_TOP: {
int new_height = old_rect->height + (event->root_y - new_y);
if ((new_height < 0) ||
(new_height < client_min_height(client) && client->rect.height >= new_height))
return;
client->rect.y = old_rect->y + (new_y - event->root_y);
client->rect.height = new_height;
break;
}
case BORDER_LEFT: {
int new_width = old_rect->width + (event->root_x - new_x);
if ((new_width < 0) ||
(new_width < client_min_width(client) && client->rect.width >= new_width))
return;
client->rect.x = old_rect->x + (new_x - event->root_x);
client->rect.width = new_width;
break;
}
}
/* Push the new position/size to X11 */
reposition_client(conn, client);
resize_client(conn, client);
xcb_flush(conn);
}
/*
* 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
@ -168,59 +232,10 @@ void floating_assign_to_workspace(Client *client, Workspace *new_workspace) {
*
*/
int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
LOG("floating border click\n");
DLOG("floating border click\n");
border_t border;
void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
switch (border) {
case BORDER_RIGHT: {
int new_width = old_rect->width + (new_x - event->root_x);
if ((new_width < 0) ||
(new_width < 50 && client->rect.width >= new_width))
return;
client->rect.width = new_width;
break;
}
case BORDER_BOTTOM: {
int new_height = old_rect->height + (new_y - event->root_y);
if ((new_height < 0) ||
(new_height < 20 && client->rect.height >= new_height))
return;
client->rect.height = old_rect->height + (new_y - event->root_y);
break;
}
case BORDER_TOP: {
int new_height = old_rect->height + (event->root_y - new_y);
if ((new_height < 0) ||
(new_height < 20 && client->rect.height >= new_height))
return;
client->rect.y = old_rect->y + (new_y - event->root_y);
client->rect.height = new_height;
break;
}
case BORDER_LEFT: {
int new_width = old_rect->width + (event->root_x - new_x);
if ((new_width < 0) ||
(new_width < 50 && client->rect.width >= new_width))
return;
client->rect.x = old_rect->x + (new_x - event->root_x);
client->rect.width = new_width;
break;
}
}
/* Push the new position/size to X11 */
reposition_client(conn, client);
resize_client(conn, client);
xcb_flush(conn);
}
if (event->event_y < 2)
border = BORDER_TOP;
else if (event->event_y >= (client->rect.height - 2))
@ -230,17 +245,31 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre
else if (event->event_x >= (client->rect.width - 2))
border = BORDER_RIGHT;
else {
LOG("Not on any border, not doing anything.\n");
DLOG("Not on any border, not doing anything.\n");
return 1;
}
LOG("border = %d\n", border);
DLOG("border = %d\n", border);
drag_pointer(conn, client, event, XCB_NONE, border, resize_callback);
struct resize_callback_params params = { border, event };
drag_pointer(conn, client, event, XCB_NONE, border, resize_callback, &params);
return 1;
}
DRAGGING_CB(drag_window_callback) {
struct xcb_button_press_event_t *event = extra;
/* Reposition the client correctly while moving */
client->rect.x = old_rect->x + (new_x - event->root_x);
client->rect.y = old_rect->y + (new_y - event->root_y);
reposition_client(conn, client);
/* Because reposition_client does not send a faked configure event (only resize does),
* we need to initiate that on our own */
fake_absolute_configure_notify(conn, client);
/* fake_absolute_configure_notify flushes */
}
/*
* Called when the user clicked on the titlebar of a floating window.
@ -248,47 +277,95 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre
*
*/
void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
LOG("floating_drag_window\n");
DLOG("floating_drag_window\n");
void drag_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
/* Reposition the client correctly while moving */
client->rect.x = old_rect->x + (new_x - event->root_x);
client->rect.y = old_rect->y + (new_y - event->root_y);
reposition_client(conn, client);
/* Because reposition_client does not send a faked configure event (only resize does),
* we need to initiate that on our own */
fake_absolute_configure_notify(conn, client);
/* fake_absolute_configure_notify flushes */
}
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback);
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event);
}
/*
* Called when the user right-clicked on the titlebar of a floating window to
* resize it.
* This is an ugly data structure which we need because there is no standard
* way of having nested functions (only available as a gcc extension at the
* moment, clang doesnt support it) or blocks (only available as a clang
* extension and only on Mac OS X systems at the moment).
*
*/
struct resize_window_callback_params {
border_t corner;
bool proportional;
xcb_button_press_event_t *event;
};
DRAGGING_CB(resize_window_callback) {
struct resize_window_callback_params *params = extra;
xcb_button_press_event_t *event = params->event;
border_t corner = params->corner;
int32_t dest_x = client->rect.x;
int32_t dest_y = client->rect.y;
uint32_t dest_width;
uint32_t dest_height;
double ratio = (double) old_rect->width / old_rect->height;
/* First guess: We resize by exactly the amount the mouse moved,
* taking into account in which corner the client was grabbed */
if (corner & BORDER_LEFT)
dest_width = old_rect->width - (new_x - event->root_x);
else dest_width = old_rect->width + (new_x - event->root_x);
if (corner & BORDER_TOP)
dest_height = old_rect->height - (new_y - event->root_y);
else dest_height = old_rect->height + (new_y - event->root_y);
/* Obey minimum window size */
dest_width = max(dest_width, client_min_width(client));
dest_height = max(dest_height, client_min_height(client));
/* User wants to keep proportions, so we may have to adjust our values */
if (params->proportional) {
dest_width = max(dest_width, (int) (dest_height * ratio));
dest_height = max(dest_height, (int) (dest_width / ratio));
}
/* If not the lower right corner is grabbed, we must also reposition
* the client by exactly the amount we resized it */
if (corner & BORDER_LEFT)
dest_x = old_rect->x + (old_rect->width - dest_width);
if (corner & BORDER_TOP)
dest_y = old_rect->y + (old_rect->height - dest_height);
client->rect = (Rect) { dest_x, dest_y, dest_width, dest_height };
/* resize_client flushes */
resize_client(conn, client);
}
/*
* Called when the user clicked on a floating window while holding the
* floating_modifier and the right mouse button.
* Calls the drag_pointer function with the resize_window callback
*
*/
void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
LOG("floating_resize_window\n");
void floating_resize_window(xcb_connection_t *conn, Client *client,
bool proportional, xcb_button_press_event_t *event) {
DLOG("floating_resize_window\n");
void resize_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
int32_t new_width = old_rect->width + (new_x - event->root_x);
int32_t new_height = old_rect->height + (new_y - event->root_y);
/* corner saves the nearest corner to the original click. It contains
* a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, ) */
border_t corner = 0;
/* Obey minimum window size and reposition the client */
if (new_width >= 50)
client->rect.width = new_width;
if (event->event_x <= (client->rect.width / 2))
corner |= BORDER_LEFT;
else corner |= BORDER_RIGHT;
if (new_height >= 20)
client->rect.height = new_height;
if (event->event_y <= (client->rect.height / 2))
corner |= BORDER_TOP;
else corner |= BORDER_RIGHT;
/* resize_client flushes */
resize_client(conn, client);
}
struct resize_window_callback_params params = { corner, proportional, event };
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback);
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, &params);
}
@ -301,7 +378,7 @@ void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_p
*
*/
void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
xcb_window_t confine_to, border_t border, callback_t callback) {
xcb_window_t confine_to, border_t border, callback_t callback, void *extra) {
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
uint32_t new_x, new_y;
Rect old_rect;
@ -351,12 +428,12 @@ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event
break;
case XCB_UNMAP_NOTIFY:
LOG("Unmap-notify, aborting\n");
DLOG("Unmap-notify, aborting\n");
xcb_event_handle(&evenths, inside_event);
goto done;
default:
LOG("Passing to original handler\n");
DLOG("Passing to original handler\n");
/* Use original handler */
xcb_event_handle(&evenths, inside_event);
break;
@ -371,7 +448,7 @@ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event
new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x;
new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y;
callback(&old_rect, new_x, new_y);
callback(conn, client, &old_rect, new_x, new_y, extra);
FREE(last_motion_notify);
}
done:
@ -387,7 +464,7 @@ done:
*
*/
void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
LOG("floating focus\n");
DLOG("floating focus\n");
if (direction == D_LEFT || direction == D_RIGHT) {
/* Go to the next/previous floating client */
@ -409,10 +486,15 @@ void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused,
*
*/
void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
LOG("floating move\n");
DLOG("floating move\n");
if (currently_focused->fullscreen) {
DLOG("Cannot move fullscreen windows\n");
return;
}
Rect destination = currently_focused->rect;
Rect *screen = &(currently_focused->workspace->screen->rect);
Rect *screen = &(currently_focused->workspace->output->rect);
switch (direction) {
case D_LEFT:
@ -437,7 +519,7 @@ void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_
(int32_t)(destination.x + 5) >= (int32_t)(screen->x + screen->width) ||
(int32_t)(destination.y + destination.height - 5) <= (int32_t)screen->y ||
(int32_t)(destination.y + 5) >= (int32_t)(screen->y + screen->height)) {
LOG("boundary check failed, not moving\n");
DLOG("boundary check failed, not moving\n");
return;
}
@ -459,7 +541,7 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) {
Client *client;
workspace->floating_hidden = !workspace->floating_hidden;
LOG("floating_hidden is now: %d\n", workspace->floating_hidden);
DLOG("floating_hidden is now: %d\n", workspace->floating_hidden);
TAILQ_FOREACH(client, &(workspace->floating_clients), floating_clients) {
if (workspace->floating_hidden)
client_unmap(conn, client);

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -17,6 +17,7 @@
#include <xcb/xcb.h>
#include <xcb/xcb_atom.h>
#include <xcb/xcb_icccm.h>
#include <xcb/randr.h>
#include <X11/XKBlib.h>
@ -28,7 +29,7 @@
#include "data.h"
#include "xcb.h"
#include "util.h"
#include "xinerama.h"
#include "randr.h"
#include "config.h"
#include "queue.h"
#include "resize.h"
@ -36,6 +37,9 @@
#include "manage.h"
#include "floating.h"
#include "workspace.h"
#include "log.h"
#include "container.h"
#include "ipc.h"
/* After mapping/unmapping windows, a notify event is generated. However, we dont want it,
since itd trigger an infinite loop of switching between the different windows when
@ -78,77 +82,45 @@ static bool event_is_ignored(const int sequence) {
return false;
}
/*
* 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) {
xcb_allow_events(conn, XCB_ALLOW_REPLAY_KEYBOARD, event->time);
xcb_flush(conn);
return 1;
}
/*
* 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) {
LOG("Keypress %d, state raw = %d\n", event->detail, event->state);
DLOG("Keypress %d, state raw = %d\n", event->detail, event->state);
/* Remove the numlock bit, all other bits are modifiers we can bind to */
uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
LOG("(removed numlock, state = %d)\n", state_filtered);
DLOG("(removed numlock, state = %d)\n", state_filtered);
/* Only use the lower 8 bits of the state (modifier masks) so that mouse
* button masks are filtered out */
state_filtered &= 0xFF;
LOG("(removed upper 8 bits, state = %d)\n", state_filtered);
DLOG("(removed upper 8 bits, state = %d)\n", state_filtered);
/* We need to get the keysym group (There are group 1 to group 4, each holding
two keysyms (without shift and with shift) using Xkb because X fails to
provide them reliably (it works in Xephyr, it does not in real X) */
XkbStateRec state;
if (XkbGetState(xkbdpy, XkbUseCoreKbd, &state) == Success && (state.group+1) == 2)
if (xkb_current_group == XkbGroup2Index)
state_filtered |= BIND_MODE_SWITCH;
LOG("(checked mode_switch, state %d)\n", state_filtered);
DLOG("(checked mode_switch, state %d)\n", state_filtered);
/* Find the binding */
Binding *bind;
TAILQ_FOREACH(bind, bindings, bindings) {
/* First compare the modifiers */
if (bind->mods != state_filtered)
continue;
Binding *bind = get_binding(state_filtered, event->detail);
/* If a symbol was specified by the user, we need to look in
* the array of translated keycodes for the events keycode */
if (bind->symbol != NULL) {
if (memmem(bind->translated_to,
bind->number_keycodes * sizeof(xcb_keycode_t),
&(event->detail), sizeof(xcb_keycode_t)) != NULL)
break;
} else {
/* This case is easier: The user specified a keycode */
if (bind->keycode == event->detail)
break;
/* No match? Then the user has Mode_switch enabled but does not have a
* specific keybinding. Fall back to the default keybindings (without
* Mode_switch). Makes it much more convenient for users of a hybrid
* layout (like us, ru). */
if (bind == NULL) {
state_filtered &= ~(BIND_MODE_SWITCH);
DLOG("no match, new state_filtered = %d\n", state_filtered);
if ((bind = get_binding(state_filtered, event->detail)) == NULL) {
ELOG("Could not lookup key binding (modifiers %d, keycode %d)\n",
state_filtered, event->detail);
return 1;
}
}
/* 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 */
if (bind == TAILQ_END(bindings)) {
xcb_allow_events(conn, ReplayKeyboard, event->time);
xcb_flush(conn);
return 1;
}
parse_command(conn, bind->command);
if (state_filtered & BIND_MODE_SWITCH) {
LOG("Mode_switch -> allow_events(SyncKeyboard)\n");
xcb_allow_events(conn, SyncKeyboard, event->time);
xcb_flush(conn);
}
return 1;
}
@ -159,21 +131,39 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_
*
*/
static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
i3Screen *screen;
Output *output;
if ((screen = get_screen_containing(x, y)) == NULL) {
LOG("ERROR: No such screen\n");
if ((output = get_output_containing(x, y)) == NULL) {
ELOG("ERROR: No such screen\n");
return;
}
if (screen == c_ws->screen)
if (output == c_ws->output)
return;
c_ws->current_row = current_row;
c_ws->current_col = current_col;
c_ws = screen->current_workspace;
c_ws = output->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);
DLOG("We're now on output %p\n", output);
/* While usually this function is only called when the user switches
* to a different output using his mouse (and thus the output is
* empty), it may be that the following race condition occurs:
* 1) the user actives a new output (say VGA1).
* 2) the cursor is sent to the first pixel of the new VGA1, thus
* generating an enter_notify for the screen (the enter_notify
* is not yet received by i3).
* 3) i3 requeries screen configuration and maps a workspace onto the
* new output.
* 4) the enter_notify event arrives and c_ws is set to the new
* workspace but the existing windows on the new workspace are not
* focused.
*
* Therefore, we re-set the focus here to be sure its correct. */
Client *first_client = SLIST_FIRST(&(c_ws->focus_stack));
if (first_client != NULL)
set_focus(global_conn, first_client, true);
}
/*
@ -181,9 +171,9 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
*
*/
int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_event_t *event) {
LOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", event->event, event->mode, event->detail, event->sequence);
DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", event->event, event->mode, event->detail, event->sequence);
if (event->mode != XCB_NOTIFY_MODE_NORMAL) {
LOG("This was not a normal notify, ignoring\n");
DLOG("This was not a normal notify, ignoring\n");
return 1;
}
/* Some events are not interesting, because they were not generated actively by the
@ -210,7 +200,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);
DLOG("Getting screen at %d x %d\n", event->root_x, event->root_y);
check_crossing_screen_boundary(event->root_x, event->root_y);
return 1;
}
@ -220,19 +210,20 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
if (client->container != NULL &&
client->container->mode == MODE_STACK &&
client->container->currently_focused != client) {
LOG("Plausibility check says: no\n");
DLOG("Plausibility check says: no\n");
return 1;
}
if (client->workspace != c_ws && client->workspace->screen == c_ws->screen) {
if (client->workspace != c_ws && client->workspace->output == c_ws->output) {
/* This can happen when a client gets assigned to a different workspace than
* the current one (see src/mainx.c:reparent_window). Shortly after it was created,
* an enter_notify will follow. */
LOG("enter_notify for a client on a different workspace but the same screen, ignoring\n");
DLOG("enter_notify for a client on a different workspace but the same screen, ignoring\n");
return 1;
}
set_focus(conn, client, false);
if (!config.disable_focus_follows_mouse)
set_focus(conn, client, false);
return 1;
}
@ -264,13 +255,14 @@ int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_not
event->request != XCB_MAPPING_MODIFIER)
return 0;
LOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n");
DLOG("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);
translate_keysyms();
grab_all_keys(conn, false);
return 0;
}
@ -284,7 +276,7 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve
cookie = xcb_get_window_attributes_unchecked(conn, event->window);
LOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence);
DLOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence);
add_ignore_event(event->sequence);
manage_window(prophs, conn, event->window, cookie, false);
@ -298,7 +290,7 @@ 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("window 0x%08x wants to be at %dx%d with %dx%d\n",
DLOG("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);
@ -328,7 +320,7 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
}
if (client->fullscreen) {
LOG("Client is in fullscreen mode\n");
DLOG("Client is in fullscreen mode\n");
Rect child_rect = client->workspace->rect;
child_rect.x = child_rect.y = 0;
@ -389,7 +381,7 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
}
}
LOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n",
DLOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n",
client->rect.x, client->rect.y, client->rect.width, client->rect.height);
/* Push the new position/size to X11 */
@ -402,22 +394,22 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
/* Dock clients can be reconfigured in their height */
if (client->dock) {
LOG("Reconfiguring height of this dock client\n");
DLOG("Reconfiguring height of this dock client\n");
if (!(event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)) {
LOG("Ignoring configure request, no height given\n");
DLOG("Ignoring configure request, no height given\n");
return 1;
}
client->desired_height = event->height;
render_workspace(conn, c_ws->screen, c_ws);
render_workspace(conn, c_ws->output, c_ws);
xcb_flush(conn);
return 1;
}
if (client->fullscreen) {
LOG("Client is in fullscreen mode\n");
DLOG("Client is in fullscreen mode\n");
Rect child_rect = client->container->workspace->rect;
child_rect.x = child_rect.y = 0;
@ -432,26 +424,30 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
}
/*
* 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) {
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
/* 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? */
if (event->x == 0 && event->y == 0)
xinerama_requery_screens(conn);
return 1;
}
return 1;
}
/*
* Gets triggered upon a RandR screen change event, that is when the user
* changes the screen configuration in any way (mode, position, )
*
*/
int handle_screen_change(void *prophs, xcb_connection_t *conn,
xcb_generic_event_t *e) {
DLOG("RandR screen change\n");
randr_query_outputs(conn);
ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}");
return 1;
}
@ -474,10 +470,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
return 1;
}
LOG("event->window = %08x, event->event = %08x\n", event->window, event->event);
LOG("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event);
DLOG("event->window = %08x, event->event = %08x\n", event->window, event->event);
DLOG("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event);
if (client == NULL) {
LOG("not a managed window. Ignoring.\n");
DLOG("not a managed window. Ignoring.\n");
/* This was most likely the destroyed frame of a client which is
* currently being unmapped, so we add this sequence (again!) to
@ -490,9 +486,14 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
client = table_remove(&by_child, event->window);
/* If this was the fullscreen client, we need to unset it */
if (client->fullscreen)
client->workspace->fullscreen_client = NULL;
/* If this was the fullscreen client, we need to unset it from all
* workspaces it was on (global fullscreen) */
if (client->fullscreen) {
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces)
if (ws->fullscreen_client == client)
ws->fullscreen_client = NULL;
}
/* Clients without a container are either floating or dock windows */
if (client->container != NULL) {
@ -508,17 +509,17 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen))
set_focus(conn, con->currently_focused, true);
} else if (client_is_floating(client)) {
LOG("Removing from floating clients\n");
DLOG("Removing from floating clients\n");
TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
}
if (client->dock) {
LOG("Removing from dock clients\n");
SLIST_REMOVE(&(client->workspace->screen->dock_clients), client, Client, dock_clients);
DLOG("Removing from dock clients\n");
SLIST_REMOVE(&(client->workspace->output->dock_clients), client, Client, dock_clients);
}
LOG("child of 0x%08x.\n", client->frame);
DLOG("child of 0x%08x.\n", client->frame);
xcb_reparent_window(conn, client->child, root, 0, 0);
client_unmap(conn, client);
@ -542,8 +543,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
if (workspace_is_visible(client->workspace))
workspace_empty = false;
if (workspace_empty)
client->workspace->screen = NULL;
if (workspace_empty) {
client->workspace->output = NULL;
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
}
/* Remove the urgency flag if set */
client->urgent = false;
@ -564,7 +567,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
if (to_focus != NULL)
set_focus(conn, to_focus, true);
else {
LOG("Restoring focus to root screen\n");
DLOG("Restoring focus to root screen\n");
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
xcb_flush(conn);
}
@ -573,6 +576,26 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
return 1;
}
/*
* A destroy notify event is sent when the window is not unmapped, but
* immediately destroyed (for example when starting a window and immediately
* killing the program which started it).
*
* We just pass on the event to the unmap notify handler (by copying the
* important fields in the event data structure).
*
*/
int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_notify_event_t *event) {
DLOG("destroy notify for 0x%08x, 0x%08x\n", event->event, event->window);
xcb_unmap_notify_event_t unmap;
unmap.sequence = event->sequence;
unmap.event = event->event;
unmap.window = event->window;
return handle_unmap_notify_event(NULL, conn, &unmap);
}
/*
* Called when a window changes its title
*
@ -580,7 +603,7 @@ 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) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
LOG("_NET_WM_NAME not specified, not changing\n");
DLOG("_NET_WM_NAME not specified, not changing\n");
return 1;
}
Client *client = table_get(&by_child, window);
@ -618,9 +641,11 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
if (client->dock)
return 1;
if (client->container != NULL &&
(client->container->mode == MODE_STACK ||
client->container->mode == MODE_TABBED))
if (!workspace_is_visible(client->workspace))
return 1;
int mode = container_mode(client->container, true);
if (mode == MODE_STACK || mode == MODE_TABBED)
render_container(conn, client->container);
else decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
xcb_flush(conn);
@ -642,7 +667,7 @@ 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) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
LOG("prop == NULL\n");
DLOG("prop == NULL\n");
return 1;
}
Client *client = table_get(&by_child, window);
@ -657,7 +682,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
char *new_name;
if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) {
perror("Could not get old name");
LOG("Could not get old name\n");
DLOG("Could not get old name\n");
return 1;
}
/* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */
@ -685,6 +710,9 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
if (client->dock)
return 1;
if (!workspace_is_visible(client->workspace))
return 1;
if (client->container != NULL &&
(client->container->mode == MODE_STACK ||
client->container->mode == MODE_TABBED))
@ -702,7 +730,7 @@ 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) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
LOG("prop == NULL\n");
DLOG("prop == NULL\n");
return 1;
}
Client *client = table_get(&by_child, window);
@ -736,7 +764,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
skip all events but the last one */
if (event->count != 0)
return 1;
LOG("window = %08x\n", event->window);
DLOG("window = %08x\n", event->window);
Client *client = table_get(&by_parent, event->window);
if (client == NULL) {
@ -750,9 +778,9 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
}
/* …or one of the bars? */
i3Screen *screen;
TAILQ_FOREACH(screen, virtual_screens, screens)
if (screen->bar == event->window)
Output *output;
TAILQ_FOREACH(output, &outputs, outputs)
if (output->bar == event->window)
render_layout(conn);
return 1;
}
@ -760,9 +788,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
if (client->dock)
return 1;
if (client->container == NULL ||
(client->container->mode != MODE_STACK &&
client->container->mode != MODE_TABBED))
if (container_mode(client->container, true) == MODE_DEFAULT)
decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
else {
uint32_t background_color;
@ -787,7 +813,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
/* Draw a black background */
xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
if (client->titlebar_position == TITLEBAR_OFF) {
if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) {
xcb_rectangle_t crect = {1, 0, client->rect.width - (1 + 1), client->rect.height - 1};
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
} else {
@ -821,7 +847,7 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message
event->data.data32[0] == _NET_WM_STATE_TOGGLE)))
client_toggle_fullscreen(conn, client);
} else {
LOG("unhandled clientmessage\n");
ELOG("unhandled clientmessage\n");
return 0;
}
@ -832,7 +858,7 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi
xcb_atom_t atom, xcb_get_property_reply_t *property) {
/* TODO: Implement this one. To do this, implement a little test program which sleep(1)s
before changing this property. */
LOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n");
ELOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n");
return 0;
}
@ -847,7 +873,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
xcb_atom_t name, xcb_get_property_reply_t *reply) {
Client *client = table_get(&by_child, window);
if (client == NULL) {
LOG("Received WM_SIZE_HINTS for unknown client\n");
DLOG("Received WM_SIZE_HINTS for unknown client\n");
return 1;
}
xcb_size_hints_t size_hints;
@ -862,27 +888,24 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) {
// TODO: Minimum size is not yet implemented
//LOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
}
bool changed = false;
if ((size_hints.flags & XCB_SIZE_HINT_P_RESIZE_INC)) {
bool changed = false;
if (size_hints.width_inc > 0)
if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF)
if (client->width_increment != size_hints.width_inc) {
client->width_increment = size_hints.width_inc;
changed = true;
}
if (size_hints.height_inc > 0)
if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF)
if (client->height_increment != size_hints.height_inc) {
client->height_increment = size_hints.height_inc;
changed = true;
}
if (changed) {
resize_client(conn, client);
xcb_flush(conn);
}
if (changed)
DLOG("resize increments changed\n");
}
int base_width = 0, base_height = 0;
@ -890,10 +913,11 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
/* base_width/height are the desired size of the window.
We check if either the program-specified size or the program-specified
min-size is available */
if (size_hints.flags & XCB_SIZE_HINT_P_SIZE) {
if (size_hints.flags & XCB_SIZE_HINT_BASE_SIZE) {
base_width = size_hints.base_width;
base_height = size_hints.base_height;
} else if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) {
/* TODO: is this right? icccm says not */
base_width = size_hints.min_width;
base_height = size_hints.min_height;
}
@ -902,11 +926,18 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
base_height != client->base_height) {
client->base_width = base_width;
client->base_height = base_height;
LOG("client's base_height changed to %d\n", base_height);
DLOG("client's base_height changed to %d\n", base_height);
DLOG("client's base_width changed to %d\n", base_width);
changed = true;
}
if (changed) {
if (client->fullscreen)
LOG("Not resizing client, it is in fullscreen mode\n");
else
DLOG("Not resizing client, it is in fullscreen mode\n");
else {
resize_client(conn, client);
xcb_flush(conn);
}
}
/* If no aspect ratio was set or if it was invalid, we ignore the hints */
@ -922,8 +953,8 @@ 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("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
LOG("width = %f, height = %f\n", width, height);
DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
DLOG("width = %f, height = %f\n", width, height);
/* Sanity checks, this is user-input, in a way */
if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0)
@ -940,7 +971,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
client->force_reconfigure = true;
if (client->container != NULL) {
if (client->container != NULL && workspace_is_visible(client->workspace)) {
render_container(conn, client->container);
xcb_flush(conn);
}
@ -956,7 +987,7 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t
xcb_atom_t name, xcb_get_property_reply_t *reply) {
Client *client = table_get(&by_child, window);
if (client == NULL) {
LOG("Received WM_HINTS for unknown client\n");
DLOG("Received WM_HINTS for unknown client\n");
return 1;
}
xcb_wm_hints_t hints;
@ -971,7 +1002,7 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (!client->urgent && client == last_focused) {
LOG("Ignoring urgency flag for current client\n");
DLOG("Ignoring urgency flag for current client\n");
return 1;
}
@ -981,14 +1012,15 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t
LOG("Urgency flag changed to %d\n", client->urgent);
workspace_update_urgent_flag(client->workspace);
redecorate_window(conn, client);
/* If the workspace this client is on is not visible, we need to redraw
* the workspace bar */
if (!workspace_is_visible(client->workspace)) {
i3Screen *screen = client->workspace->screen;
render_workspace(conn, screen, screen->current_workspace);
Output *output = client->workspace->output;
render_workspace(conn, output, output->current_workspace);
xcb_flush(conn);
} else {
redecorate_window(conn, client);
}
return 1;
@ -1005,7 +1037,7 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_
xcb_atom_t name, xcb_get_property_reply_t *reply) {
Client *client = table_get(&by_child, window);
if (client == NULL) {
LOG("No such client\n");
DLOG("No such client\n");
return 1;
}
@ -1021,7 +1053,7 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_
}
if (client->floating == FLOATING_AUTO_OFF) {
LOG("This is a popup window, putting into floating\n");
DLOG("This is a popup window, putting into floating\n");
toggle_floating_mode(conn, client, true);
}
@ -1047,10 +1079,10 @@ int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state
return 1;
xcb_window_t *leader = xcb_get_property_value(prop);
if (leader == NULL || *leader == 0)
if (leader == NULL)
return 1;
LOG("Client leader changed to %08x\n", *leader);
DLOG("Client leader changed to %08x\n", *leader);
client->leader = *leader;

399
src/ipc.c
View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -12,6 +12,7 @@
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <fcntl.h>
#include <unistd.h>
@ -21,19 +22,24 @@
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <libgen.h>
#include <ev.h>
#include <yajl/yajl_gen.h>
#include <yajl/yajl_parse.h>
#include "queue.h"
#include "i3/ipc.h"
#include "ipc.h"
#include "i3.h"
#include "util.h"
#include "commands.h"
#include "log.h"
#include "table.h"
#include "randr.h"
#include "config.h"
typedef struct ipc_client {
int fd;
TAILQ_ENTRY(ipc_client) clients;
} ipc_client;
/* Shorter names for all those yajl_gen_* functions */
#define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__)
#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str))
TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
@ -50,49 +56,318 @@ static void set_nonblock(int sockfd) {
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.
* Emulates mkdir -p (creates any missing folders)
*
*/
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);
static bool mkdirp(const char *path) {
if (mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0)
return true;
if (errno != ENOENT) {
ELOG("mkdir(%s) failed: %s\n", path, strerror(errno));
return false;
}
char *copy = strdup(path);
/* strip trailing slashes, if any */
while (copy[strlen(copy)-1] == '/')
copy[strlen(copy)-1] = '\0';
switch (message_type) {
case I3_IPC_MESSAGE_TYPE_COMMAND: {
/* To get a properly terminated buffer, we copy
* message_size bytes out of the buffer */
char *command = scalloc(message_size);
strncpy(command, (const char*)message, message_size);
parse_command(global_conn, (const char*)command);
free(command);
char *sep = strrchr(copy, '/');
if (sep == NULL)
return false;
*sep = '\0';
bool result = false;
if (mkdirp(copy))
result = mkdirp(path);
free(copy);
break;
return result;
}
static void ipc_send_message(int fd, const unsigned char *payload,
int message_type, int message_size) {
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(fd, msg + sent_bytes, bytes_to_go);
if (n == -1) {
DLOG("write() failed: %s\n", strerror(errno));
return;
}
default:
LOG("unhandled ipc message\n");
break;
sent_bytes += n;
bytes_to_go -= n;
}
}
/*
* Sends the specified event to all IPC clients which are currently connected
* and subscribed to this kind of event.
*
*/
void ipc_send_event(const char *event, uint32_t message_type, const char *payload) {
ipc_client *current;
TAILQ_FOREACH(current, &all_clients, clients) {
/* see if this client is interested in this event */
bool interested = false;
for (int i = 0; i < current->num_events; i++) {
if (strcasecmp(current->events[i], event) != 0)
continue;
interested = true;
break;
}
if (!interested)
continue;
ipc_send_message(current->fd, (const unsigned char*)payload,
message_type, strlen(payload));
}
}
/*
* Calls shutdown() on each socket and closes it. This function to be called
* when exiting or restarting only!
*
*/
void ipc_shutdown() {
ipc_client *current;
TAILQ_FOREACH(current, &all_clients, clients) {
shutdown(current->fd, SHUT_RDWR);
close(current->fd);
}
}
/*
* Executes the command and returns whether it could be successfully parsed
* or not (at the moment, always returns true).
*
*/
IPC_HANDLER(command) {
/* To get a properly terminated buffer, we copy
* message_size bytes out of the buffer */
char *command = scalloc(message_size);
strncpy(command, (const char*)message, message_size);
parse_command(global_conn, (const char*)command);
free(command);
/* For now, every command gets a positive acknowledge
* (will change with the new command parser) */
const char *reply = "{\"success\":true}";
ipc_send_message(fd, (const unsigned char*)reply,
I3_IPC_REPLY_TYPE_COMMAND, strlen(reply));
}
/*
* Formats the reply message for a GET_WORKSPACES request and sends it to the
* client
*
*/
IPC_HANDLER(get_workspaces) {
Workspace *ws;
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused == SLIST_END(&(c_ws->focus_stack)))
last_focused = NULL;
yajl_gen gen = yajl_gen_alloc(NULL, NULL);
y(array_open);
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->output == NULL)
continue;
y(map_open);
ystr("num");
y(integer, ws->num + 1);
ystr("name");
ystr(ws->utf8_name);
ystr("visible");
y(bool, ws->output->current_workspace == ws);
ystr("focused");
y(bool, c_ws == ws);
ystr("rect");
y(map_open);
ystr("x");
y(integer, ws->rect.x);
ystr("y");
y(integer, ws->rect.y);
ystr("width");
y(integer, ws->rect.width);
ystr("height");
y(integer, ws->rect.height);
y(map_close);
ystr("output");
ystr(ws->output->name);
ystr("urgent");
y(bool, ws->urgent);
y(map_close);
}
y(array_close);
const unsigned char *payload;
unsigned int length;
y(get_buf, &payload, &length);
ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length);
y(free);
}
/*
* Formats the reply message for a GET_OUTPUTS request and sends it to the
* client
*
*/
IPC_HANDLER(get_outputs) {
Output *output;
yajl_gen gen = yajl_gen_alloc(NULL, NULL);
y(array_open);
TAILQ_FOREACH(output, &outputs, outputs) {
y(map_open);
ystr("name");
ystr(output->name);
ystr("active");
y(bool, output->active);
ystr("rect");
y(map_open);
ystr("x");
y(integer, output->rect.x);
ystr("y");
y(integer, output->rect.y);
ystr("width");
y(integer, output->rect.width);
ystr("height");
y(integer, output->rect.height);
y(map_close);
ystr("current_workspace");
if (output->current_workspace == NULL)
y(null);
else y(integer, output->current_workspace->num + 1);
y(map_close);
}
y(array_close);
const unsigned char *payload;
unsigned int length;
y(get_buf, &payload, &length);
ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_OUTPUTS, length);
y(free);
}
/*
* Callback for the YAJL parser (will be called when a string is parsed).
*
*/
static int add_subscription(void *extra, const unsigned char *s,
unsigned int len) {
ipc_client *client = extra;
DLOG("should add subscription to extra %p, sub %.*s\n", client, len, s);
int event = client->num_events;
client->num_events++;
client->events = realloc(client->events, client->num_events * sizeof(char*));
/* We copy the string because it is not null-terminated and strndup()
* is missing on some BSD systems */
client->events[event] = scalloc(len+1);
memcpy(client->events[event], s, len);
DLOG("client is now subscribed to:\n");
for (int i = 0; i < client->num_events; i++)
DLOG("event %s\n", client->events[i]);
DLOG("(done)\n");
return 1;
}
/*
* Subscribes this connection to the event types which were given as a JSON
* serialized array in the payload field of the message.
*
*/
IPC_HANDLER(subscribe) {
yajl_handle p;
yajl_callbacks callbacks;
yajl_status stat;
ipc_client *current, *client = NULL;
/* Search the ipc_client structure for this connection */
TAILQ_FOREACH(current, &all_clients, clients) {
if (current->fd != fd)
continue;
client = current;
break;
}
if (client == NULL) {
ELOG("Could not find ipc_client data structure for fd %d\n", fd);
return;
}
/* Setup the JSON parser */
memset(&callbacks, 0, sizeof(yajl_callbacks));
callbacks.yajl_string = add_subscription;
p = yajl_alloc(&callbacks, NULL, NULL, (void*)client);
stat = yajl_parse(p, (const unsigned char*)message, message_size);
if (stat != yajl_status_ok) {
unsigned char *err;
err = yajl_get_error(p, true, (const unsigned char*)message,
message_size);
ELOG("YAJL parse error: %s\n", err);
yajl_free_error(p, err);
const char *reply = "{\"success\":false}";
ipc_send_message(fd, (const unsigned char*)reply,
I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply));
yajl_free(p);
return;
}
yajl_free(p);
const char *reply = "{\"success\":true}";
ipc_send_message(fd, (const unsigned char*)reply,
I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply));
}
/* The index of each callback function corresponds to the numeric
* value of the message type (see include/i3/ipc.h) */
handler_t handlers[4] = {
handle_command,
handle_get_workspaces,
handle_subscribe,
handle_get_outputs
};
/*
* Handler for activity on a client connection, receives a message from a
* client.
@ -122,11 +397,13 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
close(w->fd);
/* Delete the client from the list of clients */
struct ipc_client *current;
ipc_client *current;
TAILQ_FOREACH(current, &all_clients, clients) {
if (current->fd != w->fd)
continue;
for (int i = 0; i < current->num_events; i++)
free(current->events[i]);
/* We can call TAILQ_REMOVE because we break out of the
* TAILQ_FOREACH afterwards */
TAILQ_REMOVE(&all_clients, current, clients);
@ -135,7 +412,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
ev_io_stop(EV_A_ w);
LOG("IPC: client disconnected\n");
DLOG("IPC: client disconnected\n");
return;
}
@ -144,18 +421,18 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
/* Check if the message starts with the i3 IPC magic code */
if (n < strlen(I3_IPC_MAGIC)) {
LOG("IPC: message too short, ignoring\n");
DLOG("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");
DLOG("IPC: message does not start with the IPC magic\n");
return;
}
uint8_t *message = (uint8_t*)buf;
while (n > 0) {
LOG("IPC: n = %d\n", n);
DLOG("IPC: n = %d\n", n);
message += strlen(I3_IPC_MAGIC);
n -= strlen(I3_IPC_MAGIC);
@ -165,7 +442,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
n -= sizeof(uint32_t);
if (message_size > n) {
LOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n");
DLOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n");
return;
}
@ -174,7 +451,12 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
message += sizeof(uint32_t);
n -= sizeof(uint32_t);
ipc_handle_message(message, n, message_size, message_type);
if (message_type >= (sizeof(handlers) / sizeof(handler_t)))
DLOG("Unhandled message type: %d\n", message_type);
else {
handler_t h = handlers[message_type];
h(w->fd, message, n, message_size, message_type);
}
n -= message_size;
message += message_size;
}
@ -200,13 +482,13 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
set_nonblock(client);
struct ev_io *package = calloc(sizeof(struct ev_io), 1);
struct ev_io *package = scalloc(sizeof(struct ev_io));
ev_io_init(package, ipc_receive_message, client, EV_READ);
ev_io_start(EV_A_ package);
LOG("IPC: new client connected\n");
DLOG("IPC: new client connected\n");
struct ipc_client *new = calloc(sizeof(struct ipc_client), 1);
ipc_client *new = scalloc(sizeof(ipc_client));
new->fd = client;
TAILQ_INSERT_TAIL(&all_clients, new, clients);
@ -220,11 +502,20 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
int ipc_create_socket(const char *filename) {
int sockfd;
char *globbed = glob_path(filename);
DLOG("Creating IPC-socket at %s\n", globbed);
char *copy = sstrdup(globbed);
const char *dir = dirname(copy);
if (!path_exists(dir))
mkdirp(dir);
free(copy);
/* Unlink the unix domain socket before */
unlink(filename);
unlink(globbed);
if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
perror("socket()");
free(globbed);
return -1;
}
@ -233,12 +524,14 @@ int ipc_create_socket(const char *filename) {
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, filename);
strcpy(addr.sun_path, globbed);
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) {
perror("bind()");
free(globbed);
return -1;
}
free(globbed);
set_nonblock(sockfd);
if (listen(sockfd, 5) < 0) {

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -22,23 +22,14 @@
#include "xcb.h"
#include "table.h"
#include "util.h"
#include "xinerama.h"
#include "randr.h"
#include "layout.h"
#include "client.h"
#include "floating.h"
#include "handlers.h"
#include "workspace.h"
/*
* Updates *destination with new_value and returns true if it was changed or false
* if it was the same
*
*/
static bool update_if_necessary(uint32_t *destination, const uint32_t new_value) {
uint32_t old_value = *destination;
return ((*destination = new_value) != old_value);
}
#include "log.h"
#include "container.h"
/*
* Gets the unoccupied space (= space which is available for windows which were resized by the user)
@ -50,16 +41,16 @@ int get_unoccupied_x(Workspace *workspace) {
double unoccupied = workspace->rect.width;
double default_factor = ((float)workspace->rect.width / workspace->cols) / workspace->rect.width;
LOG("get_unoccupied_x(), starting with %f, default_factor = %f\n", unoccupied, default_factor);
DLOG("get_unoccupied_x(), starting with %f, default_factor = %f\n", unoccupied, default_factor);
for (int cols = 0; cols < workspace->cols; cols++) {
LOG("width_factor[%d] = %f, unoccupied = %f\n", cols, workspace->width_factor[cols], unoccupied);
DLOG("width_factor[%d] = %f, unoccupied = %f\n", cols, workspace->width_factor[cols], unoccupied);
if (workspace->width_factor[cols] == 0)
unoccupied -= workspace->rect.width * default_factor;
}
LOG("unoccupied space: %f\n", unoccupied);
DLOG("unoccupied space: %f\n", unoccupied);
return unoccupied;
}
@ -69,15 +60,15 @@ int get_unoccupied_y(Workspace *workspace) {
double unoccupied = height;
double default_factor = ((float)height / workspace->rows) / height;
LOG("get_unoccupied_y(), starting with %f, default_factor = %f\n", unoccupied, default_factor);
DLOG("get_unoccupied_y(), starting with %f, default_factor = %f\n", unoccupied, default_factor);
for (int rows = 0; rows < workspace->rows; rows++) {
LOG("height_factor[%d] = %f, unoccupied = %f\n", rows, workspace->height_factor[rows], unoccupied);
DLOG("height_factor[%d] = %f, unoccupied = %f\n", rows, workspace->height_factor[rows], unoccupied);
if (workspace->height_factor[rows] == 0)
unoccupied -= height * default_factor;
}
LOG("unoccupied space: %f\n", unoccupied);
DLOG("unoccupied space: %f\n", unoccupied);
return unoccupied;
}
@ -140,16 +131,15 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
- Draw two lines in a lighter color
- Draw the windows title
*/
int mode = container_mode(client->container, true);
/* Draw a rectangle in background color around the window */
if (client->borderless && (client->container == NULL ||
(client->container->mode != MODE_STACK &&
client->container->mode != MODE_TABBED)))
if (client->borderless && mode == MODE_DEFAULT)
xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
else xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background);
/* In stacking mode, we only render the rect for this specific decoration */
if (client->container != NULL && (client->container->mode == MODE_STACK || client->container->mode == MODE_TABBED)) {
if (mode == MODE_STACK || mode == MODE_TABBED) {
/* We need to use the containers width because it is the more recent value - when
in stacking mode, clients get reconfigured only on demand (the not active client
is not reconfigured), so the clients rect.width would be wrong */
@ -164,7 +154,10 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
/* Draw the inner background to have a black frame around clients (such as mplayer)
which cannot be resized exactly in our frames and therefore are centered */
xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
if (client->titlebar_position == TITLEBAR_OFF) {
if (client->titlebar_position == TITLEBAR_OFF && client->borderless) {
xcb_rectangle_t crect = {0, 0, client->rect.width, client->rect.height};
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
} else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) {
xcb_rectangle_t crect = {1, 1, client->rect.width - (1 + 1), client->rect.height - (1 + 1)};
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
} else {
@ -174,22 +167,21 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
}
}
mode = container_mode(client->container, false);
if (client->titlebar_position != TITLEBAR_OFF) {
/* Draw the lines */
xcb_draw_line(conn, drawable, gc, color->border, offset_x, offset_y, offset_x + client->rect.width, offset_y);
if ((client->container == NULL ||
(client->container->mode != MODE_STACK &&
client->container->mode != MODE_TABBED) ||
CIRCLEQ_NEXT_OR_NULL(&(client->container->clients), client, clients) == NULL))
xcb_draw_line(conn, drawable, gc, color->border,
offset_x + 2, /* x */
offset_y + font->height + 3, /* y */
offset_x + client->rect.width - 3, /* to_x */
offset_y + font->height + 3 /* to_y */);
xcb_draw_line(conn, drawable, gc, color->border,
offset_x + 2, /* x */
offset_y + font->height + 3, /* y */
offset_x + client->rect.width - 3, /* to_x */
offset_y + font->height + 3 /* to_y */);
}
/* If the client has a title, we draw it */
if (client->name != NULL && client->titlebar_position != TITLEBAR_OFF) {
if (client->name != NULL &&
(mode != MODE_DEFAULT || client->titlebar_position != TITLEBAR_OFF)) {
/* Draw the font */
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
uint32_t values[] = { color->text, color->background, font->id };
@ -212,9 +204,9 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
*
*/
void reposition_client(xcb_connection_t *conn, Client *client) {
i3Screen *screen;
Output *output;
LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
/* Note: We can use a pointer to client->x like an array of uint32_ts
because it is followed by client->y by definition */
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
@ -223,19 +215,25 @@ void reposition_client(xcb_connection_t *conn, Client *client) {
return;
/* If the client is floating, we need to check if we moved it to a different workspace */
screen = get_screen_containing(client->rect.x + (client->rect.width / 2),
output = get_output_containing(client->rect.x + (client->rect.width / 2),
client->rect.y + (client->rect.height / 2));
if (client->workspace->screen == screen)
if (client->workspace->output == output)
return;
if (screen == NULL) {
LOG("Boundary checking disabled, no screen found for (%d, %d)\n", client->rect.x, client->rect.y);
if (output == NULL) {
DLOG("Boundary checking disabled, no output found for (%d, %d)\n", client->rect.x, client->rect.y);
return;
}
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, screen->current_workspace);
if (output->current_workspace == NULL) {
DLOG("Boundary checking deferred, no current workspace on output\n");
client->force_reconfigure = true;
return;
}
DLOG("Client is on workspace %p with output %p\n", client->workspace, client->workspace->output);
DLOG("but output at %d, %d is %p\n", client->rect.x, client->rect.y, output);
floating_assign_to_workspace(client, output->current_workspace);
set_focus(conn, client, true);
}
@ -250,24 +248,15 @@ void reposition_client(xcb_connection_t *conn, Client *client) {
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_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT,
&(client->rect.x));
DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
DLOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
xcb_set_window_rect(conn, client->frame, client->rect);
/* Adjust the position of the child inside its frame.
* The coordinates of the child are relative to its frame, we
* add a border of 2 pixel to each value */
uint32_t mask = XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT;
Rect *rect = &(client->child_rect);
switch ((client->container != NULL ? client->container->mode : MODE_DEFAULT)) {
switch (container_mode(client->container, true)) {
case MODE_STACK:
case MODE_TABBED:
rect->x = 2;
@ -301,7 +290,7 @@ void resize_client(xcb_connection_t *conn, Client *client) {
/* Obey the ratio, if any */
if (client->proportional_height != 0 &&
client->proportional_width != 0) {
LOG("proportional height = %d, width = %d\n", client->proportional_height, client->proportional_width);
DLOG("proportional height = %d, width = %d\n", client->proportional_height, client->proportional_width);
double new_height = rect->height + 1;
int new_width = rect->width;
@ -317,26 +306,26 @@ void resize_client(xcb_connection_t *conn, Client *client) {
rect->height = new_height;
rect->width = new_width;
LOG("new_height = %f, new_width = %d\n", new_height, new_width);
DLOG("new_height = %f, new_width = %d\n", new_height, new_width);
}
if (client->height_increment > 1) {
int old_height = rect->height;
rect->height -= (rect->height - client->base_height) % client->height_increment;
LOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n",
DLOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n",
old_height - rect->height, client->height_increment, client->base_height);
}
if (client->width_increment > 1) {
int old_width = rect->width;
rect->width -= (rect->width - client->base_width) % client->width_increment;
LOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n",
DLOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n",
old_width - rect->width, client->width_increment, client->base_width);
}
LOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
DLOG("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));
xcb_set_window_rect(conn, client->child, *rect);
/* After configuring a child window we need to fake a configure_notify_event (see ICCCM 4.2.3).
* This is necessary to inform the client of its position relative to the root window,
@ -364,6 +353,10 @@ void render_container(xcb_connection_t *conn, Container *container) {
num_clients++;
if (container->mode == MODE_DEFAULT) {
int height = (container->height / max(1, num_clients));
int rest_pixels = (container->height % max(1, num_clients));
DLOG("height per client = %d, rest = %d\n", height, rest_pixels);
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
/* If the client is in fullscreen mode, it does not get reconfigured */
if (container->workspace->fullscreen_client == client) {
@ -371,6 +364,13 @@ void render_container(xcb_connection_t *conn, Container *container) {
continue;
}
/* If we have some pixels left to distribute, add one
* pixel to each client as long as possible. */
int this_height = height;
if (rest_pixels > 0) {
height++;
rest_pixels--;
}
/* Check if we changed client->x or client->y by updating it.
* Note the bitwise OR instead of logical OR to force evaluation of both statements */
if (client->force_reconfigure |
@ -378,7 +378,7 @@ void render_container(xcb_connection_t *conn, Container *container) {
update_if_necessary(&(client->rect.y), container->y +
(container->height / num_clients) * current_client) |
update_if_necessary(&(client->rect.width), container->width) |
update_if_necessary(&(client->rect.height), container->height / num_clients))
update_if_necessary(&(client->rect.height), this_height))
resize_client(conn, client);
/* TODO: vertical default layout */
@ -398,16 +398,16 @@ 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 && num_clients > 0) {
LOG("remapping stack win\n");
if (stack_win->rect.height == 0 && num_clients > 1) {
DLOG("remapping stack win\n");
xcb_map_window(conn, stack_win->window);
} else LOG("not remapping stackwin, height = %d, num_clients = %d\n",
} else DLOG("not remapping stackwin, height = %d, num_clients = %d\n",
stack_win->rect.height, num_clients);
if (container->mode == MODE_TABBED) {
/* By setting num_clients to 1 we force that the stack window will be only one line
* high. The rest of the code is useful in both cases. */
LOG("tabbed mode, setting num_clients = 1\n");
DLOG("tabbed mode, setting num_clients = 1\n");
if (stack_lines > 1)
stack_lines = 1;
}
@ -418,11 +418,21 @@ void render_container(xcb_connection_t *conn, Container *container) {
stack_lines = min(num_clients, container->stack_limit_value);
}
int height = decoration_height * stack_lines;
if (num_clients == 1) {
height = 0;
stack_win->rect.height = 0;
xcb_unmap_window(conn, stack_win->window);
DLOG("Just one client, setting height to %d\n", height);
}
/* Check if we need to reconfigure our stack title window */
if (update_if_necessary(&(stack_win->rect.x), container->x) |
update_if_necessary(&(stack_win->rect.y), container->y) |
update_if_necessary(&(stack_win->rect.width), container->width) |
update_if_necessary(&(stack_win->rect.height), decoration_height * stack_lines)) {
if (height > 0 && (
update_if_necessary(&(stack_win->rect.x), container->x) |
update_if_necessary(&(stack_win->rect.y), container->y) |
update_if_necessary(&(stack_win->rect.width), container->width) |
update_if_necessary(&(stack_win->rect.height), height))) {
/* Configuration can happen in two slightly different ways:
@ -456,7 +466,8 @@ void render_container(xcb_connection_t *conn, Container *container) {
}
/* Prepare the pixmap for usage */
cached_pixmap_prepare(conn, &(stack_win->pixmap));
if (num_clients > 1)
cached_pixmap_prepare(conn, &(stack_win->pixmap));
int current_row = 0, current_col = 0;
int wrap = 0;
@ -486,9 +497,9 @@ void render_container(xcb_connection_t *conn, Container *container) {
* Note the bitwise OR instead of logical OR to force evaluation of all statements */
if (client->force_reconfigure |
update_if_necessary(&(client->rect.x), container->x) |
update_if_necessary(&(client->rect.y), container->y + (decoration_height * stack_lines)) |
update_if_necessary(&(client->rect.y), container->y + height) |
update_if_necessary(&(client->rect.width), container->width) |
update_if_necessary(&(client->rect.height), container->height - (decoration_height * stack_lines)))
update_if_necessary(&(client->rect.height), container->height - height))
resize_client(conn, client);
client->force_reconfigure = false;
@ -520,13 +531,15 @@ void render_container(xcb_connection_t *conn, Container *container) {
current_client++;
} else if (container->mode == MODE_TABBED) {
if (container->stack_limit == STACK_LIMIT_ROWS) {
LOG("You limited this container in its rows. "
LOG("You limited a tabbed container in its rows. "
"This makes no sense in tabbing mode.\n");
}
offset_x = current_client++ * size_each;
}
decorate_window(conn, client, stack_win->pixmap.id, stack_win->pixmap.gc,
offset_x, offset_y);
if (stack_win->pixmap.id == XCB_NONE)
continue;
decorate_window(conn, client, stack_win->pixmap.id,
stack_win->pixmap.gc, offset_x, offset_y);
}
/* Check if we need to fill one column because of an uneven
@ -553,6 +566,8 @@ void render_container(xcb_connection_t *conn, Container *container) {
}
}
if (stack_win->pixmap.id == XCB_NONE)
return;
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);
}
@ -560,8 +575,8 @@ void render_container(xcb_connection_t *conn, Container *container) {
static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) {
Client *client;
SLIST_FOREACH(client, &(r_ws->screen->dock_clients), dock_clients) {
LOG("client is at %d, should be at %d\n", client->rect.y, *height);
SLIST_FOREACH(client, &(r_ws->output->dock_clients), dock_clients) {
DLOG("client is at %d, should be at %d\n", client->rect.y, *height);
if (client->force_reconfigure |
update_if_necessary(&(client->rect.x), r_ws->rect.x) |
update_if_necessary(&(client->rect.y), *height))
@ -573,55 +588,55 @@ static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int
resize_client(conn, client);
client->force_reconfigure = false;
LOG("desired_height = %d\n", client->desired_height);
DLOG("desired_height = %d\n", client->desired_height);
*height += client->desired_height;
}
}
static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int width, int height) {
i3Font *font = load_font(conn, config.font);
i3Screen *screen = r_ws->screen;
Output *output = r_ws->output;
enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
/* Fill the whole bar in black */
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
xcb_rectangle_t rect = {0, 0, width, height};
xcb_poly_fill_rectangle(conn, screen->bar, screen->bargc, 1, &rect);
xcb_poly_fill_rectangle(conn, output->bar, output->bargc, 1, &rect);
/* Set font */
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FONT, font->id);
xcb_change_gc_single(conn, output->bargc, XCB_GC_FONT, font->id);
int drawn = 0;
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen != screen)
if (ws->output != output)
continue;
struct Colortriple *color;
if (screen->current_workspace == ws)
if (output->current_workspace == ws)
color = &(config.bar.focused);
else if (ws->urgent)
color = &(config.bar.urgent);
else color = &(config.bar.unfocused);
/* Draw the outer rect */
xcb_draw_rect(conn, screen->bar, screen->bargc, color->border,
xcb_draw_rect(conn, output->bar, output->bargc, color->border,
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,
xcb_draw_rect(conn, output->bar, output->bargc, color->background,
drawn + 1,
2,
ws->text_width + 4 + 4,
height - 4);
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_16(conn, ws->name_len, screen->bar, screen->bargc, drawn + 5 /* X */,
xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, color->text);
xcb_change_gc_single(conn, output->bargc, XCB_GC_BACKGROUND, color->background);
xcb_image_text_16(conn, ws->name_len, output->bar, output->bargc, drawn + 5 /* X */,
font->height + 1 /* Y = baseline of font */,
(xcb_char2b_t*)ws->name);
drawn += ws->text_width + 12;
@ -662,18 +677,19 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bo
* Renders the given workspace on the given screen
*
*/
void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws) {
void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) {
i3Font *font = load_font(conn, config.font);
int width = r_ws->rect.width;
int height = r_ws->rect.height;
/* Reserve space for dock clients */
Client *client;
SLIST_FOREACH(client, &(screen->dock_clients), dock_clients)
SLIST_FOREACH(client, &(output->dock_clients), dock_clients)
height -= client->desired_height;
/* Space for the internal bar */
height -= (font->height + 6);
if (!config.disable_workspace_bar)
height -= (font->height + 6);
int xoffset[r_ws->rows];
int yoffset[r_ws->cols];
@ -707,7 +723,7 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
single_width = container->width;
}
LOG("height is %d\n", height);
DLOG("height is %d\n", height);
container->height = 0;
@ -727,10 +743,21 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
yoffset[cols] += single_height;
}
/* Reposition all floating clients with force_reconfigure == true */
TAILQ_FOREACH(client, &(r_ws->floating_clients), floating_clients) {
if (!client->force_reconfigure)
continue;
client->force_reconfigure = false;
reposition_client(conn, client);
resize_client(conn, client);
}
ignore_enter_notify_forall(conn, r_ws, false);
render_bars(conn, r_ws, width, &height);
render_internal_bar(conn, r_ws, width, font->height + 6);
if (!config.disable_workspace_bar)
render_internal_bar(conn, r_ws, width, font->height + 6);
}
/*
@ -742,14 +769,11 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
*
*/
void render_layout(xcb_connection_t *conn) {
i3Screen *screen;
Output *output;
if (virtual_screens == NULL)
return;
TAILQ_FOREACH(screen, virtual_screens, screens)
if (screen->current_workspace != NULL)
render_workspace(conn, screen, screen->current_workspace);
TAILQ_FOREACH(output, &outputs, outputs)
if (output->current_workspace != NULL)
render_workspace(conn, output, output->current_workspace);
xcb_flush(conn);
}

121
src/log.c Normal file
View File

@ -0,0 +1,121 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* src/log.c: handles the setting of loglevels, contains the logging functions.
*
*/
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "util.h"
#include "log.h"
/* loglevels.h is autogenerated at make time */
#include "loglevels.h"
static uint32_t loglevel = 0;
static bool verbose = false;
/**
* Set verbosity of i3. If verbose is set to true, informative messages will
* be printed to stdout. If verbose is set to false, only errors will be
* printed.
*
*/
void set_verbosity(bool _verbose) {
verbose = _verbose;
}
/**
* Enables the given loglevel.
*
*/
void add_loglevel(const char *level) {
/* Handle the special loglevel "all" */
if (strcasecmp(level, "all") == 0) {
loglevel = UINT32_MAX;
return;
}
for (int i = 0; i < sizeof(loglevels) / sizeof(char*); i++) {
if (strcasecmp(loglevels[i], level) != 0)
continue;
/* The position in the array (plus one) is the amount of times
* which we need to shift 1 to the left to get our bitmask for
* the specific loglevel. */
loglevel |= (1 << (i+1));
break;
}
}
/*
* Logs the given message to stdout while prefixing the current time to it.
* This is to be called by *LOG() which includes filename/linenumber/function.
*
*/
void vlog(char *fmt, va_list args) {
char timebuf[64];
/* Get current time */
time_t t = time(NULL);
/* Convert time to local time (determined by the locale) */
struct tm *tmp = localtime(&t);
/* Generate time prefix */
strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp);
printf("%s", timebuf);
vprintf(fmt, args);
}
/**
* Logs the given message to stdout while prefixing the current time to it,
* but only if verbose mode is activated.
*
*/
void verboselog(char *fmt, ...) {
va_list args;
if (!verbose)
return;
va_start(args, fmt);
vlog(fmt, args);
va_end(args);
}
/**
* Logs the given message to stdout while prefixing the current time to it.
*
*/
void errorlog(char *fmt, ...) {
va_list args;
va_start(args, fmt);
vlog(fmt, args);
va_end(args);
}
/*
* Logs the given message to stdout while prefixing the current time to it,
* but only if the corresponding debug loglevel was activated.
* This is to be called by DLOG() which includes filename/linenumber
*
*/
void debuglog(int lev, char *fmt, ...) {
va_list args;
if ((loglevel & lev) == 0)
return;
va_start(args, fmt);
vlog(fmt, args);
va_end(args);
}

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -30,7 +30,6 @@
#include <xcb/xcb_property.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/xcb_icccm.h>
#include <xcb/xinerama.h>
#include <ev.h>
@ -45,9 +44,16 @@
#include "table.h"
#include "util.h"
#include "xcb.h"
#include "randr.h"
#include "xinerama.h"
#include "manage.h"
#include "ipc.h"
#include "log.h"
#include "sighandler.h"
static int xkb_event_base;
int xkb_current_group;
xcb_connection_t *global_conn;
@ -82,6 +88,9 @@ int num_screens = 0;
/* The depth of the root screen (used e.g. for creating new pixmaps later) */
uint8_t root_depth;
/* We hope that XKB is supported and set this to false */
bool xkb_supported = true;
/*
* 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
@ -119,25 +128,65 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
*
*/
static void xkb_got_event(EV_P_ struct ev_io *w, int revents) {
LOG("got xkb event, yay\n");
XEvent ev;
DLOG("Handling XKB event\n");
XkbEvent 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);
* mapping_notify once. */
bool mapping_changed = false;
while (XPending(xkbdpy)) {
XNextEvent(xkbdpy, (XEvent*)&ev);
/* While we should never receive a non-XKB event,
* better do sanity checking */
if (ev.type != xkb_event_base)
continue;
if (ev.any.xkb_type == XkbMapNotify) {
mapping_changed = true;
continue;
}
if (ev.any.xkb_type != XkbStateNotify) {
ELOG("Unknown XKB event received (type %d)\n", ev.any.xkb_type);
continue;
}
/* See The XKB Extension: Library Specification, section 14.1 */
/* We check if the current group (each group contains
* two levels) has been changed. Mode_switch activates
* group XkbGroup2Index */
if (xkb_current_group == ev.state.group)
continue;
xkb_current_group = ev.state.group;
if (ev.state.group == XkbGroup2Index) {
DLOG("Mode_switch enabled\n");
grab_all_keys(global_conn, true);
}
if (ev.state.group == XkbGroup1Index) {
DLOG("Mode_switch disabled\n");
ungrab_all_keys(global_conn);
grab_all_keys(global_conn, false);
}
}
if (!mapping_changed)
return;
DLOG("Keyboard mapping changed, updating keybindings\n");
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");
DLOG("Re-grabbing...\n");
translate_keysyms();
grab_all_keys(global_conn, (xkb_current_group == XkbGroup2Index));
DLOG("Done\n");
}
@ -145,6 +194,8 @@ int main(int argc, char *argv[], char *env[]) {
int i, screens, opt;
char *override_configpath = NULL;
bool autostart = true;
bool only_check_config = false;
bool force_xinerama = false;
xcb_connection_t *conn;
xcb_property_handlers_t prophs;
xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
@ -153,6 +204,7 @@ int main(int argc, char *argv[], char *env[]) {
{"config", required_argument, 0, 'c'},
{"version", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{"force-xinerama", no_argument, 0, 0},
{0, 0, 0, 0}
};
int option_index = 0;
@ -165,7 +217,7 @@ int main(int argc, char *argv[], char *env[]) {
start_argv = argv;
while ((opt = getopt_long(argc, argv, "c:vahl", long_options, &option_index)) != -1) {
while ((opt = getopt_long(argc, argv, "c:Cvahld:V", long_options, &option_index)) != -1) {
switch (opt) {
case 'a':
LOG("Autostart disabled using -a\n");
@ -174,18 +226,46 @@ int main(int argc, char *argv[], char *env[]) {
case 'c':
override_configpath = sstrdup(optarg);
break;
case 'v':
printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n");
exit(EXIT_SUCCESS);
case 'l':
config_use_lexer = true;
case 'C':
LOG("Checking configuration file only (-C)\n");
only_check_config = true;
break;
case 'v':
printf("i3 version " I3_VERSION " © 2009-2010 Michael Stapelberg and contributors\n");
exit(EXIT_SUCCESS);
case 'V':
set_verbosity(true);
break;
case 'd':
LOG("Enabling debug loglevel %s\n", optarg);
add_loglevel(optarg);
break;
case 'l':
/* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */
break;
case 0:
if (strcmp(long_options[option_index].name, "force-xinerama") == 0) {
force_xinerama = true;
ELOG("Using Xinerama instead of RandR. This option should be "
"avoided at all cost because it does not refresh the list "
"of screens, so you cannot configure displays at runtime. "
"Please check if your driver really does not support RandR "
"and disable this option as soon as you can.\n");
break;
}
/* fall-through */
default:
fprintf(stderr, "Usage: %s [-c configfile] [-a] [-v]\n", argv[0]);
fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "-a: disable autostart\n");
fprintf(stderr, "-v: display version and exit\n");
fprintf(stderr, "-V: enable verbose mode\n");
fprintf(stderr, "-d <loglevel>: enable debug loglevel <loglevel>\n");
fprintf(stderr, "-c <configfile>: use the provided configfile instead\n");
fprintf(stderr, "-C: check configuration file and exit\n");
fprintf(stderr, "--force-xinerama: Use Xinerama instead of RandR. This "
"option should only be used if you are stuck with the "
"nvidia closed source driver which does not support RandR.\n");
exit(EXIT_FAILURE);
}
}
@ -204,6 +284,10 @@ int main(int argc, char *argv[], char *env[]) {
die("Cannot open display\n");
load_configuration(conn, override_configpath, false);
if (only_check_config) {
LOG("Done checking configuration file. Exiting.\n");
exit(0);
}
/* Create the initial container on the first workspace. This used to
* be part of init_table, but since it possibly requires an X
@ -234,6 +318,9 @@ int main(int argc, char *argv[], char *env[]) {
REQUEST_ATOM(UTF8_STRING);
REQUEST_ATOM(WM_STATE);
REQUEST_ATOM(WM_CLIENT_LEADER);
REQUEST_ATOM(_NET_CURRENT_DESKTOP);
REQUEST_ATOM(_NET_ACTIVE_WINDOW);
REQUEST_ATOM(_NET_WORKAREA);
/* TODO: this has to be more beautiful somewhen */
int major, minor, error;
@ -241,28 +328,32 @@ int main(int argc, char *argv[], char *env[]) {
major = XkbMajorVersion;
minor = XkbMinorVersion;
int evBase, errBase;
int errBase;
if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
fprintf(stderr, "XkbOpenDisplay() failed\n");
return 1;
if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &xkb_event_base, &errBase, &major, &minor, &error)) == NULL) {
ELOG("ERROR: XkbOpenDisplay() failed, disabling XKB support\n");
xkb_supported = false;
}
if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) {
fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n");
return 1;
}
if (xkb_supported) {
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");
return 1;
}
/* end of ugliness */
int i1;
if (!XkbQueryExtension(xkbdpy,&i1,&xkb_event_base,&errBase,&major,&minor)) {
fprintf(stderr, "XKB not supported by X-server\n");
return 1;
}
/* end of ugliness */
if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, XkbMapNotifyMask, XkbMapNotifyMask)) {
fprintf(stderr, "Could not set XKB event mask\n");
return 1;
if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd,
XkbMapNotifyMask | XkbStateNotifyMask,
XkbMapNotifyMask | XkbStateNotifyMask)) {
fprintf(stderr, "Could not set XKB event mask\n");
return 1;
}
}
/* Initialize event loop using libev */
@ -278,11 +369,13 @@ int main(int argc, char *argv[], char *env[]) {
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);
if (xkb_supported) {
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);
/* 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);
@ -305,9 +398,8 @@ int main(int argc, char *argv[], char *env[]) {
/* Expose = an Application should redraw itself, in this case its our titlebars. */
xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL);
/* Key presses/releases are pretty obvious, I think */
/* Key presses are pretty obvious, I think */
xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
/* Enter window = user moved his mouse over the window */
xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL);
@ -322,6 +414,9 @@ int main(int argc, char *argv[], char *env[]) {
it any longer. Usually, the client destroys the window shortly afterwards. */
xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL);
/* Destroy notify is handled the same as unmap notify */
xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL);
/* Configure notify = windows configuration (geometry, stacking, …). We only need
it to set up ignore the following enter_notify events */
xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL);
@ -359,13 +454,15 @@ int main(int argc, char *argv[], char *env[]) {
XCB_EVENT_MASK_POINTER_MOTION |
XCB_EVENT_MASK_PROPERTY_CHANGE |
XCB_EVENT_MASK_ENTER_WINDOW };
xcb_change_window_attributes(conn, root, mask, values);
xcb_void_cookie_t cookie;
cookie = xcb_change_window_attributes_checked(conn, root, mask, values);
check_error(conn, cookie, "Another window manager seems to be running");
/* Setup NetWM atoms */
#define GET_ATOM(name) { \
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \
if (!reply) { \
LOG("Could not get atom " #name "\n"); \
ELOG("Could not get atom " #name "\n"); \
exit(-1); \
} \
atoms[name] = reply->atom; \
@ -390,6 +487,9 @@ int main(int argc, char *argv[], char *env[]) {
GET_ATOM(UTF8_STRING);
GET_ATOM(WM_STATE);
GET_ATOM(WM_CLIENT_LEADER);
GET_ATOM(_NET_CURRENT_DESKTOP);
GET_ATOM(_NET_ACTIVE_WINDOW);
GET_ATOM(_NET_WORKAREA);
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 */
@ -423,38 +523,39 @@ int main(int argc, char *argv[], char *env[]) {
xcb_get_numlock_mask(conn);
grab_all_keys(conn);
translate_keysyms();
grab_all_keys(conn, false);
/* Autostarting exec-lines */
struct Autostart *exec;
if (autostart) {
TAILQ_FOREACH(exec, &autostarts, autostarts) {
LOG("auto-starting %s\n", exec->command);
start_application(exec->command);
}
int randr_base;
if (force_xinerama) {
initialize_xinerama(conn);
} else {
DLOG("Checking for XRandR...\n");
initialize_randr(conn, &randr_base);
xcb_event_set_handler(&evenths,
randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY,
handle_screen_change,
NULL);
}
/* check for Xinerama */
LOG("Checking for Xinerama...\n");
initialize_xinerama(conn);
xcb_flush(conn);
/* Get pointer position to see on which screen were starting */
xcb_query_pointer_reply_t *reply;
if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
LOG("Could not get pointer position\n");
ELOG("Could not get pointer position\n");
return 1;
}
i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
Output *screen = get_output_containing(reply->root_x, reply->root_y);
if (screen == NULL) {
LOG("ERROR: No screen at %d x %d, starting on the first screen\n",
ELOG("ERROR: No screen at %d x %d, starting on the first screen\n",
reply->root_x, reply->root_y);
screen = TAILQ_FIRST(virtual_screens);
screen = get_first_output();
}
LOG("Starting on %d\n", screen->current_workspace);
DLOG("Starting on %p\n", screen->current_workspace);
c_ws = screen->current_workspace;
manage_existing_windows(conn, &prophs, root);
@ -463,7 +564,7 @@ int main(int argc, char *argv[], char *env[]) {
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");
ELOG("Could not create the IPC socket, IPC disabled\n");
} else {
struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
@ -474,8 +575,24 @@ int main(int argc, char *argv[], char *env[]) {
/* Handle the events which arrived until now */
xcb_check_cb(NULL, NULL, 0);
setup_signal_handler();
/* Ignore SIGPIPE to survive errors when an IPC client disconnects
* while we are sending him a message */
signal(SIGPIPE, SIG_IGN);
/* Ungrab the server to receive events and enter libevs eventloop */
xcb_ungrab_server(conn);
/* Autostarting exec-lines */
struct Autostart *exec;
if (autostart) {
TAILQ_FOREACH(exec, &autostarts, autostarts) {
LOG("auto-starting %s\n", exec->command);
start_application(exec->command);
}
}
ev_loop(loop, 0);
/* not reached */

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -30,6 +30,8 @@
#include "floating.h"
#include "client.h"
#include "workspace.h"
#include "log.h"
#include "ewmh.h"
/*
* Go through all existing windows (if the window manager is restarted) and manage them
@ -61,6 +63,28 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *pr
free(cookies);
}
/*
* Restores the geometry of each window by reparenting it to the root window
* at the position of its frame.
*
* This is to be called *only* before exiting/restarting i3 because of evil
* side-effects which are to be expected when continuing to run i3.
*
*/
void restore_geometry(xcb_connection_t *conn) {
Workspace *ws;
Client *client;
DLOG("Restoring geometry\n");
TAILQ_FOREACH(ws, workspaces, workspaces)
SLIST_FOREACH(client, &(ws->focus_stack), focus_clients)
xcb_reparent_window(conn, client->child, root,
client->rect.x, client->rect.y);
/* Make sure our changes reach the X server, we restart/exit now */
xcb_flush(conn);
}
/*
* Do some sanity checks and then reparent the window.
*
@ -78,7 +102,7 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
/* 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");
ELOG("Could not get attributes\n");
return;
}
@ -156,9 +180,9 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
/* Events for already managed windows should already be filtered in manage_window() */
assert(new == NULL);
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);
LOG("Managing window 0x%08x\n", child);
DLOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
new = scalloc(sizeof(Client));
new->force_reconfigure = true;
/* Update the data structures */
@ -220,7 +244,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
new->awaiting_useless_unmap = true;
xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
if (xcb_request_check(conn, cookie) != NULL) {
LOG("Could not reparent the window, aborting\n");
DLOG("Could not reparent the window, aborting\n");
xcb_destroy_window(conn, new->frame);
free(new);
return;
@ -247,13 +271,19 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
for (int i = 0; i < xcb_get_property_value_length(preply); i++)
if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
LOG("Window is a dock.\n");
DLOG("Window is a dock.\n");
Output *t_out = get_output_containing(x, y);
if (t_out != c_ws->output) {
DLOG("Dock client requested to be on output %s by geometry (%d, %d)\n",
t_out->name, x, y);
new->workspace = t_out->current_workspace;
}
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);
SLIST_INSERT_HEAD(&(t_out->dock_clients), new, dock_clients);
/* If its a dock we cant make it float, so we break */
new->floating = FLOATING_AUTO_OFF;
break;
@ -263,19 +293,19 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
/* Set the dialog window to automatically floating, will be used below */
new->floating = FLOATING_AUTO_ON;
LOG("dialog/utility/toolbar/splash window, automatically floating\n");
DLOG("dialog/utility/toolbar/splash window, automatically floating\n");
}
}
/* 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");
DLOG("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");
DLOG("workspace is in autofloat mode, setting floating\n");
}
if (new->dock) {
@ -289,12 +319,12 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
TODO: bars at the top */
new->desired_height = strut[3];
if (new->desired_height == 0) {
LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
DLOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
new->desired_height = original_height;
}
LOG("the client wants to be %d pixels high\n", new->desired_height);
DLOG("the client wants to be %d pixels high\n", new->desired_height);
} else {
LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
DLOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
new->desired_height = original_height;
}
} else {
@ -316,6 +346,29 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
preply = xcb_get_property_reply(conn, leader_cookie, NULL);
handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
/* if WM_CLIENT_LEADER is set, we put the new window on the
* same window as its leader. This might be overwritten by
* assignments afterwards. */
if (new->leader != XCB_NONE) {
DLOG("client->leader is set (to 0x%08x)\n", new->leader);
Client *parent = table_get(&by_child, new->leader);
if (parent != NULL && parent->container != NULL) {
Workspace *t_ws = parent->workspace;
new->container = t_ws->table[parent->container->col][parent->container->row];
new->workspace = t_ws;
old_focused = new->container->currently_focused;
map_frame = workspace_is_visible(t_ws);
new->urgent = true;
/* This is a little tricky: we cannot use
* workspace_update_urgent_flag() because the
* new window was not yet inserted into the
* focus stack on t_ws. */
t_ws->urgent = true;
} else {
DLOG("parent is not usable\n");
}
}
struct Assignment *assign;
TAILQ_FOREACH(assign, &assignments, assignments) {
if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
@ -332,14 +385,14 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
assign->windowclass_title, assign->workspace);
if (c_ws->screen->current_workspace->num == (assign->workspace-1)) {
LOG("We are already there, no need to do anything\n");
if (c_ws->output->current_workspace->num == (assign->workspace-1)) {
DLOG("We are already there, no need to do anything\n");
break;
}
LOG("Changing container/workspace and unmapping the client\n");
DLOG("Changing container/workspace and unmapping the client\n");
Workspace *t_ws = workspace_get(assign->workspace-1);
workspace_initialize(t_ws, c_ws->screen);
workspace_initialize(t_ws, c_ws->output, false);
new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
new->workspace = t_ws;
@ -351,7 +404,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
}
if (new->workspace->fullscreen_client != NULL) {
LOG("Setting below fullscreen window\n");
DLOG("Setting below fullscreen window\n");
/* If we are in fullscreen, we should place the window below
* the fullscreen window to not be annoying */
@ -394,10 +447,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
* 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");
DLOG("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");
DLOG("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);
@ -408,10 +461,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
}
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",
DLOG("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);
LOG("outer rect (%d, %d) size (%d, %d)\n",
DLOG("outer rect (%d, %d) size (%d, %d)\n",
new->rect.x, new->rect.y, new->rect.width, new->rect.height);
/* Make sure it is on top of the other windows */
@ -455,8 +508,10 @@ map:
if (map_frame)
render_container(conn, new->container);
}
if (new->container == CUR_CELL || client_is_floating(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);
ewmh_update_active_window(new->child);
}
}
}

532
src/randr.c Normal file
View File

@ -0,0 +1,532 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* For more information on RandR, please see the X.org RandR specification at
* http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
* (take your time to read it completely, it answers all questions).
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <time.h>
#include <unistd.h>
#include <xcb/xcb.h>
#include <xcb/randr.h>
#include "queue.h"
#include "i3.h"
#include "data.h"
#include "table.h"
#include "util.h"
#include "layout.h"
#include "xcb.h"
#include "config.h"
#include "workspace.h"
#include "log.h"
#include "ewmh.h"
#include "ipc.h"
#include "client.h"
/* While a clean namespace is usually a pretty good thing, we really need
* to use shorter names than the whole xcb_randr_* default names. */
typedef xcb_randr_get_crtc_info_reply_t crtc_info;
typedef xcb_randr_mode_info_t mode_info;
typedef xcb_randr_get_screen_resources_current_reply_t resources_reply;
/* Stores all outputs available in your current session. */
struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs);
static bool randr_disabled = false;
/*
* Get a specific output by its internal X11 id. Used by randr_query_outputs
* to check if the output is new (only in the first scan) or if we are
* re-scanning.
*
*/
static Output *get_output_by_id(xcb_randr_output_t id) {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs)
if (output->id == id)
return output;
return NULL;
}
/*
* Returns the output with the given name if it is active (!) or NULL.
*
*/
Output *get_output_by_name(const char *name) {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs)
if (output->active &&
strcasecmp(output->name, name) == 0)
return output;
return NULL;
}
/*
* Returns the first output which is active.
*
*/
Output *get_first_output() {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs)
if (output->active)
return output;
return NULL;
}
/*
* Returns the active (!) output which contains the coordinates x, y or NULL
* if there is no output which contains these coordinates.
*
*/
Output *get_output_containing(int x, int y) {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
continue;
DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
x, y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
if (x >= output->rect.x && x < (output->rect.x + output->rect.width) &&
y >= output->rect.y && y < (output->rect.y + output->rect.height))
return output;
}
return NULL;
}
/*
* Gets the output which is the last one in the given direction, for example
* the output on the most bottom when direction == D_DOWN, the output most
* right when direction == D_RIGHT and so on.
*
* This function always returns a output.
*
*/
Output *get_output_most(direction_t direction, Output *current) {
Output *output, *candidate = NULL;
int position = 0;
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
continue;
/* Repeated calls of WIN determine the winner of the comparison */
#define WIN(variable, condition) \
if (variable condition) { \
candidate = output; \
position = variable; \
} \
break;
if (((direction == D_UP) || (direction == D_DOWN)) &&
(current->rect.x != output->rect.x))
continue;
if (((direction == D_LEFT) || (direction == D_RIGHT)) &&
(current->rect.y != output->rect.y))
continue;
switch (direction) {
case D_UP:
WIN(output->rect.y, <= position);
case D_DOWN:
WIN(output->rect.y, >= position);
case D_LEFT:
WIN(output->rect.x, <= position);
case D_RIGHT:
WIN(output->rect.x, >= position);
}
}
assert(candidate != NULL);
return candidate;
}
/*
* Initializes the specified output, assigning the specified workspace to it.
*
*/
void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace) {
i3Font *font = load_font(conn, config.font);
workspace->output = output;
output->current_workspace = workspace;
/* Copy rect for the workspace */
memcpy(&(workspace->rect), &(output->rect), sizeof(Rect));
/* Map clients on the workspace, if any */
workspace_map_clients(conn, workspace);
/* Create a bar window on each output */
if (!config.disable_workspace_bar) {
Rect bar_rect = {output->rect.x,
output->rect.y + output->rect.height - (font->height + 6),
output->rect.x + output->rect.width,
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};
output->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values);
output->bargc = xcb_generate_id(conn);
xcb_create_gc(conn, output->bargc, output->bar, 0, 0);
}
SLIST_INIT(&(output->dock_clients));
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
DLOG("initialized output at (%d, %d) with %d x %d\n",
output->rect.x, output->rect.y, output->rect.width, output->rect.height);
DLOG("assigning configured workspaces to this output...\n");
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws == workspace)
continue;
if (ws->preferred_output == NULL ||
get_output_by_name(ws->preferred_output) != output)
continue;
DLOG("assigning ws %d\n", ws->num + 1);
workspace_assign_to(ws, output, true);
}
}
/*
* Disables RandR support by creating exactly one output with the size of the
* X11 screen.
*
*/
void disable_randr(xcb_connection_t *conn) {
xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
DLOG("RandR extension unusable, disabling.\n");
Output *s = scalloc(sizeof(Output));
s->active = true;
s->rect.x = 0;
s->rect.y = 0;
s->rect.width = root_screen->width_in_pixels;
s->rect.height = root_screen->height_in_pixels;
s->name = "xroot-0";
TAILQ_INSERT_TAIL(&outputs, s, outputs);
randr_disabled = true;
}
/*
* This function needs to be called when changing the mode of an output when
* it already has some workspaces (or a bar window) assigned.
*
* It reconfigures the bar window for the new mode, copies the new rect into
* each workspace on this output and forces all windows on the affected
* workspaces to be reconfigured.
*
* It is necessary to call render_layout() afterwards.
*
*/
static void output_change_mode(xcb_connection_t *conn, Output *output) {
i3Font *font = load_font(conn, config.font);
Workspace *ws;
Client *client;
DLOG("Output mode changed, reconfiguring bar, updating workspaces\n");
Rect bar_rect = {output->rect.x,
output->rect.y + output->rect.height - (font->height + 6),
output->rect.x + output->rect.width,
font->height + 6};
xcb_set_window_rect(conn, output->bar, bar_rect);
/* go through all workspaces and set force_reconfigure */
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->output != output)
continue;
SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) {
client->force_reconfigure = true;
if (!client_is_floating(client))
continue;
/* For floating clients we need to translate the
* coordinates (old workspace to new workspace) */
DLOG("old: (%x, %x)\n", client->rect.x, client->rect.y);
client->rect.x -= ws->rect.x;
client->rect.y -= ws->rect.y;
client->rect.x += ws->output->rect.x;
client->rect.y += ws->output->rect.y;
DLOG("new: (%x, %x)\n", client->rect.x, client->rect.y);
}
/* Update dimensions from output */
memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect));
/* Update the dimensions of a fullscreen client, if any */
if (ws->fullscreen_client != NULL) {
DLOG("Updating fullscreen client size\n");
client = ws->fullscreen_client;
Rect r = ws->rect;
xcb_set_window_rect(conn, client->frame, r);
r.x = 0;
r.y = 0;
xcb_set_window_rect(conn, client->child, r);
}
}
}
/*
* Gets called by randr_query_outputs() for each output. The function adds new
* outputs to the list of outputs, checks if the mode of existing outputs has
* been changed or if an existing output has been disabled. It will then change
* either the "changed" or the "to_be_deleted" flag of the output, if
* appropriate.
*
*/
static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
xcb_randr_get_output_info_reply_t *output,
xcb_timestamp_t cts, resources_reply *res) {
/* each CRT controller has a position in which we are interested in */
crtc_info *crtc;
Output *new = get_output_by_id(id);
bool existing = (new != NULL);
if (!existing)
new = scalloc(sizeof(Output));
new->id = id;
FREE(new->name);
asprintf(&new->name, "%.*s",
xcb_randr_get_output_info_name_length(output),
xcb_randr_get_output_info_name(output));
DLOG("found output with name %s\n", new->name);
/* Even if no CRTC is used at the moment, we store the output so that
* we do not need to change the list ever again (we only update the
* position/size) */
if (output->crtc == XCB_NONE) {
if (!existing)
TAILQ_INSERT_TAIL(&outputs, new, outputs);
else if (new->active)
new->to_be_disabled = true;
return;
}
xcb_randr_get_crtc_info_cookie_t icookie;
icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts);
if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) {
DLOG("Skipping output %s: could not get CRTC (%p)\n",
new->name, crtc);
free(new);
return;
}
bool updated = update_if_necessary(&(new->rect.x), crtc->x) |
update_if_necessary(&(new->rect.y), crtc->y) |
update_if_necessary(&(new->rect.width), crtc->width) |
update_if_necessary(&(new->rect.height), crtc->height);
free(crtc);
new->active = (new->rect.width != 0 && new->rect.height != 0);
if (!new->active) {
DLOG("width/height 0/0, disabling output\n");
return;
}
DLOG("mode: %dx%d+%d+%d\n", new->rect.width, new->rect.height,
new->rect.x, new->rect.y);
/* If we dont need to change an existing output or if the output
* does not exist in the first place, the case is simple: we either
* need to insert the new output or we are done. */
if (!updated || !existing) {
if (!existing)
TAILQ_INSERT_TAIL(&outputs, new, outputs);
return;
}
new->changed = true;
}
/*
* (Re-)queries the outputs via RandR and stores them in the list of outputs.
*
*/
void randr_query_outputs(xcb_connection_t *conn) {
Workspace *ws;
Output *output, *other, *first;
xcb_randr_get_screen_resources_current_cookie_t rcookie;
resources_reply *res;
/* timestamp of the configuration so that we get consistent replies to all
* requests (if the configuration changes between our different calls) */
xcb_timestamp_t cts;
/* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */
xcb_randr_output_t *randr_outputs;
if (randr_disabled)
return;
/* Get screen resources (crtcs, outputs, modes) */
rcookie = xcb_randr_get_screen_resources_current(conn, root);
if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) {
disable_randr(conn);
return;
}
cts = res->config_timestamp;
int len = xcb_randr_get_screen_resources_current_outputs_length(res);
randr_outputs = xcb_randr_get_screen_resources_current_outputs(res);
/* Request information for each output */
xcb_randr_get_output_info_cookie_t ocookie[len];
for (int i = 0; i < len; i++)
ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts);
/* Loop through all outputs available for this X11 screen */
for (int i = 0; i < len; i++) {
xcb_randr_get_output_info_reply_t *output;
if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL)
continue;
handle_output(conn, randr_outputs[i], output, cts, res);
free(output);
}
free(res);
/* Check for clones, disable the clones and reduce the mode to the
* lowest common mode */
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active || output->to_be_disabled)
continue;
DLOG("output %p, position (%d, %d), checking for clones\n",
output, output->rect.x, output->rect.y);
for (other = output;
other != TAILQ_END(&outputs);
other = TAILQ_NEXT(other, outputs)) {
if (other == output || !other->active || other->to_be_disabled)
continue;
if (other->rect.x != output->rect.x ||
other->rect.y != output->rect.y)
continue;
DLOG("output %p has the same position, his mode = %d x %d\n",
other, other->rect.width, other->rect.height);
uint32_t width = min(other->rect.width, output->rect.width);
uint32_t height = min(other->rect.height, output->rect.height);
if (update_if_necessary(&(output->rect.width), width) |
update_if_necessary(&(output->rect.height), height))
output->changed = true;
update_if_necessary(&(other->rect.width), width);
update_if_necessary(&(other->rect.height), height);
DLOG("disabling output %p (%s)\n", other, other->name);
other->to_be_disabled = true;
DLOG("new output mode %d x %d, other mode %d x %d\n",
output->rect.width, output->rect.height,
other->rect.width, other->rect.height);
}
}
/* Handle outputs which have a new mode or are disabled now (either
* because the user disabled them or because they are clones) */
TAILQ_FOREACH(output, &outputs, outputs) {
if (output->to_be_disabled) {
output->active = false;
DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name);
if ((first = get_first_output()) == NULL)
die("No usable outputs available\n");
bool needs_init = (first->current_workspace == NULL);
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->output != output)
continue;
workspace_assign_to(ws, first, true);
if (!needs_init)
continue;
initialize_output(conn, first, ws);
needs_init = false;
}
Client *dock;
while (!SLIST_EMPTY(&(output->dock_clients))) {
dock = SLIST_FIRST(&(output->dock_clients));
SLIST_REMOVE_HEAD(&(output->dock_clients), dock_clients);
SLIST_INSERT_HEAD(&(first->dock_clients), dock, dock_clients);
}
output->current_workspace = NULL;
output->to_be_disabled = false;
} else if (output->changed) {
output_change_mode(conn, output);
output->changed = false;
}
}
if (TAILQ_EMPTY(&outputs)) {
ELOG("No outputs found via RandR, disabling\n");
disable_randr(conn);
}
ewmh_update_workarea();
/* Just go through each active output and associate one workspace */
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active || output->current_workspace != NULL)
continue;
ws = get_first_workspace_for_output(output);
initialize_output(conn, output, ws);
}
/* render_layout flushes */
render_layout(conn);
}
/*
* We have just established a connection to the X server and need the initial
* XRandR information to setup workspaces for each screen.
*
*/
void initialize_randr(xcb_connection_t *conn, int *event_base) {
const xcb_query_extension_reply_t *extreply;
extreply = xcb_get_extension_data(conn, &xcb_randr_id);
if (!extreply->present)
disable_randr(conn);
else randr_query_outputs(conn);
if (event_base != NULL)
*event_base = extreply->first_event;
xcb_randr_select_input(conn, root,
XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
xcb_flush(conn);
}

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -24,10 +24,49 @@
#include "xcb.h"
#include "debug.h"
#include "layout.h"
#include "xinerama.h"
#include "randr.h"
#include "config.h"
#include "floating.h"
#include "workspace.h"
#include "log.h"
/*
* This is an ugly data structure which we need because there is no standard
* way of having nested functions (only available as a gcc extension at the
* moment, clang doesnt support it) or blocks (only available as a clang
* extension and only on Mac OS X systems at the moment).
*
*/
struct callback_params {
resize_orientation_t orientation;
Output *screen;
xcb_window_t helpwin;
uint32_t *new_position;
};
DRAGGING_CB(resize_callback) {
struct callback_params *params = extra;
Output *screen = params->screen;
DLOG("new x = %d, y = %d\n", new_x, new_y);
if (params->orientation == O_VERTICAL) {
/* Check if the new coordinates are within screen boundaries */
if (new_x > (screen->rect.x + screen->rect.width - 25) ||
new_x < (screen->rect.x + 25))
return;
*(params->new_position) = new_x;
xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_X, params->new_position);
} else {
if (new_y > (screen->rect.y + screen->rect.height - 25) ||
new_y < (screen->rect.y + 25))
return;
*(params->new_position) = new_y;
xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_Y, params->new_position);
}
xcb_flush(conn);
}
/*
* Renders the resize window between the first/second container and resizes
@ -36,10 +75,10 @@
*/
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 new_position;
i3Screen *screen = get_screen_containing(event->root_x, event->root_y);
uint32_t new_position;
Output *screen = get_output_containing(event->root_x, event->root_y);
if (screen == NULL) {
LOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y);
ELOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y);
return 1;
}
@ -48,12 +87,12 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
* screens during runtime. Instead, we just use the most right and most
* bottom Xinerama screen and use their position + width/height to get
* the area of pixels currently in use */
i3Screen *most_right = get_screen_most(D_RIGHT, screen),
*most_bottom = get_screen_most(D_DOWN, screen);
Output *most_right = get_output_most(D_RIGHT, screen),
*most_bottom = get_output_most(D_DOWN, screen);
LOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x);
DLOG("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);
DLOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
uint32_t mask = 0;
uint32_t values[2];
@ -92,36 +131,16 @@ 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), true, mask, values);
XCB_CURSOR_SB_H_DOUBLE_ARROW :
XCB_CURSOR_SB_V_DOUBLE_ARROW), true, mask, values);
xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin);
xcb_flush(conn);
void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
LOG("new x = %d, y = %d\n", new_x, new_y);
if (orientation == O_VERTICAL) {
/* Check if the new coordinates are within screen boundaries */
if (new_x > (screen->rect.x + screen->rect.width - 25) ||
new_x < (screen->rect.x + 25))
return;
struct callback_params params = { orientation, screen, helpwin, &new_position };
values[0] = new_position = new_x;
xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values);
} else {
if (new_y > (screen->rect.y + screen->rect.height - 25) ||
new_y < (screen->rect.y + 25))
return;
values[0] = new_position = new_y;
xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values);
}
xcb_flush(conn);
}
drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback);
drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback, &params);
xcb_destroy_window(conn, helpwin);
xcb_destroy_window(conn, grabwin);
@ -163,8 +182,29 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco
if (ws->width_factor[second] == 0)
new_unoccupied_x += default_width;
LOG("\n\n\n");
LOG("old = %d, new = %d\n", old_unoccupied_x, new_unoccupied_x);
DLOG("\n\n\n");
DLOG("old = %d, new = %d\n", old_unoccupied_x, new_unoccupied_x);
int cols_without_wf = 0;
int old_width, old_second_width;
for (int col = 0; col < ws->cols; col++)
if (ws->width_factor[col] == 0)
cols_without_wf++;
DLOG("old_unoccupied_x = %d\n", old_unoccupied_x);
DLOG("Updating first (before = %f)\n", ws->width_factor[first]);
/* Convert 0 (for default width_factor) to actual numbers */
if (ws->width_factor[first] == 0)
old_width = (old_unoccupied_x / max(cols_without_wf, 1));
else old_width = ws->width_factor[first] * old_unoccupied_x;
DLOG("second (before = %f)\n", ws->width_factor[second]);
if (ws->width_factor[second] == 0)
old_second_width = (old_unoccupied_x / max(cols_without_wf, 1));
else old_second_width = ws->width_factor[second] * old_unoccupied_x;
DLOG("middle = %f\n", ws->width_factor[first]);
/* If the space used for customly resized columns has changed we need to adapt the
* other customly resized columns, if any */
@ -173,37 +213,33 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco
if (ws->width_factor[col] == 0)
continue;
LOG("Updating other column (%d) (current width_factor = %f)\n", col, ws->width_factor[col]);
DLOG("Updating other column (%d) (current width_factor = %f)\n", col, ws->width_factor[col]);
ws->width_factor[col] = (ws->width_factor[col] * old_unoccupied_x) / new_unoccupied_x;
LOG("to %f\n", ws->width_factor[col]);
DLOG("to %f\n", ws->width_factor[col]);
}
LOG("old_unoccupied_x = %d\n", old_unoccupied_x);
LOG("Updating first (before = %f)\n", ws->width_factor[first]);
DLOG("Updating first (before = %f)\n", ws->width_factor[first]);
/* Convert 0 (for default width_factor) to actual numbers */
if (ws->width_factor[first] == 0)
ws->width_factor[first] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x;
LOG("middle = %f\n", ws->width_factor[first]);
int old_width = ws->width_factor[first] * old_unoccupied_x;
LOG("first->width = %d, pixels = %d\n", pixels);
DLOG("first->width = %d, pixels = %d\n", old_width, pixels);
ws->width_factor[first] *= (float)(old_width + pixels) / old_width;
LOG("-> %f\n", ws->width_factor[first]);
DLOG("-> %f\n", ws->width_factor[first]);
LOG("Updating second (before = %f)\n", ws->width_factor[second]);
DLOG("Updating second (before = %f)\n", ws->width_factor[second]);
if (ws->width_factor[second] == 0)
ws->width_factor[second] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x;
LOG("middle = %f\n", ws->width_factor[second]);
old_width = ws->width_factor[second] * old_unoccupied_x;
LOG("second->width = %d, pixels = %d\n", pixels);
ws->width_factor[second] *= (float)(old_width - pixels) / old_width;
LOG("-> %f\n", ws->width_factor[second]);
LOG("new unoccupied_x = %d\n", get_unoccupied_x(ws));
DLOG("middle = %f\n", ws->width_factor[second]);
DLOG("second->width = %d, pixels = %d\n", old_second_width, pixels);
ws->width_factor[second] *= (float)(old_second_width - pixels) / old_second_width;
DLOG("-> %f\n", ws->width_factor[second]);
LOG("\n\n\n");
DLOG("new unoccupied_x = %d\n", get_unoccupied_x(ws));
DLOG("\n\n\n");
} else {
int ws_height = workspace_height(ws);
int default_height = ws_height / ws->rows;
@ -228,24 +264,25 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco
if (ws->height_factor[row] == 0)
cols_without_hf++;
LOG("old_unoccupied_y = %d\n", old_unoccupied_y);
DLOG("old_unoccupied_y = %d\n", old_unoccupied_y);
DLOG("Updating first (before = %f)\n", ws->height_factor[first]);
LOG("Updating first (before = %f)\n", ws->height_factor[first]);
/* Convert 0 (for default width_factor) to actual numbers */
if (ws->height_factor[first] == 0)
old_height = (old_unoccupied_y / max(cols_without_hf, 1));
else old_height = ws->height_factor[first] * old_unoccupied_y;
LOG("second (before = %f)\n", ws->height_factor[second]);
DLOG("second (before = %f)\n", ws->height_factor[second]);
if (ws->height_factor[second] == 0)
old_second_height = (old_unoccupied_y / max(cols_without_hf, 1));
else old_second_height = ws->height_factor[second] * old_unoccupied_y;
LOG("middle = %f\n", ws->height_factor[first]);
DLOG("middle = %f\n", ws->height_factor[first]);
LOG("\n\n\n");
LOG("old = %d, new = %d\n", old_unoccupied_y, new_unoccupied_y);
DLOG("\n\n\n");
DLOG("old = %d, new = %d\n", old_unoccupied_y, new_unoccupied_y);
/* If the space used for customly resized columns has changed we need to adapt the
* other customly resized columns, if any */
@ -254,33 +291,33 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco
if (ws->height_factor[row] == 0)
continue;
LOG("Updating other column (%d) (current width_factor = %f)\n", row, ws->height_factor[row]);
DLOG("Updating other column (%d) (current width_factor = %f)\n", row, ws->height_factor[row]);
ws->height_factor[row] = (ws->height_factor[row] * old_unoccupied_y) / new_unoccupied_y;
LOG("to %f\n", ws->height_factor[row]);
DLOG("to %f\n", ws->height_factor[row]);
}
LOG("Updating first (before = %f)\n", ws->height_factor[first]);
DLOG("Updating first (before = %f)\n", ws->height_factor[first]);
/* Convert 0 (for default width_factor) to actual numbers */
if (ws->height_factor[first] == 0)
ws->height_factor[first] = ((float)ws_height / ws->rows) / new_unoccupied_y;
LOG("first->width = %d, pixels = %d\n", old_height, pixels);
DLOG("first->width = %d, pixels = %d\n", old_height, pixels);
ws->height_factor[first] *= (float)(old_height + pixels) / old_height;
LOG("-> %f\n", ws->height_factor[first]);
DLOG("-> %f\n", ws->height_factor[first]);
LOG("Updating second (before = %f)\n", ws->height_factor[second]);
DLOG("Updating second (before = %f)\n", ws->height_factor[second]);
if (ws->height_factor[second] == 0)
ws->height_factor[second] = ((float)ws_height / ws->rows) / new_unoccupied_y;
LOG("middle = %f\n", ws->height_factor[second]);
LOG("second->width = %d, pixels = %d\n", old_second_height, pixels);
DLOG("middle = %f\n", ws->height_factor[second]);
DLOG("second->width = %d, pixels = %d\n", old_second_height, pixels);
ws->height_factor[second] *= (float)(old_second_height - pixels) / old_second_height;
LOG("-> %f\n", ws->height_factor[second]);
DLOG("-> %f\n", ws->height_factor[second]);
LOG("new unoccupied_y = %d\n", get_unoccupied_y(ws));
DLOG("new unoccupied_y = %d\n", get_unoccupied_y(ws));
LOG("\n\n\n");
DLOG("\n\n\n");
}
render_layout(conn);

224
src/sighandler.c Normal file
View File

@ -0,0 +1,224 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2010 Michael Stapelberg and contributors
* © 2009-2010 Jan-Erik Rediger
*
* See file LICENSE for license information.
*
* sighandler.c: contains all functions for signal handling
*
*/
#include <ev.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <iconv.h>
#include <signal.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_event.h>
#include <xcb/xcb_keysyms.h>
#include <X11/keysym.h>
#include "i3.h"
#include "util.h"
#include "xcb.h"
#include "log.h"
#include "config.h"
#include "randr.h"
static xcb_gcontext_t pixmap_gc;
static xcb_pixmap_t pixmap;
static int raised_signal;
static char *crash_text[] = {
"i3 just crashed.",
"To debug this problem, either attach gdb now",
"or press 'e' to exit and get a core-dump.",
"If you want to keep your session,",
"press 'r' to restart i3 in-place."
};
static int crash_text_longest = 1;
/*
* Draw the window containing the info text
*
*/
static int sig_draw_window(xcb_connection_t *conn, xcb_window_t win, int width, int height, int font_height) {
/* re-draw the background */
xcb_rectangle_t border = { 0, 0, width, height},
inner = { 2, 2, width - 4, height - 4};
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
/* restore font color */
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
for (int i = 0; i < sizeof(crash_text) / sizeof(char*); i++) {
int text_len = strlen(crash_text[i]);
char *full_text = convert_utf8_to_ucs2(crash_text[i], &text_len);
xcb_image_text_16(conn, text_len, pixmap, pixmap_gc, 8 /* X */,
3 + (i + 1) * font_height /* Y = baseline of font */,
(xcb_char2b_t*)full_text);
free(full_text);
}
/* Copy the contents of the pixmap to the real window */
xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, width, height);
xcb_flush(conn);
return 1;
}
/*
* Handles keypresses of 'e' or 'r' to exit or restart i3
*
*/
static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
uint16_t state = event->state;
/* Apparantly, after activating numlock once, the numlock modifier
* stays turned on (use xev(1) to verify). So, to resolve useful
* keysyms, we remove the numlock flag from the event state */
state &= ~xcb_numlock_mask;
xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, state);
if (sym == 'e') {
DLOG("User issued exit-command, raising error again.\n");
raise(raised_signal);
exit(1);
}
if (sym == 'r')
i3_restart();
return 1;
}
/*
* Opens the window we use for input/output and maps it
*
*/
static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, uint32_t width, uint32_t height) {
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
xcb_window_t win = xcb_generate_id(conn);
uint32_t mask = 0;
uint32_t values[2];
mask |= XCB_CW_BACK_PIXEL;
values[0] = 0;
mask |= XCB_CW_OVERRIDE_REDIRECT;
values[1] = 1;
/* center each popup on the specified screen */
uint32_t x = screen_rect.x + ((screen_rect.width / 2) - (width / 2)),
y = screen_rect.y + ((screen_rect.height / 2) - (height / 2));
xcb_create_window(conn,
XCB_COPY_FROM_PARENT,
win, /* the window id */
root, /* parent == root */
x, y, width, height, /* dimensions */
0, /* border = 0, we draw our own */
XCB_WINDOW_CLASS_INPUT_OUTPUT,
XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
mask,
values);
/* Map the window (= make it visible) */
xcb_map_window(conn, win);
return win;
}
/*
* Handle signals
* It creates a window asking the user to restart in-place
* or exit to generate a core dump
*
*/
void handle_signal(int sig, siginfo_t *info, void *data) {
DLOG("i3 crashed. SIG: %d\n", sig);
struct sigaction action;
action.sa_handler = SIG_DFL;
sigaction(sig, &action, NULL);
raised_signal = sig;
xcb_connection_t *conn = global_conn;
/* setup event handler for key presses */
xcb_event_handlers_t sig_evenths;
memset(&sig_evenths, 0, sizeof(xcb_event_handlers_t));
xcb_event_handlers_init(conn, &sig_evenths);
xcb_event_set_key_press_handler(&sig_evenths, sig_handle_key_press, NULL);
i3Font *font = load_font(conn, config.font);
/* width and height of the popup window, so that the text fits in */
int crash_text_num = sizeof(crash_text) / sizeof(char*);
int height = 13 + (crash_text_num * font->height);
/* calculate width for longest text */
int text_len = strlen(crash_text[crash_text_longest]);
char *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len);
int font_width = predict_text_width(conn, config.font, longest_text, text_len);
int width = font_width + 20;
/* Open a popup window on each virtual screen */
Output *screen;
xcb_window_t win;
TAILQ_FOREACH(screen, &outputs, outputs) {
if (!screen->active)
continue;
win = open_input_window(conn, screen->rect, width, height);
/* Create pixmap */
pixmap = xcb_generate_id(conn);
pixmap_gc = xcb_generate_id(conn);
xcb_create_pixmap(conn, root_depth, pixmap, win, width, height);
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
/* Create graphics context */
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font->id);
/* Grab the keyboard to get all input */
xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
/* Grab the cursor inside the popup */
xcb_grab_pointer(conn, false, win, XCB_NONE, XCB_GRAB_MODE_ASYNC,
XCB_GRAB_MODE_ASYNC, win, XCB_NONE, XCB_CURRENT_TIME);
sig_draw_window(conn, win, width, height, font->height);
xcb_flush(conn);
}
xcb_event_wait_for_event_loop(&sig_evenths);
}
/*
* Setup signal handlers to safely handle SIGSEGV and SIGFPE
*
*/
void setup_signal_handler() {
struct sigaction action;
action.sa_sigaction = handle_signal;
action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
sigemptyset(&action.sa_mask);
if (sigaction(SIGSEGV, &action, NULL) == -1 ||
sigaction(SIGFPE, &action, NULL) == -1)
ELOG("Could not setup signal handler");
}

View File

@ -27,6 +27,7 @@
#include "layout.h"
#include "config.h"
#include "workspace.h"
#include "log.h"
int current_workspace = 0;
int num_workspaces = 1;
@ -52,7 +53,7 @@ void init_table() {
static void new_container(Workspace *workspace, Container **container, int col, int row, bool skip_layout_switch) {
Container *new;
new = *container = calloc(sizeof(Container), 1);
new = *container = scalloc(sizeof(Container));
CIRCLEQ_INIT(&(new->clients));
new->colspan = 1;
new->rowspan = 1;
@ -96,9 +97,9 @@ void expand_table_rows_at_head(Workspace *workspace) {
workspace->height_factor = realloc(workspace->height_factor, sizeof(float) * workspace->rows);
LOG("rows = %d\n", workspace->rows);
DLOG("rows = %d\n", workspace->rows);
for (int rows = (workspace->rows - 1); rows >= 1; rows--) {
LOG("Moving height_factor %d (%f) to %d\n", rows-1, workspace->height_factor[rows-1], rows);
DLOG("Moving height_factor %d (%f) to %d\n", rows-1, workspace->height_factor[rows-1], rows);
workspace->height_factor[rows] = workspace->height_factor[rows-1];
}
@ -110,7 +111,7 @@ void expand_table_rows_at_head(Workspace *workspace) {
/* Move the other rows */
for (int cols = 0; cols < workspace->cols; cols++)
for (int rows = workspace->rows - 1; rows > 0; rows--) {
LOG("Moving row %d to %d\n", rows-1, rows);
DLOG("Moving row %d to %d\n", rows-1, rows);
workspace->table[cols][rows] = workspace->table[cols][rows-1];
workspace->table[cols][rows]->row = rows;
}
@ -130,7 +131,7 @@ void expand_table_cols(Workspace *workspace) {
workspace->width_factor[workspace->cols-1] = 0;
workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols);
workspace->table[workspace->cols-1] = calloc(sizeof(Container*) * workspace->rows, 1);
workspace->table[workspace->cols-1] = scalloc(sizeof(Container*) * workspace->rows);
for (int c = 0; c < workspace->rows; c++)
new_container(workspace, &(workspace->table[workspace->cols-1][c]), workspace->cols-1, c, true);
@ -148,21 +149,21 @@ void expand_table_cols_at_head(Workspace *workspace) {
workspace->width_factor = realloc(workspace->width_factor, sizeof(float) * workspace->cols);
LOG("cols = %d\n", workspace->cols);
DLOG("cols = %d\n", workspace->cols);
for (int cols = (workspace->cols - 1); cols >= 1; cols--) {
LOG("Moving width_factor %d (%f) to %d\n", cols-1, workspace->width_factor[cols-1], cols);
DLOG("Moving width_factor %d (%f) to %d\n", cols-1, workspace->width_factor[cols-1], cols);
workspace->width_factor[cols] = workspace->width_factor[cols-1];
}
workspace->width_factor[0] = 0;
workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols);
workspace->table[workspace->cols-1] = calloc(sizeof(Container*) * workspace->rows, 1);
workspace->table[workspace->cols-1] = scalloc(sizeof(Container*) * workspace->rows);
/* Move the other columns */
for (int rows = 0; rows < workspace->rows; rows++)
for (int cols = workspace->cols - 1; cols > 0; cols--) {
LOG("Moving col %d to %d\n", cols-1, cols);
DLOG("Moving col %d to %d\n", cols-1, cols);
workspace->table[cols][rows] = workspace->table[cols-1][rows];
workspace->table[cols][rows]->col = cols;
}
@ -201,7 +202,7 @@ static void shrink_table_cols(Workspace *workspace) {
if (workspace->width_factor[cols] == 0)
continue;
LOG("Added free space (%f) to %d (had %f)\n", free_space, cols,
DLOG("Added free space (%f) to %d (had %f)\n", free_space, cols,
workspace->width_factor[cols]);
workspace->width_factor[cols] += free_space;
break;
@ -230,7 +231,7 @@ static void shrink_table_rows(Workspace *workspace) {
if (workspace->height_factor[rows] == 0)
continue;
LOG("Added free space (%f) to %d (had %f)\n", free_space, rows,
DLOG("Added free space (%f) to %d (had %f)\n", free_space, rows,
workspace->height_factor[rows]);
workspace->height_factor[rows] += free_space;
break;
@ -256,7 +257,7 @@ static void free_container(xcb_connection_t *conn, Workspace *workspace, int col
}
static void move_columns_from(xcb_connection_t *conn, Workspace *workspace, int cols) {
LOG("firstly freeing \n");
DLOG("firstly freeing \n");
/* Free the columns which are cleaned up */
for (int rows = 0; rows < workspace->rows; rows++)
@ -264,10 +265,10 @@ static void move_columns_from(xcb_connection_t *conn, Workspace *workspace, int
for (; cols < workspace->cols; cols++)
for (int rows = 0; rows < workspace->rows; rows++) {
LOG("at col = %d, row = %d\n", cols, rows);
DLOG("at col = %d, row = %d\n", cols, rows);
Container *new_container = workspace->table[cols][rows];
LOG("moving cols = %d to cols -1 = %d\n", cols, cols-1);
DLOG("moving cols = %d to cols -1 = %d\n", cols, cols-1);
workspace->table[cols-1][rows] = new_container;
new_container->row = rows;
@ -283,7 +284,7 @@ static void move_rows_from(xcb_connection_t *conn, Workspace *workspace, int row
for (int cols = 0; cols < workspace->cols; cols++) {
Container *new_container = workspace->table[cols][rows];
LOG("moving rows = %d to rows -1 = %d\n", rows, rows - 1);
DLOG("moving rows = %d to rows -1 = %d\n", rows, rows - 1);
workspace->table[cols][rows-1] = new_container;
new_container->row = rows-1;
@ -296,19 +297,19 @@ static void move_rows_from(xcb_connection_t *conn, Workspace *workspace, int row
*
*/
void dump_table(xcb_connection_t *conn, Workspace *workspace) {
LOG("dump_table()\n");
DLOG("dump_table()\n");
FOR_TABLE(workspace) {
Container *con = workspace->table[cols][rows];
LOG("----\n");
LOG("at col=%d, row=%d\n", cols, rows);
LOG("currently_focused = %p\n", con->currently_focused);
DLOG("----\n");
DLOG("at col=%d, row=%d\n", cols, rows);
DLOG("currently_focused = %p\n", con->currently_focused);
Client *loop;
CIRCLEQ_FOREACH(loop, &(con->clients), clients) {
LOG("got client %08x / %s\n", loop->child, loop->name);
DLOG("got client %08x / %s\n", loop->child, loop->name);
}
LOG("----\n");
DLOG("----\n");
}
LOG("done\n");
DLOG("done\n");
}
/*
@ -316,7 +317,7 @@ void dump_table(xcb_connection_t *conn, Workspace *workspace) {
*
*/
void cleanup_table(xcb_connection_t *conn, Workspace *workspace) {
LOG("cleanup_table()\n");
DLOG("cleanup_table()\n");
/* Check for empty columns if we got more than one column */
for (int cols = 0; (workspace->cols > 1) && (cols < workspace->cols);) {
@ -327,7 +328,7 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) {
break;
}
if (completely_empty) {
LOG("Removing completely empty column %d\n", cols);
DLOG("Removing completely empty column %d\n", cols);
if (cols < (workspace->cols - 1))
move_columns_from(conn, workspace, cols+1);
else {
@ -344,14 +345,14 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) {
/* Check for empty rows if we got more than one row */
for (int rows = 0; (workspace->rows > 1) && (rows < workspace->rows);) {
bool completely_empty = true;
LOG("Checking row %d\n", rows);
DLOG("Checking row %d\n", rows);
for (int cols = 0; cols < workspace->cols; cols++)
if (workspace->table[cols][rows]->currently_focused != NULL) {
completely_empty = false;
break;
}
if (completely_empty) {
LOG("Removing completely empty row %d\n", rows);
DLOG("Removing completely empty row %d\n", rows);
if (rows < (workspace->rows - 1))
move_rows_from(conn, workspace, rows+1);
else {
@ -381,25 +382,25 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) {
*
*/
void fix_colrowspan(xcb_connection_t *conn, Workspace *workspace) {
LOG("Fixing col/rowspan\n");
DLOG("Fixing col/rowspan\n");
FOR_TABLE(workspace) {
Container *con = workspace->table[cols][rows];
if (con->colspan > 1) {
LOG("gots one with colspan %d (at %d c, %d r)\n", con->colspan, cols, rows);
DLOG("gots one with colspan %d (at %d c, %d r)\n", con->colspan, cols, rows);
while (con->colspan > 1 &&
(!cell_exists(workspace, cols + (con->colspan-1), rows) &&
workspace->table[cols + (con->colspan - 1)][rows]->currently_focused != NULL))
con->colspan--;
LOG("fixed it to %d\n", con->colspan);
DLOG("fixed it to %d\n", con->colspan);
}
if (con->rowspan > 1) {
LOG("gots one with rowspan %d (at %d c, %d r)\n", con->rowspan, cols, rows);
DLOG("gots one with rowspan %d (at %d c, %d r)\n", con->rowspan, cols, rows);
while (con->rowspan > 1 &&
(!cell_exists(workspace, cols, rows + (con->rowspan - 1)) &&
workspace->table[cols][rows + (con->rowspan - 1)]->currently_focused != NULL))
con->rowspan--;
LOG("fixed it to %d\n", con->rowspan);
DLOG("fixed it to %d\n", con->rowspan);
}
}
}

View File

@ -31,6 +31,11 @@
#include "util.h"
#include "xcb.h"
#include "client.h"
#include "log.h"
#include "ewmh.h"
#include "manage.h"
#include "workspace.h"
#include "ipc.h"
static iconv_t conversion_descriptor = 0;
struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent);
@ -45,24 +50,14 @@ int max(int a, int b) {
}
/*
* Logs the given message to stdout while prefixing the current time to it.
* This is to be called by LOG() which includes filename/linenumber
* Updates *destination with new_value and returns true if it was changed or false
* if it was the same
*
*/
void slog(char *fmt, ...) {
va_list args;
char timebuf[64];
bool update_if_necessary(uint32_t *destination, const uint32_t new_value) {
uint32_t old_value = *destination;
va_start(args, fmt);
/* Get current time */
time_t t = time(NULL);
/* Convert time to local time (determined by the locale) */
struct tm *tmp = localtime(&t);
/* Generate time prefix */
strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp);
printf("%s", timebuf);
vprintf(fmt, args);
va_end(args);
return ((*destination = new_value) != old_value);
}
/*
@ -148,7 +143,7 @@ void start_application(const char *command) {
shell = "/bin/sh";
/* This is the child */
execl(shell, shell, "-c", command, NULL);
execl(shell, shell, "-c", command, (void*)NULL);
/* not reached */
}
exit(0);
@ -164,7 +159,7 @@ void start_application(const char *command) {
void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_message) {
xcb_generic_error_t *error = xcb_request_check(conn, cookie);
if (error != NULL) {
fprintf(stderr, "ERROR: %s : %d\n", err_message , error->error_code);
fprintf(stderr, "ERROR: %s (X error %d)\n", err_message , error->error_code);
xcb_disconnect(conn);
exit(-1);
}
@ -178,16 +173,16 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes
*
*/
char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
size_t input_size = strlen(input) + 1;
/* UCS-2 consumes exactly two bytes for each glyph */
int buffer_size = input_size * 2;
size_t input_size = strlen(input) + 1;
/* UCS-2 consumes exactly two bytes for each glyph */
int buffer_size = input_size * 2;
char *buffer = smalloc(buffer_size);
size_t output_size = buffer_size;
/* We need to use an additional pointer, because iconv() modifies it */
char *output = buffer;
char *buffer = smalloc(buffer_size);
size_t output_size = buffer_size;
/* We need to use an additional pointer, because iconv() modifies it */
char *output = buffer;
/* We convert the input into UCS-2 big endian */
/* We convert the input into UCS-2 big endian */
if (conversion_descriptor == 0) {
conversion_descriptor = iconv_open("UCS-2BE", "UTF-8");
if (conversion_descriptor == 0) {
@ -196,22 +191,22 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
}
}
/* Get the conversion descriptor back to original state */
iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
/* Get the conversion descriptor back to original state */
iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
/* Convert our text */
int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
/* Convert our text */
int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
if (rc == (size_t)-1) {
perror("Converting to UCS-2 failed");
if (real_strlen != NULL)
*real_strlen = 0;
*real_strlen = 0;
return NULL;
}
}
if (real_strlen != NULL)
*real_strlen = ((buffer_size - output_size) / 2) - 1;
*real_strlen = ((buffer_size - output_size) / 2) - 1;
return buffer;
return buffer;
}
/*
@ -250,6 +245,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
c_ws->current_row = current_row;
c_ws->current_col = current_col;
c_ws = client->workspace;
ewmh_update_current_desktop();
/* Load current_col/current_row if we switch to a client without a container */
current_col = c_ws->current_col;
current_row = c_ws->current_row;
@ -265,6 +261,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
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);
ewmh_update_active_window(client->child);
//xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10);
if (client->container != NULL) {
@ -280,7 +277,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
Client *last_focused = get_last_focused_client(conn, client->container, client);
if (last_focused != NULL) {
LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child);
DLOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child);
uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE };
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
}
@ -294,22 +291,27 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
/* If the last client was a floating client, we need to go to the next
* tiling client in stack and re-decorate it. */
if (old_client != NULL && client_is_floating(old_client)) {
LOG("Coming from floating client, searching next tiling...\n");
DLOG("Coming from floating client, searching next tiling...\n");
Client *current;
SLIST_FOREACH(current, &(client->workspace->focus_stack), focus_clients) {
if (client_is_floating(current))
continue;
LOG("Found window: %p / child %p\n", current->frame, current->child);
DLOG("Found window: %p / child %p\n", current->frame, current->child);
redecorate_window(conn, current);
break;
}
}
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
/* Clear the urgency flag if set (necessary when i3 sets the flag, for
* example when automatically putting windows on the workspace of their
* leader) */
client->urgent = false;
workspace_update_urgent_flag(client->workspace);
/* If were in stacking mode, this renders the container to update changes in the title
bars and to raise the focused client */
if ((old_client != NULL) && (old_client != client) && !old_client->dock)
@ -411,14 +413,14 @@ after_stackwin:
if (client == container->currently_focused || client == last_focused)
continue;
LOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame);
DLOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame);
uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, client->frame,
XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
}
if (last_focused != NULL) {
LOG("Putting last_focused directly underneath the currently focused\n");
DLOG("Putting last_focused directly underneath the currently focused\n");
uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, last_focused->frame,
XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
@ -457,15 +459,15 @@ Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitl
goto done;
}
LOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title);
DLOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title);
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen == NULL)
if (ws->output == NULL)
continue;
Client *client;
SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) {
LOG("Checking client with class=%s / %s, name=%s\n", client->window_class_instance,
DLOG("Checking client with class=%s / %s, name=%s\n", client->window_class_instance,
client->window_class_class, client->name);
if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len))
continue;
@ -481,6 +483,47 @@ done:
return matching;
}
/*
* Goes through the list of arguments (for exec()) and checks if the given argument
* is present. If not, it copies the arguments (because we cannot realloc it) and
* appends the given argument.
*
*/
static char **append_argument(char **original, char *argument) {
int num_args;
for (num_args = 0; original[num_args] != NULL; num_args++) {
DLOG("original argument: \"%s\"\n", original[num_args]);
/* If the argument is already present we return the original pointer */
if (strcmp(original[num_args], argument) == 0)
return original;
}
/* Copy the original array */
char **result = smalloc((num_args+2) * sizeof(char*));
memcpy(result, original, num_args * sizeof(char*));
result[num_args] = argument;
result[num_args+1] = NULL;
return result;
}
/*
* Restart i3 in-place
* appends -a to argument list to disable autostart
*
*/
void i3_restart() {
restore_geometry(global_conn);
ipc_shutdown();
LOG("restarting \"%s\"...\n", start_argv[0]);
/* make sure -a is in the argument list or append it */
start_argv = append_argument(start_argv, "-a");
execvp(start_argv[0], start_argv);
/* not reached */
}
#if defined(__OpenBSD__)
/*

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -22,10 +22,13 @@
#include "config.h"
#include "xcb.h"
#include "table.h"
#include "xinerama.h"
#include "randr.h"
#include "layout.h"
#include "workspace.h"
#include "client.h"
#include "log.h"
#include "ewmh.h"
#include "ipc.h"
/*
* Returns a pointer to the workspace with the given number (starting at 0),
@ -42,10 +45,10 @@ Workspace *workspace_get(int number) {
/* If we are still there, we could not find the requested workspace. */
int last_ws = TAILQ_LAST(workspaces, workspaces_head)->num;
LOG("We need to initialize that one, last ws = %d\n", last_ws);
DLOG("We need to initialize that one, last ws = %d\n", last_ws);
for (int c = last_ws; c < number; c++) {
LOG("Creating new ws\n");
DLOG("Creating new ws\n");
ws = scalloc(sizeof(Workspace));
ws->num = c+1;
@ -55,8 +58,12 @@ Workspace *workspace_get(int number) {
workspace_set_name(ws, NULL);
TAILQ_INSERT_TAIL(workspaces, ws, workspaces);
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
}
LOG("done\n");
DLOG("done\n");
ewmh_update_workarea();
return ws;
}
@ -80,13 +87,13 @@ void workspace_set_name(Workspace *ws, const char *name) {
errx(1, "asprintf() failed");
FREE(ws->name);
FREE(ws->utf8_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);
ws->utf8_name = label;
}
/*
@ -96,7 +103,7 @@ void workspace_set_name(Workspace *ws, const char *name) {
*
*/
bool workspace_is_visible(Workspace *ws) {
return (ws->screen->current_workspace == ws);
return (ws->output != NULL && ws->output->current_workspace == ws);
}
/*
@ -109,29 +116,29 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
/* t_ws (to workspace) is just a convenience pointer to the workspace were switching to */
Workspace *t_ws = workspace_get(workspace-1);
LOG("show_workspace(%d)\n", workspace);
DLOG("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);
workspace_initialize(t_ws, c_ws->output, false);
if (c_ws->screen != t_ws->screen) {
/* We need to switch to the other screen first */
LOG("moving over to other screen.\n");
if (c_ws->output != t_ws->output) {
/* We need to switch to the other output first */
DLOG("moving over to other output.\n");
/* Store the old client */
Client *old_client = CUR_CELL->currently_focused;
c_ws = t_ws->screen->current_workspace;
c_ws = t_ws->output->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);
Rect *dims = &(c_ws->output->rect);
xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0,
dims->x + (dims->width / 2), dims->y + (dims->height / 2));
}
@ -140,10 +147,19 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
if ((old_client != NULL) && !old_client->dock)
redecorate_window(conn, old_client);
else xcb_flush(conn);
/* We need to check if a global fullscreen-client is blocking
* the t_ws and if necessary switch that to local fullscreen */
Client* client = c_ws->fullscreen_client;
if (client != NULL && client->workspace != c_ws) {
if (c_ws->fullscreen_client->workspace != c_ws)
c_ws->fullscreen_client = NULL;
client_enter_fullscreen(conn, client, false);
}
}
/* Check if we need to change something or if were already there */
if (c_ws->screen->current_workspace->num == (workspace-1)) {
if (c_ws->output->current_workspace->num == (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);
@ -152,24 +168,28 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
xcb_flush(conn);
}
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
return;
}
Workspace *old_workspace = c_ws;
c_ws = t_ws->screen->current_workspace = workspace_get(workspace-1);
c_ws = t_ws->output->current_workspace = workspace_get(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);
DLOG("new current row = %d, current col = %d\n", current_row, current_col);
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
workspace_map_clients(conn, c_ws);
/* POTENTIAL TO IMPROVE HERE: due to the call to _map_clients first and
* render_layout afterwards, there is a short flickering on the source
* workspace (assign ws 3 to screen 0, ws 4 to screen 1, create single
* workspace (assign ws 3 to output 0, ws 4 to output 1, create single
* client on ws 4, move it to ws 3, switch to ws 3, youll see the
* flickering). */
@ -184,63 +204,61 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
/* We can warp the pointer only after the window has been
* reconfigured in render_layout, otherwise the pointer will
* be warped to the old position, which will not work when we
* moved it to another screen. */
* moved it to another output. */
if (last_focused != SLIST_END(&(c_ws->focus_stack)) && need_warp) {
client_warp_pointer_into(conn, last_focused);
xcb_flush(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").
* Assigns the given workspace to the given output by correctly updating its
* state and reconfiguring all the clients on this workspace.
*
* This is called when initializing a output and when re-assigning it to a
* different output which just got available (if you configured it to be on
* output 1 and you just plugged in output 1).
*
*/
static i3Screen *get_screen_from_preference(struct screens_head *slist, char *preference) {
i3Screen *screen;
char *rest;
int preferred_screen = strtol(preference, &rest, 10);
void workspace_assign_to(Workspace *ws, Output *output, bool hide_it) {
Client *client;
bool empty = true;
bool visible = workspace_is_visible(ws);
LOG("Getting screen for preference \"%s\" (%d)\n", preference, preferred_screen);
ws->output = output;
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);
}
/* Copy the dimensions from the virtual output */
memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect));
LOG("Looking for screen at %d x %d\n", x, y);
ewmh_update_workarea();
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;
/* Force reconfiguration for each client on that workspace */
SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) {
client->force_reconfigure = true;
empty = false;
}
return NULL;
if (empty)
return;
/* Render the workspace to reconfigure the clients. However, they will be visible now, so… */
render_workspace(global_conn, output, ws);
/* …unless we want to see them at the moment, we should hide that workspace */
if (visible && !hide_it)
return;
/* however, if this is the current workspace, we only need to adjust
* the outputs current_workspace pointer (and must not unmap the
* windows) */
if (c_ws == ws) {
DLOG("Need to adjust output->current_workspace...\n");
output->current_workspace = c_ws;
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
return;
}
workspace_unmap_clients(global_conn, ws);
}
/*
@ -250,35 +268,43 @@ static i3Screen *get_screen_from_preference(struct screens_head *slist, char *pr
* the screen is not attached at the moment.
*
*/
void workspace_initialize(Workspace *ws, i3Screen *screen) {
if (ws->screen != NULL) {
LOG("Workspace already initialized\n");
void workspace_initialize(Workspace *ws, Output *output, bool recheck) {
Output *old_output;
if (ws->output != NULL && !recheck) {
DLOG("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;
old_output = ws->output;
/* Copy the dimensions from the virtual screen */
memcpy(&(ws->rect), &(ws->screen->rect), sizeof(Rect));
/* If this workspace has no preferred output or if the output it wants
* to be on is not available at the moment, we initialize it with
* the output which was given */
if (ws->preferred_output == NULL ||
(ws->output = get_output_by_name(ws->preferred_output)) == NULL)
ws->output = output;
DLOG("old_output = %p, ws->output = %p\n", old_output, ws->output);
/* If the assignment did not change, we do not need to update anything */
if (old_output != NULL && ws->output == old_output)
return;
workspace_assign_to(ws, ws->output, false);
}
/*
* Gets the first unused workspace for the given screen, taking into account
* the preferred_screen setting of every workspace (workspace assignments).
* the preferred_output setting of every workspace (workspace assignments).
*
*/
Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *screen) {
Workspace *get_first_workspace_for_output(Output *output) {
Workspace *result = NULL;
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->preferred_screen == NULL ||
!screens_are_equal(get_screen_from_preference(slist, ws->preferred_screen), screen))
if (ws->preferred_output == NULL ||
get_output_by_name(ws->preferred_output) != output)
continue;
result = ws;
@ -287,9 +313,8 @@ Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *
if (result == NULL) {
/* No assignment found, returning first unused workspace */
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen != NULL)
if (ws->output != NULL)
continue;
result = ws;
@ -298,16 +323,15 @@ Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *
}
if (result == NULL) {
LOG("No existing free workspace found to assign, creating a new one\n");
DLOG("No existing free workspace found to assign, creating a new one\n");
Workspace *ws;
int last_ws = 0;
TAILQ_FOREACH(ws, workspaces, workspaces)
last_ws = ws->num;
result = workspace_get(last_ws + 1);
}
workspace_initialize(result, screen);
workspace_initialize(result, output, false);
return result;
}
@ -359,7 +383,7 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
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);
DLOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child);
client_unmap(conn, client);
unmapped_clients++;
}
@ -369,7 +393,7 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
if (!client_is_floating(client))
continue;
LOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child);
DLOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child);
client_unmap(conn, client);
unmapped_clients++;
@ -380,15 +404,15 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
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) {
DLOG("workspace %p is empty\n", u_ws);
SLIST_FOREACH(dock, &(u_ws->output->dock_clients), dock_clients) {
if (dock->workspace != u_ws)
continue;
LOG("Re-assigning dock client to c_ws (%p)\n", c_ws);
DLOG("Re-assigning dock client to c_ws (%p)\n", c_ws);
dock->workspace = c_ws;
}
u_ws->screen = NULL;
u_ws->output = NULL;
}
/* Unmap the stack windows on the given workspace, if any */
@ -406,16 +430,50 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
*/
void workspace_update_urgent_flag(Workspace *ws) {
Client *current;
bool old_flag = ws->urgent;
bool urgent = false;
SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) {
if (!current->urgent)
continue;
ws->urgent = true;
return;
urgent = true;
break;
}
ws->urgent = false;
ws->urgent = urgent;
if (old_flag != urgent)
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}");
}
/*
* Returns the width of the workspace.
*
*/
int workspace_width(Workspace *ws) {
return ws->rect.width;
}
/*
* Returns the effective height of the workspace (without the internal bar and
* without dock clients).
*
*/
int workspace_height(Workspace *ws) {
int height = ws->rect.height;
i3Font *font = load_font(global_conn, config.font);
/* Reserve space for dock clients */
Client *client;
SLIST_FOREACH(client, &(ws->output->dock_clients), dock_clients)
height -= client->desired_height;
/* Space for the internal bar */
if (!config.disable_workspace_bar)
height -= (font->height + 6);
return height;
}
/*

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -21,6 +21,7 @@
#include "i3.h"
#include "util.h"
#include "xcb.h"
#include "log.h"
TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts);
unsigned int xcb_numlock_mask;
@ -98,14 +99,6 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl
/* If the window class is XCB_WINDOW_CLASS_INPUT_ONLY, depth has to be 0 */
uint16_t depth = (window_class == XCB_WINDOW_CLASS_INPUT_ONLY ? 0 : XCB_COPY_FROM_PARENT);
/* Use the default cursor (left pointer) */
if (cursor > -1) {
i3Font *cursor_font = load_font(conn, "cursor");
xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1,
0, 0, 0, 65535, 65535, 65535);
}
xcb_create_window(conn,
depth,
result, /* the window id */
@ -117,8 +110,14 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl
mask,
values);
if (cursor > -1)
xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
/* Set the cursor */
i3Font *cursor_font = load_font(conn, "cursor");
xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
(cursor == -1 ? XCB_CURSOR_LEFT_PTR : cursor),
(cursor == -1 ? XCB_CURSOR_LEFT_PTR : cursor) + 1,
0, 0, 0, 65535, 65535, 65535);
xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
xcb_free_cursor(conn, cursor_id);
/* Map the window (= make it visible) */
if (map)
@ -270,7 +269,7 @@ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) {
*
*/
void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) {
LOG("preparing pixmap\n");
DLOG("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)
@ -279,11 +278,11 @@ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap)
memcpy(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect));
if (pixmap->id == 0 || pixmap->gc == 0) {
LOG("Creating new pixmap...\n");
DLOG("Creating new pixmap...\n");
pixmap->id = xcb_generate_id(conn);
pixmap->gc = xcb_generate_id(conn);
} else {
LOG("Re-creating this pixmap...\n");
DLOG("Re-creating this pixmap...\n");
xcb_free_gc(conn, pixmap->gc);
xcb_free_pixmap(conn, pixmap->id);
}
@ -309,7 +308,7 @@ int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *t
cookie = xcb_query_text_extents(conn, font->id, length, (xcb_char2b_t*)text);
if ((reply = xcb_query_text_extents_reply(conn, cookie, &error)) == NULL) {
LOG("Could not get text extents (X error code %d)\n",
ELOG("Could not get text extents (X error code %d)\n",
error->error_code);
/* We return the rather safe guess of 7 pixels, because a
* rendering error is better than a crash. Plus, the user will
@ -321,3 +320,16 @@ int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *t
free(reply);
return width;
}
/*
* Configures the given window to have the size/position specified by given rect
*
*/
void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r) {
xcb_configure_window(conn, window,
XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT,
&(r.x));
}

View File

@ -3,234 +3,96 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* This is LEGACY code (we support RandR, which can do much more than
* Xinerama), but necessary for the poor users of the nVidia binary
* driver which does not support RandR in 2010 *sigh*.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <time.h>
#include <xcb/xcb.h>
#include <xcb/xinerama.h>
#include "queue.h"
#include "i3.h"
#include "data.h"
#include "table.h"
#include "util.h"
#include "xinerama.h"
#include "layout.h"
#include "xcb.h"
#include "config.h"
#include "workspace.h"
#include "log.h"
#include "randr.h"
/* This TAILQ of i3Screens stores the virtual screens, used for handling overlapping screens
* (xrandr --same-as) */
struct screens_head *virtual_screens;
static bool xinerama_enabled = true;
static int num_screens;
/*
* Returns true if both screen objects describe the same screen (checks their
* size and position).
* Looks in outputs for the Output whose start coordinates are x, y
*
*/
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
*
*/
i3Screen *get_screen_at(int x, int y, struct screens_head *screenlist) {
i3Screen *screen;
TAILQ_FOREACH(screen, screenlist, screens)
if (screen->rect.x == x && screen->rect.y == y)
return screen;
static Output *get_screen_at(int x, int y) {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs)
if (output->rect.x == x && output->rect.y == y)
return output;
return NULL;
}
/*
* Looks in virtual_screens for the i3Screen which contains coordinates x, y
*
*/
i3Screen *get_screen_containing(int x, int y) {
i3Screen *screen;
TAILQ_FOREACH(screen, virtual_screens, screens) {
LOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
x, y, screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
if (x >= screen->rect.x && x < (screen->rect.x + screen->rect.width) &&
y >= screen->rect.y && y < (screen->rect.y + screen->rect.height))
return screen;
}
return NULL;
}
/*
* 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 *current) {
i3Screen *screen, *candidate = NULL;
int position = 0;
TAILQ_FOREACH(screen, virtual_screens, screens) {
/* Repeated calls of WIN determine the winner of the comparison */
#define WIN(variable, condition) \
if (variable condition) { \
candidate = screen; \
position = variable; \
} \
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);
case D_DOWN:
WIN(screen->rect.y, >= position);
case D_LEFT:
WIN(screen->rect.x, <= position);
case D_RIGHT:
WIN(screen->rect.x, >= position);
}
}
assert(candidate != NULL);
return candidate;
}
static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspace *workspace) {
i3Font *font = load_font(conn, config.font);
workspace->screen = screen;
screen->current_workspace = workspace;
/* Create a bar for each screen */
Rect bar_rect = {screen->rect.x,
screen->rect.y + screen->rect.height - (font->height + 6),
screen->rect.x + screen->rect.width,
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, true, mask, values);
screen->bargc = xcb_generate_id(conn);
xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0);
SLIST_INIT(&(screen->dock_clients));
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);
}
/*
* Fills virtual_screens with exactly one screen with width/height of the whole X server.
*
*/
static void disable_xinerama(xcb_connection_t *conn) {
xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
i3Screen *s = calloc(sizeof(i3Screen), 1);
s->rect.x = 0;
s->rect.y = 0;
s->rect.width = root_screen->width_in_pixels;
s->rect.height = root_screen->height_in_pixels;
num_screens = 1;
s->num = 0;
TAILQ_INSERT_TAIL(virtual_screens, s, screens);
xinerama_enabled = false;
}
/*
* Gets the Xinerama screens and converts them to virtual i3Screens (only one screen for two
* Gets the Xinerama screens and converts them to virtual Outputs (only one screen for two
* Xinerama screen which are configured in clone mode) in the given screenlist
*
*/
static void query_screens(xcb_connection_t *conn, struct screens_head *screenlist) {
static void query_screens(xcb_connection_t *conn) {
xcb_xinerama_query_screens_reply_t *reply;
xcb_xinerama_screen_info_t *screen_info;
time_t before_trying = time(NULL);
/* Try repeatedly to find screens (there might be short timeframes in
* which the X server does not return any screens, such as when rotating
* screens), but not longer than 5 seconds (strictly speaking, only four
* seconds of trying are guaranteed due to the 1-second-resolution) */
while ((time(NULL) - before_trying) < 5) {
reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL);
if (!reply) {
LOG("Couldn't get Xinerama screens\n");
return;
}
screen_info = xcb_xinerama_query_screens_screen_info(reply);
int screens = xcb_xinerama_query_screens_screen_info_length(reply);
num_screens = 0;
reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL);
if (!reply) {
ELOG("Couldn't get Xinerama screens\n");
return;
}
screen_info = xcb_xinerama_query_screens_screen_info(reply);
int screens = xcb_xinerama_query_screens_screen_info_length(reply);
for (int screen = 0; screen < screens; screen++) {
i3Screen *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org, screenlist);
if (s != NULL) {
/* This screen already exists. We use the littlest screen so that the user
can always see the complete workspace */
s->rect.width = min(s->rect.width, screen_info[screen].width);
s->rect.height = min(s->rect.height, screen_info[screen].height);
} else {
s = calloc(sizeof(i3Screen), 1);
s->rect.x = screen_info[screen].x_org;
s->rect.y = screen_info[screen].y_org;
s->rect.width = screen_info[screen].width;
s->rect.height = screen_info[screen].height;
/* We always treat the screen at 0x0 as the primary screen */
if (s->rect.x == 0 && s->rect.y == 0)
TAILQ_INSERT_HEAD(screenlist, s, screens);
else TAILQ_INSERT_TAIL(screenlist, s, screens);
num_screens++;
}
LOG("found Xinerama screen: %d x %d at %d x %d\n",
screen_info[screen].width, screen_info[screen].height,
screen_info[screen].x_org, screen_info[screen].y_org);
for (int screen = 0; screen < screens; screen++) {
Output *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org);
if (s != NULL) {
DLOG("Re-used old Xinerama screen %p\n", s);
/* This screen already exists. We use the littlest screen so that the user
can always see the complete workspace */
s->rect.width = min(s->rect.width, screen_info[screen].width);
s->rect.height = min(s->rect.height, screen_info[screen].height);
} else {
s = scalloc(sizeof(Output));
asprintf(&(s->name), "xinerama-%d", num_screens);
DLOG("Created new Xinerama screen %s (%p)\n", s->name, s);
s->active = true;
s->rect.x = screen_info[screen].x_org;
s->rect.y = screen_info[screen].y_org;
s->rect.width = screen_info[screen].width;
s->rect.height = screen_info[screen].height;
/* We always treat the screen at 0x0 as the primary screen */
if (s->rect.x == 0 && s->rect.y == 0)
TAILQ_INSERT_HEAD(&outputs, s, outputs);
else TAILQ_INSERT_TAIL(&outputs, s, outputs);
num_screens++;
}
free(reply);
DLOG("found Xinerama screen: %d x %d at %d x %d\n",
screen_info[screen].width, screen_info[screen].height,
screen_info[screen].x_org, screen_info[screen].y_org);
}
if (num_screens == 0) {
LOG("No screens found. This is weird. Trying again...\n");
continue;
}
free(reply);
break;
if (num_screens == 0) {
ELOG("No screens found. Please fix your setup. i3 will exit now.\n");
exit(0);
}
}
@ -240,198 +102,27 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis
*
*/
void initialize_xinerama(xcb_connection_t *conn) {
virtual_screens = scalloc(sizeof(struct screens_head));
TAILQ_INIT(virtual_screens);
if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) {
LOG("Xinerama extension not found, disabling.\n");
disable_xinerama(conn);
DLOG("Xinerama extension not found, disabling.\n");
disable_randr(conn);
} else {
xcb_xinerama_is_active_reply_t *reply;
reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL);
if (reply == NULL || !reply->state) {
LOG("Xinerama is not active (in your X-Server), disabling.\n");
disable_xinerama(conn);
DLOG("Xinerama is not active (in your X-Server), disabling.\n");
disable_randr(conn);
} else
query_screens(conn, virtual_screens);
query_screens(conn);
FREE(reply);
}
i3Screen *screen;
num_screens = 0;
/* Just go through each workspace and associate as many screens as we can. */
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);
}
}
/*
* 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) {
i3Font *font = load_font(conn, config.font);
/* POSSIBLE PROBLEM: Is the order of the Xinerama screens always constant? That is, can
it change when I move the --right-of video projector to --left-of? */
if (!xinerama_enabled) {
LOG("Xinerama is disabled\n");
return;
}
/* We use a separate copy to diff with the previous set of screens */
struct screens_head *new_screens = scalloc(sizeof(struct screens_head));
TAILQ_INIT(new_screens);
query_screens(conn, new_screens);
i3Screen *first = TAILQ_FIRST(new_screens),
*screen,
*old_screen;
int screen_count = 0;
/* Mark each workspace which currently is assigned to a screen, so we
* can garbage-collect afterwards */
Output *output;
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces)
ws->reassigned = (ws->screen == NULL);
TAILQ_FOREACH(screen, new_screens, screens) {
screen->num = screen_count;
screen->current_workspace = NULL;
TAILQ_FOREACH(old_screen, virtual_screens, screens) {
if (old_screen->num != screen_count)
continue;
LOG("Found a matching screen\n");
/* Use the same workspace */
screen->current_workspace = old_screen->current_workspace;
/* 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);
Rect bar_rect = {screen->rect.x,
screen->rect.y + screen->rect.height - (font->height + 6),
screen->rect.x + screen->rect.width,
font->height + 6};
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));
/* Copy the list head for the dock clients */
screen->dock_clients = old_screen->dock_clients;
SLIST_INIT(&(old_screen->dock_clients));
/* Update the dimensions */
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
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 == NULL) {
/* 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);
ws->reassigned = true;
/* 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++;
/* Just go through each active output and associate one workspace */
TAILQ_FOREACH(output, &outputs, outputs) {
ws = get_first_workspace_for_output(output);
initialize_output(conn, output, ws);
}
/* check for dock_clients which are out of bounds */
TAILQ_FOREACH(old_screen, virtual_screens, screens) {
if (SLIST_EMPTY(&(old_screen->dock_clients)))
continue;
LOG("dock_clients out of bounds at screen %p, reassigning\n", old_screen);
if (SLIST_EMPTY(&(first->dock_clients))) {
first->dock_clients = old_screen->dock_clients;
continue;
}
/* We need to merge the lists */
Client *dock_client;
while (!SLIST_EMPTY(&(old_screen->dock_clients))) {
dock_client = SLIST_FIRST(&(old_screen->dock_clients));
SLIST_INSERT_HEAD(&(first->dock_clients), dock_client, dock_clients);
SLIST_REMOVE_HEAD(&(old_screen->dock_clients), dock_clients);
}
}
/* Check for workspaces which are out of bounds */
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->reassigned)
continue;
Client *client;
LOG("Closing bar window (%p)\n", ws->screen->bar);
xcb_destroy_window(conn, ws->screen->bar);
LOG("Workspace %d's screen out of bounds, assigning to first screen\n", ws->num + 1);
ws->screen = first;
memcpy(&(ws->rect), &(first->rect), sizeof(Rect));
/* Force reconfiguration for each client on that workspace */
FOR_TABLE(ws)
CIRCLEQ_FOREACH(client, &(ws->table[cols][rows]->clients), clients)
client->force_reconfigure = true;
/* Render the workspace to reconfigure the clients. However, they will be visible now, so… */
render_workspace(conn, first, ws);
/* …unless we want to see them at the moment, we should hide that workspace */
if (workspace_is_visible(ws))
continue;
workspace_unmap_clients(conn, ws);
if (c_ws == ws) {
LOG("Need to adjust c_ws...\n");
c_ws = first->current_workspace;
}
}
xcb_flush(conn);
/* Free the old list */
while (!TAILQ_EMPTY(virtual_screens)) {
screen = TAILQ_FIRST(virtual_screens);
TAILQ_REMOVE(virtual_screens, screen, screens);
free(screen);
}
free(virtual_screens);
virtual_screens = new_screens;
LOG("Current workspace is now: %d\n", first->current_workspace);
render_layout(conn);
}

View File

@ -1,7 +1,7 @@
#!perl
# vim:ts=4:sw=4:expandtab
use Test::More tests => 4;
use Test::More tests => 3;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
@ -9,6 +9,7 @@ use Time::HiRes qw(sleep);
use FindBin;
use lib "$FindBin::Bin/lib";
use i3test;
use AnyEvent::I3;
BEGIN {
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
@ -17,19 +18,14 @@ BEGIN {
my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
isa_ok($sock, 'IO::Socket::UNIX');
my $i3 = i3;
#####################################################################
# Ensure IPC works by switching workspaces
#####################################################################
# Switch to the first workspace to get a clean testing environment
$sock->write(i3test::format_ipc_command("1"));
sleep(0.25);
$i3->command('1')->recv;
# Create a window so we can get a focus different from NULL
my $window = i3test::open_standard_window($x);
@ -41,9 +37,7 @@ my $focus = $x->input_focus;
diag("old focus = $focus");
# Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9"));
sleep(0.25);
$i3->command('9')->recv;
my $new_focus = $x->input_focus;
isnt($focus, $new_focus, "Focus changed");

View File

@ -4,7 +4,7 @@
# the workspace to be empty).
# TODO: skip it by default?
use Test::More tests => 8;
use Test::More tests => 13;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
@ -12,29 +12,25 @@ use Time::HiRes qw(sleep);
use FindBin;
use lib "$FindBin::Bin/lib";
use i3test;
use AnyEvent::I3;
BEGIN {
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
}
my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
isa_ok($sock, 'IO::Socket::UNIX');
my $i3 = i3;
# Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9"));
sleep(0.25);
$i3->command('9')->recv;
#####################################################################
# Create two windows and make sure focus switching works
#####################################################################
# Change mode of the container to "default" for following tests
$sock->write(i3test::format_ipc_command("d"));
sleep(0.25);
$i3->command('d')->recv;
my $top = i3test::open_standard_window($x);
my $mid = i3test::open_standard_window($x);
@ -52,8 +48,7 @@ diag("bottom id = " . $bottom->id);
sub focus_after {
my $msg = shift;
$sock->write(i3test::format_ipc_command($msg));
sleep(0.5);
$i3->command($msg)->recv;
return $x->input_focus;
}
@ -76,4 +71,48 @@ is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)");
$focus = focus_after("j");
is($focus, $top->id, "Top window focused (wrapping to the bottom works)");
###############################################
# Test focus with empty containers and colspan
###############################################
# Switch to the 10. workspace
$i3->command('10')->recv;
$top = i3test::open_standard_window($x);
$bottom = i3test::open_standard_window($x);
sleep 0.25;
$focus = focus_after("mj");
$focus = focus_after("mh");
$focus = focus_after("k");
is($focus, $bottom->id, "Selecting top window without snapping doesn't work");
$focus = focus_after("sl");
is($focus, $bottom->id, "Bottom window focused");
$focus = focus_after("k");
is($focus, $top->id, "Top window focused");
# Same thing, but left/right instead of top/bottom
# Switch to the 11. workspace
$i3->command('11')->recv;
my $left = i3test::open_standard_window($x);
my $right = i3test::open_standard_window($x);
sleep 0.25;
$focus = focus_after("ml");
$focus = focus_after("h");
$focus = focus_after("mk");
$focus = focus_after("l");
is($focus, $left->id, "Selecting right window without snapping doesn't work");
$focus = focus_after("sj");
is($focus, $left->id, "left window focused");
$focus = focus_after("l");
is($focus, $right->id, "right window focused");
diag( "Testing i3, Perl $], $^X" );

View File

@ -4,7 +4,7 @@
# the workspace to be empty).
# TODO: skip it by default?
use Test::More tests => 10;
use Test::More tests => 8;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
@ -12,21 +12,18 @@ use Time::HiRes qw(sleep);
use FindBin;
use lib "$FindBin::Bin/lib";
use i3test;
use AnyEvent::I3;
BEGIN {
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
}
my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
isa_ok($sock, 'IO::Socket::UNIX');
my $i3 = i3;
# Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9"));
sleep(0.25);
$i3->command('9')->recv;
#####################################################################
# Create two windows and make sure focus switching works
@ -50,8 +47,7 @@ diag("bottom id = " . $bottom->id);
sub focus_after {
my $msg = shift;
$sock->write(i3test::format_ipc_command($msg));
sleep(0.5);
$i3->command($msg)->recv;
return $x->input_focus;
}
@ -82,9 +78,7 @@ is($focus, $top->id, "Top window focused");
# Move window cross-workspace
#####################################################################
$sock->write(i3test::format_ipc_command("m12"));
$sock->write(i3test::format_ipc_command("t"));
$sock->write(i3test::format_ipc_command("m13"));
$sock->write(i3test::format_ipc_command("12"));
$sock->write(i3test::format_ipc_command("13"));
for my $cmd (qw(m12 t m13 12 13)) {
$i3->command($cmd)->recv;
}
ok(1, "Still living");

View File

@ -3,7 +3,7 @@
# Checks if the focus is correctly restored, when creating a floating client
# over an unfocused tiling client and destroying the floating one again.
use Test::More tests => 6;
use Test::More tests => 4;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
@ -11,28 +11,25 @@ use Time::HiRes qw(sleep);
use FindBin;
use lib "$FindBin::Bin/lib";
use i3test;
use AnyEvent::I3;
BEGIN {
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
use_ok('X11::XCB::Window') or BAIL_OUT('Could not load X11::XCB::Window');
}
my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
isa_ok($sock, 'IO::Socket::UNIX');
my $i3 = i3;
# Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9"));
sleep(0.25);
$i3->command('9')->recv;
my $tiled_left = i3test::open_standard_window($x);
my $tiled_right = i3test::open_standard_window($x);
sleep(0.25);
$sock->write(i3test::format_ipc_command("ml"));
$i3->command('ml')->recv;
# Get input focus before creating the floating window
my $focus = $x->input_focus;

View File

@ -4,7 +4,7 @@
# the workspace to be empty).
# TODO: skip it by default?
use Test::More tests => 24;
use Test::More tests => 22;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
@ -12,21 +12,18 @@ use Time::HiRes qw(sleep);
use FindBin;
use lib "$FindBin::Bin/lib";
use i3test;
use AnyEvent::I3;
BEGIN {
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
}
my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
isa_ok($sock, 'IO::Socket::UNIX');
my $i3 = i3;
# Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9"));
sleep(0.25);
$i3->command('9')->recv;
#####################################################################
# Create two windows and make sure focus switching works
@ -50,8 +47,7 @@ diag("bottom id = " . $bottom->id);
sub focus_after {
my $msg = shift;
$sock->write(i3test::format_ipc_command($msg));
sleep(0.25);
$i3->command($msg)->recv;
return $x->input_focus;
}

View File

@ -12,7 +12,6 @@ use i3test;
use List::Util qw(first);
BEGIN {
#use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
}
@ -43,4 +42,17 @@ sleep 0.25;
my $rect = $window->rect;
is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
my $fwindow = $x->root->create_child(
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30],
background_color => '#FF0000',
type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
);
$fwindow->transient_for($window);
$fwindow->map;
sleep 0.25;
diag( "Testing i3, Perl $], $^X" );

View File

@ -4,7 +4,7 @@
# the workspace to be empty).
# TODO: skip it by default?
use Test::More tests => 9;
use Test::More tests => 7;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
@ -13,21 +13,18 @@ use FindBin;
use Digest::SHA1 qw(sha1_base64);
use lib "$FindBin::Bin/lib";
use i3test;
use AnyEvent::I3;
BEGIN {
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
}
my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
isa_ok($sock, 'IO::Socket::UNIX');
my $i3 = i3;
# Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9"));
sleep(0.25);
$i3->command('9')->recv;
#####################################################################
# Create two windows and make sure focus switching works
@ -51,8 +48,7 @@ diag("bottom id = " . $bottom->id);
sub focus_after {
my $msg = shift;
$sock->write(i3test::format_ipc_command($msg));
sleep(0.5);
$i3->command($msg)->recv;
return $x->input_focus;
}
@ -74,7 +70,7 @@ my $random_mark = sha1_base64(rand());
$focus = focus_after("goto $random_mark");
is($focus, $mid->id, "focus unchanged");
$sock->write(i3test::format_ipc_command("mark $random_mark"));
$i3->command("mark $random_mark")->recv;
$focus = focus_after("k");
is($focus, $top->id, "Top window focused");

View File

@ -4,7 +4,7 @@
# the workspace to be empty).
# TODO: skip it by default?
use Test::More tests => 17;
use Test::More tests => 15;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
@ -13,21 +13,18 @@ use FindBin;
use Digest::SHA1 qw(sha1_base64);
use lib "$FindBin::Bin/lib";
use i3test;
use AnyEvent::I3;
BEGIN {
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
}
my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
isa_ok($sock, 'IO::Socket::UNIX');
my $i3 = i3;
# Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9"));
sleep 0.25;
$i3->command('9')->recv;
#####################################################################
# Create a floating window and see if resizing works
@ -78,13 +75,11 @@ sub test_resize {
test_resize;
# Test borderless
$sock->write(i3test::format_ipc_command("bb"));
sleep 0.25;
$i3->command('bb')->recv;
test_resize;
# Test with 1-px-border
$sock->write(i3test::format_ipc_command("bp"));
sleep 0.25;
$i3->command('bp')->recv;
test_resize;

53
testcases/t/13-urgent.t Normal file
View File

@ -0,0 +1,53 @@
#!perl
# vim:ts=4:sw=4:expandtab
# Beware that this test uses workspace 9 to perform some tests (it expects
# the workspace to be empty).
# TODO: skip it by default?
use Test::More tests => 7;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
use Time::HiRes qw(sleep);
use FindBin;
use Digest::SHA1 qw(sha1_base64);
use lib "$FindBin::Bin/lib";
use i3test;
use AnyEvent::I3;
BEGIN {
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
}
my $x = X11::XCB::Connection->new;
my $i3 = i3;
# Switch to the nineth workspace
$i3->command('9')->recv;
#####################################################################
# Create two windows and put them in stacking mode
#####################################################################
my $top = i3test::open_standard_window($x);
sleep 0.25;
my $bottom = i3test::open_standard_window($x);
sleep 0.25;
$i3->command('s')->recv;
#####################################################################
# Add the urgency hint, switch to a different workspace and back again
#####################################################################
$top->add_hint('urgency');
sleep 1;
$i3->command('1')->recv;
$i3->command('9')->recv;
$i3->command('1')->recv;
my $std = i3test::open_standard_window($x);
sleep 0.25;
$std->add_hint('urgency');
sleep 1;

View File

@ -0,0 +1,66 @@
#!perl
# vim:ts=4:sw=4:expandtab
# Beware that this test uses workspace 9 and 10 to perform some tests (it expects
# the workspace to be empty).
# TODO: skip it by default?
use Test::More tests => 3;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
use Time::HiRes qw(sleep);
use FindBin;
use Digest::SHA1 qw(sha1_base64);
use lib "$FindBin::Bin/lib";
use i3test;
use AnyEvent::I3;
BEGIN {
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
}
my $x = X11::XCB::Connection->new;
my $i3 = i3;
# Switch to the nineth workspace
$i3->command('9')->recv;
#####################################################################
# Create a parent window
#####################################################################
my $window = $x->root->create_child(
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#C0C0C0',
);
$window->name('Parent window');
$window->map;
sleep 0.25;
#########################################################################
# Switch workspace to 10 and open a child window. It should be positioned
# on workspace 9.
#########################################################################
$i3->command('10')->recv;
my $child = $x->root->create_child(
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#C0C0C0',
);
$child->name('Child window');
$child->client_leader($window);
$child->map;
sleep 0.25;
isnt($x->input_focus, $child->id, "Child window focused");
# Switch back
$i3->command('9')->recv;
is($x->input_focus, $child->id, "Child window focused");

View File

@ -0,0 +1,25 @@
#!perl
# vim:ts=4:sw=4:expandtab
use Test::More tests => 3;
use Test::Exception;
use List::MoreUtils qw(all);
use FindBin;
use lib "$FindBin::Bin/lib";
use i3test;
use AnyEvent::I3;
my $i3 = i3;
####################
# Request workspaces
####################
my $workspaces = $i3->get_workspaces->recv;
ok(@{$workspaces} > 0, "More than zero workspaces found");
my $name_exists = all { defined($_->{name}) } @{$workspaces};
ok($name_exists, "All workspaces have a name");
diag( "Testing i3, Perl $], $^X" );

View File

@ -29,15 +29,4 @@ sub open_standard_window {
return $window;
}
sub format_ipc_command {
my $msg = shift;
my $len;
{ use bytes; $len = length($msg); }
my $message = "i3-ipc" . pack("LL", $len, 0) . $msg;
return $message;
}
1