Merge branch 'tree' into next

This commit is contained in:
Michael Stapelberg 2011-07-31 21:56:02 +02:00
commit 2728c02467
214 changed files with 24460 additions and 11141 deletions

32
.gitignore vendored
View File

@ -1,19 +1,37 @@
*.o
i3
i3-input/i3-input
i3-msg/i3-msg
tags
include/loglevels.h
loglevels.tmp
src/*.output
src/*.tab.*
src/*.yy.c
*.swp
*.gcda
*.gcno
testcases/testsuite-*
testcases/latest
*.output
*.tab.*
*.yy.c
man/i3-msg.1
man/i3-msg.xml
man/i3-msg.html
man/i3-nagbar.1
man/i3-nagbar.xml
man/i3-nagbar.html
man/i3-wsbar.1
man/i3-wsbar.xml
man/i3-wsbar.html
man/i3-input.1
man/i3-input.xml
man/i3-input.html
man/i3.1
man/i3.xml
man/i3.html
tags
*.tar.bz2*
i3
i3-input/i3-input
i3-nagbar/i3-nagbar
i3-msg/i3-msg
i3-config-wizard/i3-config-wizard
docs/*.html
docs/*.aux
docs/*.out
docs/*.pdf

63
DEPENDS
View File

@ -1,32 +1,39 @@
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
* x11-utils for xmessage (only for displaying the welcome message, so this is
mainly interesting for distributors)
i3 has the following dependencies:
Recommendations:
* i3lock for locking your screen
* dmenu for launching applications
"min" means minimum required version
"lkgv" means last known good version
Get the libraries from:
http://xcb.freedesktop.org/dist/xcb-proto-1.5.tar.bz2
http://xcb.freedesktop.org/dist/libxcb-1.1.93.tar.bz2
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/
┌─────────────┬────────┬────────┬────────────────────────────────────────┐
│ dependency │ min. │ lkgv │ URL │
├─────────────┼────────┼────────┼────────────────────────────────────────┤
│ pkg-config │ 0.25 │ 0.26 │ http://pkgconfig.freedesktop.org/ │
│ xcb-proto │ 1.3 │ 1.6 │ http://xcb.freedesktop.org/dist/ │
│ libxcb │ 1.1.93 │ 1.7 │ http://xcb.freedesktop.org/dist/ │
│ xcb-util │ 0.3.3 │ 0.3.8 │ http://xcb.freedesktop.org/dist/ │
│ libev │ 3.0 │ 4.04 │ http://libev.schmorp.de/ │
│ flex │ 2.5.35 │ 2.5.35 │ http://flex.sourceforge.net/ │
│ bison │ 2.4.1 │ 2.4.1 │ http://www.gnu.org/software/bison/ │
│ yajl │ 1.0.8 │ 2.0.1 │ http://lloyd.github.com/yajl/ │
│ asciidoc │ 8.3.0 │ 8.6.4 │ http://www.methods.co.nz/asciidoc/ │
│ xmlto │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/ │
│ docbook-xml │ 4.5 │ 4.5 │ http://www.methods.co.nz/asciidoc/ │
│ libxcursor │ 1.1.11 │ 1.1.11 │ http://ftp.x.org/pub/current/src/lib/ │
│ Xlib │ 1.3.3 │ 1.4.3 │ http://ftp.x.org/pub/current/src/lib/ │
└─────────────┴────────┴────────┴────────────────────────────────────────┘
http://i3.zekjur.net/i3lock/
http://tools.suckless.org/dmenu
i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any new
dependencies.
i3-wsbar is implemented in Perl and has the following dependencies:
• IPC::Run
• Try::Tiny
• AnyEvent
• AnyEvent::I3
All of them are available at CPAN, see http://search.cpan.org/
Use your distributions packages or cpan(1) to install them.
i3-migrate-config-to-v4.pl is implemented in Perl, but it has no dependencies
besides Perl 5.10.

View File

@ -3,7 +3,7 @@ TOPDIR=$(shell pwd)
include $(TOPDIR)/common.mk
# Depend on the object files of all source-files in src/*.c and on all header files
AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c
AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c
FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c))
FILES:=$(FILES:.c=.o)
HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h))
@ -13,24 +13,30 @@ HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h))
# 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)
#UNUSED:=$(warning Generating loglevels.h)
else
UNUSED:=$(shell $(MAKE) loglevels.h)
endif
SUBDIRS=i3-msg i3-input i3-nagbar i3-config-wizard
# Depend on the specific file (.c for each .o) and on all headers
src/%.o: src/%.c ${HEADERS}
echo "CC $<"
$(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/$(shell basename $< .c)/ { print NR }' loglevels.tmp))" -c -o $@ $<
$(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -c -o $@ $<
all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES}
all: i3 subdirs
i3: src/cfgparse.y.o src/cfgparse.yy.o src/cmdparse.y.o src/cmdparse.yy.o ${FILES}
echo "LINK i3"
$(CC) -o i3 ${FILES} src/cfgparse.y.o src/cfgparse.yy.o $(LDFLAGS)
echo ""
echo "SUBDIR i3-msg"
$(MAKE) TOPDIR=$(TOPDIR) -C i3-msg
echo "SUBDIR i3-input"
$(MAKE) TOPDIR=$(TOPDIR) -C i3-input
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
subdirs:
for dir in $(SUBDIRS); do \
echo ""; \
echo "MAKE $$dir"; \
$(MAKE) -C $$dir; \
done
loglevels.h:
echo "LOGLEVELS"
@ -47,12 +53,24 @@ loglevels.h:
src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS}
echo "LEX $<"
flex -i -o$(@:.o=.c) $<
$(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c)
$(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c)
src/cmdparse.yy.o: src/cmdparse.l src/cmdparse.y.o ${HEADERS}
echo "LEX $<"
flex -Pcmdyy -i -o$(@:.o=.c) $<
$(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c)
src/cfgparse.y.o: src/cfgparse.y ${HEADERS}
echo "YACC $<"
bison --debug --verbose -b $(basename $< .y) -d $<
$(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c)
$(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c)
src/cmdparse.y.o: src/cmdparse.y ${HEADERS}
echo "YACC $<"
bison -p cmdyy --debug --verbose -b $(basename $< .y) -d $<
$(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c)
install: all
echo "INSTALL"
@ -61,20 +79,22 @@ install: all
$(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/
$(INSTALL) -m 0755 i3-migrate-config-to-v4.pl $(DESTDIR)$(PREFIX)/bin/
test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config
test -e $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes || $(INSTALL) -m 0644 i3.config.keycodes $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes
$(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
for dir in $(SUBDIRS); do \
$(MAKE) -C $$dir install; \
done
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 i3-wsbar pseudo-doc.doxygen Makefile i3-${VERSION}
cp -r src i3-msg include man i3-${VERSION}
cp i3-migrate-config-to-v4.pl i3.config.keycodes DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen i3-wsbar Makefile i3-${VERSION}
cp -r src i3-msg i3-nagbar i3-config-wizard yajl-fallback include man i3-${VERSION}
# Only copy toplevel documentation (important stuff)
mkdir i3-${VERSION}/docs
# Pre-generate documentation
@ -83,21 +103,32 @@ dist: distclean
# Only copy source code from i3-input
mkdir i3-${VERSION}/i3-input
find i3-input -maxdepth 1 -type f \( -name "*.c" -or -name "*.h" -or -name "Makefile" \) -exec cp '{}' i3-${VERSION}/i3-input \;
sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION=${GIT_VERSION}/g;s/^VERSION:=\(.*\)/VERSION=${VERSION}/g' common.mk > i3-${VERSION}/common.mk
sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION:=$(shell echo '${GIT_VERSION}' | sed 's/\\/\\\\/g')/g;s/^VERSION:=\(.*\)/VERSION:=${VERSION}/g' common.mk > i3-${VERSION}/common.mk
# Pre-generate a manpage to allow distributors to skip this step and save some dependencies
make -C man
$(MAKE) -C man
cp man/*.1 i3-${VERSION}/man/
tar cfj i3-${VERSION}.tar.bz2 i3-${VERSION}
rm -rf i3-${VERSION}
clean:
rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.{output,dot} src/cfgparse.yy.c loglevels.tmp include/loglevels.h
rm -f src/*.o src/*.gcno src/cfgparse.tab.{c,h} src/cfgparse.yy.c src/cfgparse.{output,dot} src/cmdparse.tab.{c,h} src/cmdparse.yy.c src/cmdparse.{output,dot} loglevels.tmp include/loglevels.h
(which lcov >/dev/null && lcov -d . --zerocounters) || true
$(MAKE) -C docs clean
$(MAKE) -C man clean
$(MAKE) TOPDIR=$(TOPDIR) -C i3-msg clean
$(MAKE) TOPDIR=$(TOPDIR) -C i3-input clean
$(MAKE) TOPDIR=$(TOPDIR) -C i3-nagbar clean
$(MAKE) TOPDIR=$(TOPDIR) -C i3-config-wizard clean
distclean: clean
rm -f i3
$(MAKE) TOPDIR=$(TOPDIR) -C i3-msg distclean
$(MAKE) TOPDIR=$(TOPDIR) -C i3-input distclean
$(MAKE) TOPDIR=$(TOPDIR) -C i3-nagbar distclean
$(MAKE) TOPDIR=$(TOPDIR) -C i3-config-wizard distclean
coverage:
rm -f /tmp/i3-coverage.info
rm -rf /tmp/i3-coverage
lcov -d . -b . --capture -o /tmp/i3-coverage.info
genhtml -o /tmp/i3-coverage/ /tmp/i3-coverage.info

View File

@ -10,16 +10,27 @@ packages for them.
Please make sure the manpage for i3 will be properly created and installed
in your package.
Also please provide the path to a suitable terminal emulator which is installed
as a dependency of your package (e.g. urxvt). On systems which have a special
commend to launch the best available terminal emulator, please use this one
(e.g. x-terminal-emulator on debian).
On debian, this looks like this:
# Compilation
$(MAKE)
$(MAKE) TERM_EMU=x-terminal-emulator
$(MAKE) -C man
# Installation
$(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install
mkdir -p $(CURDIR)/debian/i3-wm/usr/share/man/man1
cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
cp man/*.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
Please make sure that i3-migrate-config-to-v4.pl and i3-config-wizard are
installed with i3. The Perl script is necessary to (automatically) convert v3
configs to v4. The wizard provides the possibility to create a keysym-based
config with the users preferred modifier and should be started on the first
start of i3 (it will automatically exit if it finds a config file).
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

View File

@ -1,29 +0,0 @@
Release notes for i3 v3.δ-bf1
-----------------------------
This is the first bugfix release (bf1) for version 3.δ (transcribed 3.d) of
i3. Because many bugs were fixed after the release of version 3.δ, we thought
users of the stable releases might profit from this additional bugfix release.
Thanks for this release go out to msi, merovius, Grauwolf, jace, Syntropy,
Mirko, helgiks and Moredread.
A list of changes follows:
* Bugfix: Dont draw window title when titlebar is disabled
* Bugfix: Correctly switch border types for floating windows
* Bugfix: Correctly replay pointer if the click handler does not trigger
* Bugfix: Also allow WORDs as workspace names
* Bugfix: Correctly clear the urgency hint if a window gets unmapped without
clearing it
* Bugfix: Fix resizing of floating windows in borderless/1-px-border mode
* Bugfix: Accept underscores in bindsym
* Bugfix: Dont set the urgency flag if the window is focused
* Bugfix: Handle stack-limit cols on tabbed containers
* Bugfix: Resize client after updating base_width/base_height
* Bugfix: Force render containers after setting the client active
* Bugfix: Fix two problems in resizing floating windows with right mouse
* Bugfix: Use more precise floating point arithmetics
* Bugfix: Correctly place new windows below fullscreen windows
-- Michael Stapelberg, 2009-12-21

View File

@ -1,128 +0,0 @@
Release notes for i3 v3.ε
-----------------------------
This is the fifth version (3.ε, transcribed 3.e) of i3. It is considered
stable.
A really big change in this release is the support of RandR instead of
Xinerama. The Xinerama API is a subset of RandR and its limitations clearly
showed when you reconfigured outputs using xrandr(1) during runtime (it was
not designed to handle such changes). The implementation of RandR fixes some
long-standing bugs (workspaces were messed up when reconfiguring outputs)
and cleans up some code. Furthermore, you are now able to assign workspaces
to outputs (like LVDS1, VGA1, …) instead of the formerly used heuristics
like "the screen at position (x, y)" or "the second screen in the list".
Furthermore, another big change is the separation of debug output (the
so-called logfile): you now need to enable verbose output (parameter -V)
and you need to specify which (if any) debug output you want to see (parameter
-d <loglevels>). When starting without -V, i3 will only log errors. This is
what you usually want for a production system. When enabling verbose output,
you will see the names and window classes of new windows (useful for creating
assignments in your configuration file) and other useful messages. For an
explanation of the debuglevels, please see the "How to debug" document (for
the impatient: "-d all" gives you full output).
In 3.δ, a new parser/lexer was introduced and available using the -l option.
The old parser/lexer has been removed in the meantime, so in 3.ε, the "new"
parser/lexer is always used and you do not need the -l option anymore. To
make debugging errors in your configuration easier, the error messages have
been very much improved. Also, the parser tries to skip invalid lines (though
it may not always succeed, it usually works and does not crash i3).
Starting from version 3.ε, i3 obeys the XDG base directory specification,
meaning that you can now put your configuration file into ~/.config/i3/config,
which might be useful if you manage your ~/.config directory in some way (git,
…). The old configuration file path is still supported (there are no plans
to change this), but using ~/.config seems reasonable for clean setups.
You can disable the internal workspace bar in this release. Instead of the
internal bar, you can use dzen2 (or similar) in dock mode (-dock for dzen2,
but you need an svn revision). The sample implementation i3-wsbar takes
stdin, generates a combined bar (workspaces + stdin) and starts dzen2 on
your outputs as needed (does the right thing when you reconfigure your
monitors dynamically).
To accomplish the external workspace bar feature, the IPC interface has
seen much love: requests and replies now use JSON for serialization of
data structures and provide a nice and simple way to get information (like
the current workspaces or outputs) from i3 or send commands to it. You can
also subscribe to certain types of events (workspace or output changes).
See the AnyEvent::I3 module for a sample implementation of a library.
Thanks for this release go out to Merovius, badboy, xeen, Atsutane, Ciprian,
dirkson, Mirko, sur5r, artoj, Scytale, fallen, Thomas, Sasha, dothebart, msi
and all other people who reported bugs/made suggestions.
A complete list of changes follows:
* Implement RandR instead of Xinerama
* Obey the XDG Base Directory Specification for config file paths
* lexer/parser: proper error messages
* Add new options -V for verbose mode and -d <loglevel> for debug log levels
* Implement resize command for floating clients
* Include date of the last commit in version string
* Fixed cursor orientation when resizing
* Added focus_follows_mouse config option
* Feature: Cycle through workspaces
* Fix bindings using the cursor keys in default config
* added popup for handling SIGSEGV or SIGFPE
* Correctly exit when another window manager is already running
* Take into account the windows base_{width,height} when resizing
* Disable XKB instead of quitting with an error
* Make containers containing exactly one window behave like default containers
* Also warp the pointer when moving a window to a another visible workspace
* work around clients setting 0xFFFF as resize increments
* Move autostart after creating the IPC socket in start process
* Restore geometry of all windows before exiting/restarting
* When in fullscreen mode, focus whole screens instead of denying to focus
* draw consistent borders for each frame in a tabbed/stacked container
* Update fullscreen client position/size when an output changes
* i3-input: Bugfix: repeatedly grab the keyboard if it does not succeed
* put windows with WM_CLIENT_LEADER on the workspace of their leader
* use real functions instead of nested functions (enables compilation with
llvm-clang)
* implement screen-spanning fullscreen mode
* floating resize now uses arbitrary corners
* floating resize now works proportionally when pressing shift
* Dont use SYNC key bindings for mode_switch but re-grab keys
* support PREFIX and SYSCONFDIR in Makefile
* make pointer follow the focus when moving to a different screen also for
floating clients
* start dock clients on the output they request to be started on according
to their geometry
* handle destroy notify events like unmap notify events
* ewmh: correctly set _NET_CURRENT_DESKTOP to the number of the active
workspace
* ewmh: correctly set _NET_ACTIVE_WINDOW
* ewmh: implement support for _NET_WORKAREA (rdesktop can use that)
* default ipc-socket path is now ~/.i3/ipc.sock, enabled in the default config
* Bugfix: Containers could lose their snap state
* Bugfix: Use ev_loop_new to not block SIGCHLD
* Bugfix: if a font provides no per-char info for width, fall back to default
* Bugfix: lexer: return to INITIAL state after floating_modifier
* Bugfix: Dont leak IPC socket to launched processes
* Bugfix: Use both parts of WM_CLASS (it contains instance and class)
* Bugfix: Correctly do boundary checking/moving to other workspaces when
moving floating clients via keyboard
* Bugfix: checked for wrong flag in size hints
* Bugfix: Correctly render workspace names containing some non-ascii chars
* Bugfix: Correctly position floating windows sending configure requests
* Bugfix: Dont remap stack windows errnously when changing workspaces
* Bugfix: configure floating windows above tiling windows when moving them
to another workspace
* Bugfix: Take window out of fullscreen mode before entering floating mode
* Bugfix: Dont enter BIND_A2WS_COND state too early
* Bugfix: only restore focus if the workspace is focused, not if it is visible
* Bugfix: numlock state will now be filtered in i3-input and signal handler
* Bugfix: Dont unmap windows when current workspace gets reassigned
* Bugfix: correctly translate coordinates for floating windows when outputs
change
* Bugfix: Correctly switch workspace when using the "jump" command
* Bugfix: Fix rendering of workspace names after "reload"
* Bugfix: Correctly ignore clicks when in fullscreen mode
* Bugfix: Dont allow fullscreen floating windows to be moved
* Bugfix: Dont render containers which are not visible on hint changes
* Some memory leaks/invalid accesses have been fixed
-- Michael Stapelberg, 2010-03-30

135
RELEASE-NOTES-4.0 Normal file
View File

@ -0,0 +1,135 @@
┌────────────────────────────┐
│ Release notes for i3 v4.0 │
└────────────────────────────┘
This is the first release of the new major version of i3, v4.0. It has been a
long time since v3.ε was released (over one year). A lot has been happening
since then, we made 736 commits compare that to the total number of 1664
commits for i3.
The reason for the high number of commits and long time for this release is the
big refactoring we have been doing. Instead of using several lists and a table
as data structures, we now use a single tree of containers. These containers
represent invisible entities like your X11 root window, your different monitors
and workspaces, but also visible entities like actual windows.
Using a tree has made a lot of things cleaner and easier in the code *and* in
the user interface. Admittedly though, you will probably need a day or two to
get used to a few more advanced movement commands if you are used to v3.ε right
now.
┌────────────────────────────┐
│ New features │
└────────────────────────────┘
• In addition to the proper flex/bison based parser for the config file
introduced in 3.δ, we now also have a flex/bison parser for commands. What
this means is that we can have more human-readable, beautiful command names
instead of cryptic commands like 'f' for fullscreen or 'mh' for move left.
In fact, the commands for the aforementioned functions *are* 'fullscreen'
and 'move left'!
• You can now chain commands using ';' (a semicolon). One example for that is
'workspace 3 ; exec /usr/bin/urxvt' to switch to a new workspace and open a
terminal.
• You can specify which windows should be affected by your command by using
different criteria. A good example is '[class="Firefox"] kill' to get rid
of all Firefox windows.
• As the configuration file needs new commands (and a few options are
obsolete), you need to change it. To make this process a little bit easier
for you, this release comes with the script i3-migrate-config-to-v4.pl. Just
run it on your current config file and it will spit out a v4 config file to
stdout. To make things even better, i3 automatically detects v3 config files
and calls that script, so you never end up with a non-working config :).
• Similarly to the criteria when using commands, we now have a 'for_window'
configuration directive, which lets you automatically apply certain commands
to certain windows. Use it to set border styles per window, for example with
'for_window [class="XTerm"] border 1pixel'.
• Since dock clients (like dzen2) are now part of the layout tree (as opposed
to a custom data structure as before), it was easy to implement top and
bottom dock areas. Programs which properly specify the dock hint get placed
on the edge of the screen they request. i3bar has the -dtop and -dbottom
parameters, for example.
• The internal workspace bar is obsolete. Use i3bar instead.
• Resizing now works between all windows!
• Fullscreen now works for everything!
• Floating now works for everything!
• Your layout is now preserved when doing an inplace restart.
• When you have an error in your config file, a new program called i3-nagbar
will tell you so. It offers you two buttons: One to view the error in your
$PAGER and one to edit your config in your $EDITOR.
• The default config used key symbols (like 'bind Mod1+f fullscreen') instead
of key codes. If you use a non-qwerty layout, the program i3-config-wizard
can create a key symbol based config file based on your current layout. You
can also chose between Windows (Mod4) and Alt (Mod1) as your default
modifier. i3-config-wizard will automatically be started as long as you
dont have a configuration file for i3.
• Custom X cursor themes are now supported.
• The RandR backend now respects the primary output.
• A wrong 'font' configuration in your config file will no longer make i3
exit. Instead, it will fall back to a different font and tell you about the
error in its log.
• The default split direction (whether a new window gets placed right next to
the current one or below the current one) is now automatically set to
horizontal if you have a monitor that is wider than high or vertical if you
a monitor which is higher than wide. This works great with rotated monitors.
• Sockets and temporary files are now placed in XDG_RUNTIME_DIR, if set (this
is used on systemd based systems).
• Tools like i3bar, i3-msg etc. use the I3_SOCKET_PATH property which is set
to the X11 root window, so you dont have to configure your socket path
anywhere.
• The kill command kills single windows by default now. To kill a whole
application, use 'kill client'.
• IPC: Commands can now have custom replies. When the parser encounters an
error, a proper error reply is sent.
• There is now an 'exec_always' configuration directive which works like
'exec' but will also be run when restarting.
┌────────────────────────────┐
│ Future features │
└────────────────────────────┘
Our plans were big but our time and manpower is limited. Therefore, the
following features did not make it into this release. However, the foundation
is now in place and implementing them is possible, so stay tuned!
• Saving/Restoring specific parts of your layout
• Session saving
• Sticky windows
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
aniou, artoj, badboy, cloud, cradle, David Coppa, dbp, dothebart, eeemsi,
eelvex, f8l, fernando, jan, jimdigriz, jon, julien, kacper, ktosiek,
lexszero, litemotiv, lourens, madroach, marcus, merovius, mike, mirko, mseed,
mxf, phnom, quaec, rogutes, sardemff7, smartass, thepub, tiago, tucos,
woddf2, xpt, ys
-- Michael Stapelberg, 2011-07-24

45
RELEASE-NOTES-tree-pr1 Normal file
View File

@ -0,0 +1,45 @@
Release notes for i3 tree-pr1
-----------------------------
This is a PREVIEW RELEASE for the tree branch. It is *NOT* part of i3s regular
releases and should *NOT* be packaged in the usual way for distributions.
Instead, provide a separate, unofficial package if possible.
The so called tree branch is the place where the next version of i3 is
developed. This time, we did a major code refactoring bringing many changes.
The idea is to use a tree as datastructure instead of separate lists (like one
for outputs, workspaces and a table for storing your window layout).
Quite a few advantages arise from this new data structure. The most prominent
ones will be a slightly different look and feel, the possibility to store your
layout and restore it later, correct resizing, a much cleaner command parser
and more little improvements.
As this is a preview release, some things are not working yet. Generally,
though, the core developers are using it already and think its good enough to
try it out. With this release, we want to gather feedback from you, so please
report any bugs you encounter in our bugtracker at http://i3.zekjur.net/bugs
What should be working in this release?
---------------------------------------
• Basic window management, navigation, moving
• Fullscreen mode, correct aspect ratio
• Stacked/Tabbed layout, floating mode
• Splitting (for fancy layouts), resizing
• Restarts, preserving the layout
• i3bar, get it from http://git.merovius.de/
If any of these features do not work (correctly), please file a bugreport.
What is not working in this release?
------------------------------------
• RandR changes (i3 needs to be restarted)
• Assignments
• Configfile compatibility
• Workspace switching is sometimes not working. If you find a pattern, please
report it.
• There are still some bugs in resizing. Please report!
-- Michael Stapelberg, 2010-12-06

43
RELEASE-NOTES-tree-pr2 Normal file
View File

@ -0,0 +1,43 @@
Release notes for i3 tree-pr2
-----------------------------
This is the second PREVIEW RELEASE for the tree branch. It is *NOT* part of
i3s regular releases and should *NOT* be packaged in the usual way for
distributions. Instead, provide a separate, unofficial package if possible.
The so called tree branch is the place where the next version of i3 is
developed. This time, we did a major code refactoring bringing many changes.
The idea is to use a tree as datastructure instead of separate lists (like one
for outputs, workspaces and a table for storing your window layout).
Quite a few advantages arise from this new data structure. The most prominent
ones will be a slightly different look and feel, the possibility to store your
layout and restore it later, correct resizing, a much cleaner command parser
and more little improvements.
As this is a preview release, some things are not working yet. Generally,
though, the core developers are using it already and think its good enough to
try it out. With this release, we want to gather feedback from you, so please
report any bugs you encounter in our bugtracker at http://i3.zekjur.net/bugs
What should be working in this release?
---------------------------------------
• Basic window management, navigation, moving
• Fullscreen mode, correct aspect ratio
• Stacked/Tabbed layout, floating mode
• Splitting (for fancy layouts), resizing
• Restarts (preserving the layout), crash handler
• RandR changes, keyboard layout changes
• Dock clients
• i3bar, get it from http://git.merovius.de/
If any of these features do not work (correctly), please file a bugreport.
What is not working in this release?
------------------------------------
• Assignments
• Configfile compatibility
-- Michael Stapelberg, 2011-03-07

43
RELEASE-NOTES-tree-pr3 Normal file
View File

@ -0,0 +1,43 @@
Release notes for i3 tree-pr3
-----------------------------
This is the third PREVIEW RELEASE for the tree branch. It is *NOT* part of
i3s regular releases and should *NOT* be packaged in the usual way for
distributions. Instead, provide a separate, unofficial package if possible.
The so called tree branch is the place where the next version of i3 is
developed. This time, we did a major code refactoring bringing many changes.
The idea is to use a tree as datastructure instead of separate lists (like one
for outputs, workspaces and a table for storing your window layout).
Quite a few advantages arise from this new data structure. The most prominent
ones will be a slightly different look and feel, the possibility to store your
layout and restore it later, correct resizing, a much cleaner command parser
and more little improvements.
As this is a preview release, some things are not working yet. Generally,
though, the core developers are using it already and think its good enough to
try it out. With this release, we want to gather feedback from you, so please
report any bugs you encounter in our bugtracker at http://bugs.i3wm.org/
What should be working in this release?
---------------------------------------
• Basic window management, navigation, moving
• Fullscreen mode, correct aspect ratio
• Stacked/Tabbed layout, floating mode
• Splitting (for fancy layouts), resizing
• Restarts (preserving the layout), crash handler
• RandR changes, keyboard layout changes
• Dock clients
• Assignments
• i3bar, get it from http://git.merovius.de/
If any of these features do not work (correctly), please file a bugreport.
What is not working in this release?
------------------------------------
• Configfile compatibility
-- Michael Stapelberg, 2011-05-28

37
RELEASE-NOTES-tree-pr4 Normal file
View File

@ -0,0 +1,37 @@
Release notes for i3 tree-pr4
-----------------------------
This is the fourth PREVIEW RELEASE for the tree branch. It is *NOT* part of
i3s regular releases and should *NOT* be packaged in the usual way for
distributions. Instead, provide a separate, unofficial package if possible.
The so called tree branch is the place where the next version of i3 is
developed. This time, we did a major code refactoring bringing many changes.
The idea is to use a tree as datastructure instead of separate lists (like one
for outputs, workspaces and a table for storing your window layout).
Quite a few advantages arise from this new data structure. The most prominent
ones will be a slightly different look and feel, the possibility to store your
layout and restore it later, correct resizing, a much cleaner command parser
and more little improvements.
This release is considered a release candidate for i3 v4.0. We will not make
big changes and plan to release v4.0 in a few weeks.
With this release, we want to gather feedback from you, so please
report any bugs you encounter in our bugtracker at http://bugs.i3wm.org/
What should be working in this release?
---------------------------------------
• Basic window management, navigation, moving
• Fullscreen mode, correct aspect ratio
• Stacked/Tabbed layout, floating mode
• Splitting (for fancy layouts), resizing
• Restarts (preserving the layout), crash handler
• RandR changes, keyboard layout changes
• Dock clients
• Assignments
• Config file compatibility
• i3bar, get it from http://git.merovius.de/
-- Michael Stapelberg, 2011-07-15

View File

@ -1,5 +1,6 @@
UNAME=$(shell uname)
DEBUG=1
COVERAGE=0
INSTALL=install
ifndef PREFIX
PREFIX=/usr
@ -11,10 +12,20 @@ ifndef SYSCONFDIR
SYSCONFDIR=$(PREFIX)/etc
endif
endif
TERM_EMU=xterm
# The escaping is absurd, but we need to escape for shell, sed, make, define
GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f .git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' .git/HEAD || echo 'unknown'))"
GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f $(TOPDIR)/.git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' $(TOPDIR)/.git/HEAD || echo 'unknown'))"
VERSION:=$(shell git describe --tags --abbrev=0)
ifeq ($(shell which pkg-config 2>/dev/null 1>/dev/null || echo 1),1)
$(error "pkg-config was not found")
endif
# An easier way to get CFLAGS and LDFLAGS falling back in case there's
# no pkg-config support for certain libraries
cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1))
ldflags_for_lib = $(shell pkg-config --exists $(1) && pkg-config --libs $(1) || echo -l$(2))
CFLAGS += -std=c99
CFLAGS += -pipe
CFLAGS += -Wall
@ -22,40 +33,43 @@ CFLAGS += -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}\"
CFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\"
# Check if pkg-config is installed, because without pkg-config, the following
# check for the version of libxcb cannot be done.
ifeq ($(shell which pkg-config 2>/dev/null 1>/dev/null || echo 1),1)
$(error "pkg-config was not found")
CFLAGS += $(call cflags_for_lib, xcb-keysyms)
ifeq ($(shell pkg-config --exists xcb-util || echo 1),1)
CPPFLAGS += -DXCB_COMPAT
CFLAGS += $(call cflags_for_lib, xcb-atom)
CFLAGS += $(call cflags_for_lib, xcb-aux)
else
CFLAGS += $(call cflags_for_lib, xcb-util)
endif
CFLAGS += $(call cflags_for_lib, xcb-icccm)
CFLAGS += $(call cflags_for_lib, xcb-xinerama)
CFLAGS += $(call cflags_for_lib, xcb-randr)
CFLAGS += $(call cflags_for_lib, xcb)
CFLAGS += $(call cflags_for_lib, xcursor)
CFLAGS += $(call cflags_for_lib, x11)
CFLAGS += $(call cflags_for_lib, yajl)
CFLAGS += $(call cflags_for_lib, libev)
CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\"
CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\"
CPPFLAGS += -DTERM_EMU=\"$(TERM_EMU)\"
ifeq ($(shell pkg-config --exists xcb-keysyms || echo 1),1)
$(error "pkg-config could not find xcb-keysyms.pc")
LIBS += -lm
LIBS += $(call ldflags_for_lib, xcb-event, xcb-event)
LIBS += $(call ldflags_for_lib, xcb-keysyms, xcb-keysyms)
ifeq ($(shell pkg-config --exists xcb-util || echo 1),1)
LIBS += $(call ldflags_for_lib, xcb-atom, xcb-atom)
LIBS += $(call ldflags_for_lib, xcb-aux, xcb-aux)
else
LIBS += $(call ldflags_for_lib, xcb-util)
endif
ifeq ($(shell pkg-config --exact-version=0.3.3 xcb-keysyms && echo 1),1)
# xcb-keysyms fixed API from 0.3.3 to 0.3.4, so for some months, we will
# have this here. Distributions should upgrade their libxcb in the meantime.
CFLAGS += -DOLD_XCB_KEYSYMS_API
endif
LDFLAGS += -lm
LDFLAGS += -lxcb-event
LDFLAGS += -lxcb-property
LDFLAGS += -lxcb-keysyms
LDFLAGS += -lxcb-atom
LDFLAGS += -lxcb-aux
LDFLAGS += -lxcb-icccm
LDFLAGS += -lxcb-xinerama
LDFLAGS += -lxcb-randr
LDFLAGS += -lxcb
LDFLAGS += -lyajl
LDFLAGS += -lX11
LDFLAGS += -lev
LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib
LIBS += $(call ldflags_for_lib, xcb-icccm, xcb-icccm)
LIBS += $(call ldflags_for_lib, xcb-xinerama, xcb-xinerama)
LIBS += $(call ldflags_for_lib, xcb-randr, xcb-randr)
LIBS += $(call ldflags_for_lib, xcb, xcb)
LIBS += $(call ldflags_for_lib, xcursor, Xcursor)
LIBS += $(call ldflags_for_lib, x11, X11)
LIBS += $(call ldflags_for_lib, yajl, yajl)
LIBS += $(call ldflags_for_lib, libev, ev)
ifeq ($(UNAME),NetBSD)
# We need -idirafter instead of -I to prefer the systems iconv over GNU libiconv
@ -65,12 +79,16 @@ endif
ifeq ($(UNAME),OpenBSD)
CFLAGS += -I${X11BASE}/include
LDFLAGS += -liconv
LIBS += -liconv
LDFLAGS += -L${X11BASE}/lib
endif
ifeq ($(UNAME),FreeBSD)
LDFLAGS += -liconv
LIBS += -liconv
endif
ifeq ($(UNAME),Darwin)
LIBS += -liconv
endif
# Fallback for libyajl 1 which did not include yajl_version.h. We need
@ -78,7 +96,7 @@ endif
CFLAGS += -idirafter yajl-fallback
ifneq (,$(filter Linux GNU GNU/%, $(UNAME)))
CFLAGS += -D_GNU_SOURCE
CPPFLAGS += -D_GNU_SOURCE
endif
ifeq ($(DEBUG),1)
@ -87,6 +105,12 @@ CFLAGS += -gdwarf-2
CFLAGS += -g3
else
CFLAGS += -O2
CFLAGS += -freorder-blocks-and-partition
endif
ifeq ($(COVERAGE),1)
CFLAGS += -fprofile-arcs -ftest-coverage
LIBS += -lgcov
endif
# Dont print command lines which are run

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
i3-wm (4.0-0) unstable; urgency=low
* NOT YET RELEASED
-- Michael Stapelberg <michael@stapelberg.de> Sun, 24 Jul 2011 00:10:30 +0200
i3-wm (3.e-bf1-3) unstable; urgency=low
* include keyboard-layer{1,2}.png in docs (Closes: #595295)

12
debian/control vendored
View File

@ -3,7 +3,7 @@ Section: utils
Priority: extra
Maintainer: Michael Stapelberg <michael@stapelberg.de>
DM-Upload-Allowed: yes
Build-Depends: debhelper (>= 6), 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
Build-Depends: debhelper (>= 6), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl, texlive-latex-base, texlive-latex-recommended, texlive-latex-extra
Standards-Version: 3.9.1
Homepage: http://i3.zekjur.net/
@ -27,12 +27,12 @@ Provides: x-window-manager
Suggests: rxvt-unicode | x-terminal-emulator
Recommends: xfonts-base, libanyevent-i3-perl, libanyevent-perl, libipc-run-perl
Description: an improved dynamic tiling window manager
Key features of i3 are good support of multi-monitor setups (workspaces are
Key features of i3 are correct implementation of Xinerama (workspaces are
assigned to virtual screens, i3 does the right thing when attaching new
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.
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.
.
Please be aware i3 is primarily targeted at advanced users and developers.

1
debian/rules vendored
View File

@ -45,7 +45,6 @@ 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 ipc.html multi-monitor.html wsbar.html refcard.pdf
all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html tree-migrating.html refcard.pdf
hacking-howto.html: hacking-howto
asciidoc -a toc -n $<
@ -10,6 +10,10 @@ debugging.html: debugging
userguide.html: userguide
asciidoc -a toc -n $<
tree-migrating.html: tree-migrating
asciidoc -a toc -n $<
ipc.html: ipc
asciidoc -a toc -n $<
@ -23,5 +27,4 @@ refcard.pdf: refcard.tex
pdflatex refcard.tex && pdflatex refcard.tex
clean:
rm -f */*.{aux,log,toc,bm,pdf,dvi}
rm -f *.log *.html
find . -regex ".*\.\(aux\|out\|log\|toc\|bm\|pdf\|dvi\|log\|html\)" -exec rm '{}' \;

View File

@ -1,7 +1,7 @@
Debugging i3: How To
====================
Michael Stapelberg <michael+i3@stapelberg.de>
March 2010
July 2011
This document describes how to debug i3 suitably for sending us useful bug
reports, even if you have no clue of C programming.
@ -12,14 +12,21 @@ debugging and/or need further help, do not hesitate to contact us!
== Enabling logging
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 seperate
log file with date/time in it is a good idea to not get confused about the
different log files later on.
i3 logs useful information to stdout. To have a clearly defined place where log
files will be saved, you should redirect stdout and stderr in your
+~/.xsession+. While youre at it, putting each run of i3 in a separate log
file with date/time in its filename is a good idea to not get confused about
the different log files later on.
--------------------------------------------------------------------
exec /usr/bin/i3 -V -d all >/home/michael/i3/i3log-$(date +'%F-%k-%M-%S') 2>&1
exec /usr/bin/i3 >~/i3log-$(date +'%F-%k-%M-%S') 2>&1
--------------------------------------------------------------------
To enable verbose output and all levels of debug output (required when
attaching logfiles to bugreports), add the parameters +-V -d all+, like this:
--------------------------------------------------------------------
exec /usr/bin/i3 -V -d all >~/i3log-$(date +'%F-%k-%M-%S') 2>&1
--------------------------------------------------------------------
== Enabling core dumps
@ -29,7 +36,7 @@ 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):
use the following command (in your +~/.xsession+, before starting i3):
-------------------
ulimit -c unlimited
@ -50,9 +57,9 @@ process id (%p) in it. You can save this setting across reboots using
== Compiling with debug symbols
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:
is compiled with debug symbols, that is, that the symbols are not stripped
during the build process. You can check whether your executable contains
symbols by issuing the following command:
----------------
file $(which i3)

View File

@ -1,7 +1,7 @@
Hacking i3: How To
==================
Michael Stapelberg <michael+i3@stapelberg.de>
December 2009
July 2011
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
@ -65,6 +65,13 @@ the layout you need at the moment.
=== The layout table
*********************************************************************************
This section has not been updated for v4.0 yet, sorry! We wanted to release on
time, but we will update this soon. Please talk to us on IRC if you need to
know stuff *NOW* :).
*********************************************************************************
/////////////////////////////////////////////////////////////////////////////////
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
@ -106,9 +113,15 @@ window).
|========
Furthermore, you can freely resize table cells.
/////////////////////////////////////////////////////////////////////////////////
== Files
include/atoms.xmacro::
A file containing all X11 atoms which i3 uses. This file will be included
various times (for defining, requesting and receiving the atoms), each time
with a different definition of xmacro().
include/data.h::
Contains data definitions used by nearly all files. You really need to read
this first.
@ -128,19 +141,27 @@ 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/cmdparse.l::
Contains the lexer for i3 commands, written for +flex(1)+.
src/commands.c::
Parsing commands and actually executing them (focusing, moving, …).
src/cmdparse.y::
Contains the parser for i3 commands, written for +bison(1)+.
src/con.c::
Contains all functions which deal with containers directly (creating
containers, searching containers, getting specific properties from containers,
…).
src/config.c::
Parses the configuration file.
Contains all functions handling the configuration file (calling the parser
(src/cfgparse.y) with the correct path, switching key bindings mode).
src/debug.c::
Contains debugging functions to print unhandled X events.
src/ewmh.c::
iFunctions to get/set certain EWMH properties easily.
src/floating.c::
Contains functions for floating mode (mostly resizing/dragging).
@ -151,88 +172,162 @@ unmapping, key presses, button presses, …).
src/ipc.c::
Contains code for the IPC interface.
src/layout.c::
Renders your layout (screens, workspaces, containers).
src/load_layout.c::
Contains code for loading layouts from JSON files.
src/mainx.c::
src/log.c::
Handles the setting of loglevels, contains the logging functions.
src/main.c::
Initializes the window manager.
src/manage.c::
Looks at existing or new windows and decides whether to manage them. If so, it
reparents the window and inserts it into our data structures.
src/resize.c::
Contains the functions to resize columns/rows in the table.
src/match.c::
A "match" is a data structure which acts like a mask or expression to match
certain windows or not. For example, when using commands, you can specify a
command like this: [title="*Firefox*"] kill. The title member of the match
data structure will then be filled and i3 will check each window using
match_matches_window() to find the windows affected by this command.
src/table.c::
Manages the most important internal data structure, the design table.
src/move.c::
Contains code to move a container in a specific direction.
src/output.c::
Functions to handle CT_OUTPUT cons.
src/randr.c::
The RandR API is used to get (and re-query) the configured outputs (monitors,
…).
src/render.c::
Renders the tree data structure by assigning coordinates to every node. These
values will later be pushed to X11 in +src/x.c+.
src/resize.c::
Contains the functions to resize containers.
src/sighandler.c::
Handles +SIGSEGV+, +SIGABRT+ and +SIGFPE+ by showing a dialog that i3 crashed.
You can chose to let it dump core, to restart it in-place or to restart it
in-place but forget about the layout.
src/tree.c::
Contains functions which open or close containers in the tree, change focus or
cleanup ("flatten") the tree. See also +src/move.c+ for another similar
function, which was moved into its own file because it is so long.
src/util.c::
Contains useful functions which are not really dependant on anything.
src/window.c::
Handlers to update X11 window properties like +WM_CLASS+, +_NET_WM_NAME+,
+CLIENT_LEADER+, etc.
src/workspace.c::
Contains all functions related to workspaces (displaying, hiding, renaming…)
src/x.c::
Transfers our in-memory tree (see +src/render.c+) to X11.
src/xcb.c::
Contains wrappers to use xcb more easily.
src/xcursor.c::
XCursor functions (for cursor themes).
src/xinerama.c::
(Re-)initializes the available screens and converts them to virtual screens
(see below).
Legacy support for Xinerama. See +src/randr.c+ for the preferred API.
== Data structures
*********************************************************************************
This section has not been updated for v4.0 yet, sorry! We wanted to release on
time, but we will update this soon. Please talk to us on IRC if you need to
know stuff *NOW* :).
*********************************************************************************
/////////////////////////////////////////////////////////////////////////////////
See include/data.h for documented data structures. The most important ones are
explained right here.
image:bigpicture.png[The Big Picture]
/////////////////////////////////////////////////////////////////////////////////
So, the hierarchy is:
. *X11 root window*, the root container
. *Virtual screens* (Screen 0 in this example)
. *Workspaces* (Workspace 1 in this example)
. *Table* (There can only be one table per Workspace)
. *Container* (left and right in this example)
. *Client* (The two clients in the left container)
. *Content container* (there are also containers for dock windows)
. *Workspaces* (Workspace 1 in this example, with horizontal orientation)
. *Split container* (vertically split)
. *X11 window containers*
The data type is +Con+, in all cases.
=== 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
A virtual screen (type `i3Screen`) is generated from the connected outputs
obtained through RandR. The difference to the raw RandR outputs as seen
when using +xrandr(1)+ is that it falls back to the lowest common resolution of
the logical screens.
the actual enabled outputs.
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 a screen resolution of 1280x800 px and you
connect a video projector with a resolution of 1024x768 px, set it up in clone
mode (+xrandr \--output VGA1 \--mode 1024x768 \--same-as LVDS1+), 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
However, if you configure it using +xrandr \--output VGA1 \--mode 1024x768
\--right-of LVDS1+, 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
A workspace is identified by its name. 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
seperate of each other. Other window managers also call this ``Virtual
separate of each other. Other window managers also call this ``Virtual
desktops''.
=== The layout table
*********************************************************************************
This section has not been updated for v4.0 yet, sorry! We wanted to release on
time, but we will update this soon. Please talk to us on IRC if you need to
know stuff *NOW* :).
*********************************************************************************
/////////////////////////////////////////////////////////////////////////////////
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
*********************************************************************************
This section has not been updated for v4.0 yet, sorry! We wanted to release on
time, but we will update this soon. Please talk to us on IRC if you need to
know stuff *NOW* :).
*********************************************************************************
/////////////////////////////////////////////////////////////////////////////////
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
A client is x11-speak for a window.
@ -244,11 +339,11 @@ 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), `CIRCLEQ` (circular
queues) and TAILQ (tail queues). Usually, only forward traversal is necessary,
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
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
@ -258,14 +353,14 @@ should be chosen for those:
* ``conn'' is the xcb_connection_t
* ``event'' is the event of the particular type
* ``container'' names a container
* ``client'' names a client, for example when using a +CIRCLEQ_FOREACH+
* ``con'' names a container
* ``current'' is a loop variable when using +TAILQ_FOREACH+ etc.
== Startup (src/mainx.c, main())
* Establish the xcb connection
* Check for XKB extension on the seperate X connection
* Check for Xinerama screens
* Check for XKB extension on the separate X connection, load Xcursor
* Check for RandR screens (with a fall-back to Xinerama)
* Grab the keycodes for which bindings exist
* Manage all existing windows
* Enter the event loop
@ -303,9 +398,10 @@ the correct state.
Then, it looks through all bindings and gets the one which matches the received
event.
The bound command is parsed directly in command mode.
The bound command is parsed by the cmdparse lexer/parser, see +parse_cmd+ in
+src/cmdparse.y+.
== Manage windows (src/mainx.c, manage_window() and reparent_window())
== Manage windows (src/main.c, manage_window() and reparent_window())
`manage_window()` does some checks to decide whether the window should be
managed at all:
@ -325,7 +421,7 @@ 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,
of the screen. To get the height which needs 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
@ -339,7 +435,7 @@ 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
After reparenting the window, `render_tree()` 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)
@ -388,6 +484,15 @@ src/layout.c, function resize_client().
== Rendering (src/layout.c, render_layout() and render_container())
*********************************************************************************
This section has not been updated for v4.0 yet, sorry! We wanted to release on
time, but we will update this soon. Please talk to us on IRC if you need to
know stuff *NOW* :).
*********************************************************************************
/////////////////////////////////////////////////////////////////////////////////
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
@ -460,7 +565,18 @@ floating windows:
* The new width_factor for each involved column (respectively row) will be
calculated.
== User commands / commandmode (src/commands.c)
/////////////////////////////////////////////////////////////////////////////////
== User commands / commandmode (src/cmdparse.{l,y})
*********************************************************************************
This section has not been updated for v4.0 yet, sorry! We wanted to release on
time, but we will update this soon. Please talk to us on IRC if you need to
know stuff *NOW* :).
*********************************************************************************
/////////////////////////////////////////////////////////////////////////////////
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
@ -485,6 +601,148 @@ 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.
/////////////////////////////////////////////////////////////////////////////////
== Moving containers
The movement code is pretty delicate. You need to consider all cases before
making any changes or before being able to fully understand how it works.
=== Case 1: Moving inside the same container
The reference layout for this case is a single workspace in horizontal
orientation with two containers on it. Focus is on the left container (1).
[width="15%",cols="^,^"]
|========
| 1 | 2
|========
When moving the left window to the right (command +move right+), tree_move will
look for a container with horizontal orientation and finds the parent of the
left container, that is, the workspace. Afterwards, it runs the code branch
commented with "the easy case": it calls TAILQ_NEXT to get the container right
of the current one and swaps both containers.
=== Case 2: Move a container into a split container
The reference layout for this case is a horizontal workspace with two
containers. The right container is a v-split with two containers. Focus is on
the left container (1).
[width="15%",cols="^,^"]
|========
1.2+^.^| 1 | 2
| 3
|========
When moving to the right (command +move right+), i3 will work like in case 1
("the easy case"). However, as the right container is not a leaf container, but
a v-split, the left container (1) will be inserted at the right position (below
2, assuming that 2 is focused inside the v-split) by calling +insert_con_into+.
+insert_con_into+ detaches the container from its parent and inserts it
before/after the given target container. Afterwards, the on_remove_child
callback is called on the old parent container which will then be closed, if
empty.
Afterwards, +con_focus+ will be called to fix the focus stack and the tree will
be flattened.
=== Case 3: Moving to non-existant top/bottom
Like in case 1, the reference layout for this case is a single workspace in
horizontal orientation with two containers on it. Focus is on the left
container:
[width="15%",cols="^,^"]
|========
| 1 | 2
|========
This time however, the command is +move up+ or +move down+. tree_move will look
for a container with vertical orientation. As it will not find any,
+same_orientation+ is NULL and therefore i3 will perform a forced orientation
change on the workspace by creating a new h-split container, moving the
workspace contents into it and then changing the workspace orientation to
vertical. Now it will again search for parent containers with vertical
orientation and it will find the workspace.
This time, the easy case code path will not be run as we are not moving inside
the same container. Instead, +insert_con_into+ will be called with the focused
container and the container above/below the current one (on the level of
+same_orientation+).
Now, +con_focus+ will be called to fix the focus stack and the tree will be
flattened.
=== Case 4: Moving to existant top/bottom
The reference layout for this case is a vertical workspace with two containers.
The bottom one is a h-split containing two containers (1 and 2). Focus is on
the bottom left container (1).
[width="15%",cols="^,^"]
|========
2+| 3
| 1 | 2
|========
This case is very much like case 3, only this time the forced workspace
orientation change does not need to be performed because the workspace already
is in vertical orientation.
=== Case 5: Moving in one-child h-split
The reference layout for this case is a horizontal workspace with two
containers having a v-split on the left side with a one-child h-split on the
bottom. Focus is on the bottom left container (2(h)):
[width="15%",cols="^,^"]
|========
| 1 1.2+^.^| 3
| 2(h)
|========
In this case, +same_orientation+ will be set to the h-split container around
the focused container. However, when trying the easy case, the next/previous
container +swap+ will be NULL. Therefore, i3 will search again for a
+same_orientation+ container, this time starting from the parent of the h-split
container.
After determining a new +same_orientation+ container (if it is NULL, the
orientation will be force-changed), this case is equivalent to case 2 or case
4.
=== Case 6: Floating containers
The reference layout for this case is a horizontal workspace with two
containers plus one floating h-split container. Focus is on the floating
container.
TODO: nice illustration. table not possible?
When moving up/down, the container needs to leave the floating container and it
needs to be placed on the workspace (at workspace level). This is accomplished
by calling the function +attach_to_workspace+.
== Click handling
Without much ado, here is the list of cases which need to be considered:
* click to focus (tiling + floating) and raise (floating)
* click to focus/raise when in stacked/tabbed mode
* floating_modifier + left mouse button to drag a floating con
* floating_modifier + right mouse button to resize a floating con
* click on decoration in a floating con to either initiate a resize (if there
is more than one child in the floating con) or to drag the
floating con (if its the one at the top).
* click on border in a floating con to resize the floating con
* floating_modifier + right mouse button to resize a tiling con
* click on border/decoration to resize a tiling con
== Gotchas
* Forgetting to call `xcb_flush(conn);` after sending a request. This usually

209
docs/ipc
View File

@ -3,16 +3,19 @@ IPC interface (interprocess communication)
Michael Stapelberg <michael+i3@stapelberg.de>
March 2010
This document describes how to interface with i3 from a seperate process. This
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+.
most languages. In the default configuration file, the ipc-socket gets created
in +/tmp/i3-%u/ipc-socket.%p+ where +%u+ is your UNIX username and +%p+ is the
PID of i3.
All i3 utilities, like +i3-msg+ and +i3-input+ will read the +I3_SOCKET_PATH+
X11 property, stored on the X11 root window.
== Establishing a connection
@ -21,7 +24,7 @@ snippet illustrates this in Perl:
-------------------------------------------------------------
use IO::Socket::UNIX;
my $sock = IO::Socket::UNIX->new(Peer => '~/.i3/ipc.sock');
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
-------------------------------------------------------------
== Sending messages to i3
@ -52,6 +55,10 @@ SUBSCRIBE (2)::
GET_OUTPUTS (3)::
Gets the current outputs. The reply will be a JSON-encoded list of outputs
(see the reply section).
GET_TREE (4)::
Gets the layout tree. i3 uses a tree as data structure which includes
every container. The reply will be the JSON-encoded tree (see the reply
section).
So, a typical message could look like this:
--------------------------------------------------
@ -101,6 +108,8 @@ SUBSCRIBE (2)::
Confirmation/Error code for the SUBSCRIBE message.
GET_OUTPUTS (3)::
Reply to the GET_OUTPUTS message.
GET_TREE (4)::
Reply to the GET_TREE message.
=== COMMAND reply
@ -226,6 +235,190 @@ rect (map)::
]
-------------------
=== GET_TREE reply
The reply consists of a serialized tree. Each node in the tree (representing
one container) has at least the properties listed below. While the nodes might
have more properties, please do not use any properties which are not documented
here. They are not yet finalized and will probably change!
id (integer)::
The internal ID (actually a C pointer value) of this container. Do not
make any assumptions about it. You can use it to (re-)identify and
address containers when talking to i3.
name (string)::
The internal name of this container. For all containers which are part
of the tree structure down to the workspace contents, this is set to a
nice human-readable name of the container.
For all other containers, the content is not defined (yet).
border (string)::
Can be either "normal", "none" or "1pixel", dependending on the
containers border style.
layout (string)::
Can be either "default", "stacked", "tabbed", "dockarea" or "output".
Other values might be possible in the future, should we add new
layouts.
orientation (string)::
Can be either "none" (for non-split containers), "horizontal" or
"vertical".
percent (float)::
The percentage which this container takes in its parent. A value of
+null+ means that the percent property does not make sense for this
container, for example for the root container.
rect (map)::
The absolute display coordinates for this container. Display
coordinates means that when you have two 1600x1200 monitors on a single
X11 Display (the standard way), the coordinates of the first window on
the second monitor are +{ "x": 1600, "y": 0, "width": 1600, "height":
1200 }+.
window_rect (map)::
The coordinates of the *actual client window* inside its container.
These coordinates are relative to the container and do not include the
window decoration (which is actually rendered on the parent container).
So, when using the +default+ layout, you will have a 2 pixel border on
each side, making the window_rect +{ "x": 2, "y": 0, "width": 632,
"height": 366 }+ (for example).
geometry (map)::
The original geometry the window specified when i3 mapped it. Used when
switching a window to floating mode, for example.
urgent (bool)::
Whether this container (window or workspace) has the urgency hint set.
focused (bool)::
Whether this container is currently focused.
Please note that in the following example, I have left out some keys/values
which are not relevant for the type of the node. Otherwise, the example would
be by far too long (it already is quite long, despite showing only 1 window and
one dock window).
It is useful to have an overview of the structure before taking a look at the
JSON dump:
* root
** LVDS1
*** topdock
*** content
**** workspace 1
***** window 1
*** bottomdock
**** dock window 1
** VGA1
*Example:*
-----------------------
{
"id": 6875648,
"name": "root",
"rect": {
"x": 0,
"y": 0,
"width": 1280,
"height": 800
},
"nodes": [
{
"id": 6878320,
"name": "LVDS1",
"layout": "output",
"rect": {
"x": 0,
"y": 0,
"width": 1280,
"height": 800
},
"nodes": [
{
"id": 6878784,
"name": "topdock",
"layout": "dockarea",
"orientation": "vertical",
"rect": {
"x": 0,
"y": 0,
"width": 1280,
"height": 0
},
},
{
"id": 6879344,
"name": "content",
"rect": {
"x": 0,
"y": 0,
"width": 1280,
"height": 782
},
"nodes": [
{
"id": 6880464,
"name": "1",
"orientation": "horizontal",
"rect": {
"x": 0,
"y": 0,
"width": 1280,
"height": 782
},
"floating_nodes": [],
"nodes": [
{
"id": 6929968,
"name": "#aa0000",
"border": "normal",
"percent": 1,
"rect": {
"x": 0,
"y": 18,
"width": 1280,
"height": 782
}
}
]
}
]
},
{
"id": 6880208,
"name": "bottomdock",
"layout": "dockarea",
"orientation": "vertical",
"rect": {
"x": 0,
"y": 782,
"width": 1280,
"height": 18
},
"nodes": [
{
"id": 6931312,
"name": "#00aa00",
"percent": 1,
"rect": {
"x": 0,
"y": 782,
"width": 1280,
"height": 18
}
}
]
}
]
}
]
}
------------------------
== Events
[[events]]
@ -242,7 +435,7 @@ 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
seperate connection to receive events.
separate connection to receive events.
=== Subscribing to events
@ -290,8 +483,8 @@ if ($is_event) {
=== workspace event
This event consists of a single serialized map containing a property
+change (string)+ which indicates the type of the change ("focus", "create",
"init", "empty", "urgent").
+change (string)+ which indicates the type of the change ("focus", "init",
"empty", "urgent").
*Example:*
---------------------

192
docs/tree-migrating Normal file
View File

@ -0,0 +1,192 @@
Tree branch: Migrating
======================
Michael Stapelberg <michael+i3@stapelberg.de>
November 2010
== Introduction
The tree branch (referring to a branch of i3 in the git repository) is the new
version of i3. Due to the very deep changes and heavy refactoring of the source
source, we decided to develop it in a separate branch (instead of using the
next/master-branch system like before).
== Current status
Currently, the code is mostly working. Some of the i3 core developers have been
using the tree branch version for a few weeks now. So, if you are eager to try
out the new features and help us find bugs, give it a try!
At the same time, a word of warning is appropriate: This version of i3 might
crash unexpectedly, so please be careful with important data (do not work for
two days without saving…).
== Getting the latest tree branch version
Check out the latest version:
---------------------------------------------
$ git clone -b tree git://code.stapelberg.de/i3
---------------------------------------------
Then build and install it (has the same dependencies as the latest stable i3
version):
-----------------------------
$ cd i3
$ make
$ sudo cp i3 /usr/bin/i3-tree
-----------------------------
…and execute +i3-tree+ instead of +i3+ in your Xsession.
*IMPORTANT:* Please note that configuration file compatibility is not yet done.
So, make sure you use/customize the provided +i3.config+ file.
== Tree
The most important change and reason for the name is that i3 stores all
information about the X11 outputs, workspaces and layout of the windows on them
in a tree. The root node is the X11 root window, followed by the X11 outputs,
then workspaces and finally the windows themselve. In previous versions of i3
we had multiple lists (of outputs, workspaces) and a table for each workspace.
That approach turned out to be complicated to use (snapping), understand and
implement.
=== The tree consists of Containers
The building blocks of our tree are so called +Containers+. A +Container+ can
host a window (meaning an X11 window, one that you can actually see and use,
like a browser). Alternatively, it could contain one or more +Containers+. A
simple example is the workspace: When you start i3 with a single monitor, a
single workspace and you open two terminal windows, you will end up with a tree
like this:
image::tree-layout2.png["layout2",float="right"]
image::tree-shot4.png["shot4",title="Two terminals on standard workspace"]
=== Orientation and Split Containers
[[OrientationSplit]]
It is only natural to use so-called +Split Containers+ in order to build a
layout when using a tree as data structure. In i3, every +Container+ has an
orientation (horizontal, vertical or unspecified). So, in our example with the
workspace, the default orientation of the workspace +Container+ is horizontal
(most monitors are widescreen nowadays). If you change the orientation to
vertical (+Alt+v+ in the default config) and *then* open two terminals, i3 will
configure your windows like this:
image::tree-shot2.png["shot2",title="Vertical Workspace Orientation"]
An interesting new feature of the tree branch is the ability to split anything:
Lets assume you have two terminals on a workspace (with horizontal
orientation), focus is on the right terminal. Now you want to open another
terminal window below the current one. If you would just open a new terminal
window, it would show up to the right due to the horizontal workspace
orientation. Instead, press +Alt+v+ to create a +Vertical Split Container+ (to
open a +Horizontal Split Container+, use +Alt+h+). Now you can open a new
terminal and it will open below the current one:
image::tree-layout1.png["Layout",float="right"]
image::tree-shot1.png["shot",title="Vertical Split Container"]
unfloat::[]
You probably guessed it already: There is no limit on how deep your hierarchy
of splits can be.
=== Level up
Lets stay with our example from above. We have a terminal on the left and two
vertically split terminals on the right, focus is on the bottom right one. When
you open a new terminal, it will open below the current one.
So, how can you open a new terminal window to the *right* of the current one?
The solution is to use +level up+, which will focus the +Parent Container+ of
the current +Container+. In this case, you would focus the +Vertical Split
Container+ which is *inside* the horizontally oriented workspace. Thus, now new
windows will be opened to the right of the +Vertical Split Container+:
image::tree-shot3.png["shot3",title="Level Up, then open new terminal"]
== Commands
The authoritive reference for commands is +src/cmdparse.y+. You can also find
most commands in +i3.config+. Here comes a short overview over the important
commands:
=== Manipulating layout
-------------------------------
layout <default|stacked|tabbed>
-------------------------------
=== Changing Focus
--------------------------
next <horizontal|vertical>
prev <horizontal|vertical>
--------------------------
.Examples:
-------------------------
bindsym Mod1+Left prev h
bindsym Mod1+Right next h
bindsym Mod1+Down next v
bindsym Mod1+Up prev v
-------------------------
=== Moving
-----------------------------------------
move <before|after> <horizontal|vertical>
-----------------------------------------
.Examples:
-----------------------------------------
bindsym Mod1+Shift+Left move before h
bindsym Mod1+Shift+Right move after h
bindsym Mod1+Shift+Down move before v
bindsym Mod1+Shift+Up move after v
-----------------------------------------
=== Changing workspace
---------------------------
workspace <name>
---------------------------
.Examples:
---------------------------
bindsym Mod1+1 workspace 1
bindsym Mod1+2 workspace 2
---------------------------
=== Moving Containers to workspaces
---------------------
move workspace <name>
---------------------
-------------------------------------
bindsym Mod1+Shift+1 move workspace 1
bindsym Mod1+Shift+2 move workspace 2
-------------------------------------
=== Changing border style
---------------------------
border <normal|none|1pixel>
---------------------------
=== Changing container mode
-----------------------------
mode <tiling|floating|toggle>
-----------------------------
== The rest
What is not mentioned here explicitly is either unchanged and can be read in
the http://i3.zekjur.net/docs/userguide.html[i3 Users Guide] or it is not yet
implemented.

File diff suppressed because it is too large Load Diff

45
dump-asy.pl Executable file
View File

@ -0,0 +1,45 @@
#!/usr/bin/env perl
# vim:ts=4:sw=4:expandtab
# renders the layout tree using asymptote
use strict;
use warnings;
use Data::Dumper;
use AnyEvent::I3;
use File::Temp;
use v5.10;
my $i3 = i3("/tmp/nestedcons");
my $tree = $i3->get_tree->recv;
my $tmp = File::Temp->new(UNLINK => 0, SUFFIX => '.asy');
say $tmp "import drawtree;";
say $tmp "treeLevelStep = 2cm;";
sub dump_node {
my ($n, $parent) = @_;
my $o = ($n->{orientation} eq 'none' ? "u" : ($n->{orientation} eq 'horizontal' ? "h" : "v"));
my $w = (defined($n->{window}) ? $n->{window} : "N");
my $na = $n->{name};
$na =~ s/#/\\#/g;
my $name = "($na, $o, $w)";
print $tmp "TreeNode n" . $n->{id} . " = makeNode(";
print $tmp "n" . $parent->{id} . ", " if defined($parent);
print $tmp "\"" . $name . "\");\n";
dump_node($_, $n) for @{$n->{nodes}};
}
dump_node($tree);
say $tmp "draw(n" . $tree->{id} . ", (0, 0));";
close($tmp);
my $rep = "$tmp";
$rep =~ s/asy$/eps/;
system("cd /tmp && asy $tmp && gv $rep && rm $rep");

219
gtk-tree-watch.pl Executable file
View File

@ -0,0 +1,219 @@
#!/usr/bin/env perl
# vim:ts=4:sw=4:expandtab
# renders the layout tree using asymptote
use strict;
use warnings;
use JSON::XS;
use Data::Dumper;
use AnyEvent::I3;
use v5.10;
use Gtk2 '-init';
use Gtk2::SimpleMenu;
use Glib qw/TRUE FALSE/;
my $window = Gtk2::Window->new('toplevel');
$window->signal_connect('delete_event' => sub { Gtk2->main_quit; });
my $tree_store = Gtk2::TreeStore->new(qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/);
my $i3 = i3("/tmp/nestedcons");
my $tree_view = Gtk2::TreeView->new($tree_store);
my $layout_box = undef;
sub copy_node {
my ($n, $parent, $piter, $pbox) = @_;
my $o = ($n->{orientation} == 0 ? "u" : ($n->{orientation} == 1 ? "h" : "v"));
my $w = (defined($n->{window}) ? $n->{window} : "N");
# convert a rectangle struct to X11 notation (WxH+X+Y)
my $r = $n->{rect};
my $x = $r->{x};
my $y = $r->{y};
my $dim = $r->{width}."x".$r->{height}.($x<0?$x:"+$x").($y<0?$y:"+$y");
# add node to the tree with all known properties
my $iter = $tree_store->append($piter);
$tree_store->set($iter, 0 => $n->{name}, 1 => $w, 2 => $o, 3 => sprintf("0x%08x", $n->{id}), 4 => $n->{urgent}, 5 => $n->{focused}, 6 => $n->{layout}, 7 => $dim);
# also create a box for the node, each node has a vbox
# for combining the title (and properties) with the
# container itself, the container will be empty in case
# of no children, a vbox or hbox
my $box;
if($n->{orientation} == 1) {
$box = Gtk2::HBox->new(1, 5);
} else {
$box = Gtk2::VBox->new(1, 5);
}
# combine label and container
my $node = Gtk2::Frame->new($n->{name}.",".$o.",".$w);
$node->set_shadow_type('etched-out');
$node->add($box);
# the parent is added onto a scrolled window, so add it with a viewport
if(defined($pbox)) {
$pbox->pack_start($node, 1, 1, 0);
} else {
$layout_box = $node;
}
# recurse into children
copy_node($_, $n, $iter, $box) for @{$n->{nodes}};
# if it is a window draw a nice color
if(defined($n->{window})) {
# use a drawing area to fill a colored rectangle
my $area = Gtk2::DrawingArea->new();
# the color is stored as hex in the name
$area->{"user-data"} = $n->{name};
$area->signal_connect(expose_event => sub {
my ($widget, $event) = @_;
# fetch a cairo context and it width/height to start drawing nodes
my $cr = Gtk2::Gdk::Cairo::Context->create($widget->window());
my $w = $widget->allocation->width;
my $h = $widget->allocation->height;
my $hc = $widget->{"user-data"};
my $r = hex(substr($hc, 1, 2)) / 255.0;
my $g = hex(substr($hc, 3, 2)) / 255.0;
my $b = hex(substr($hc, 5, 2)) / 255.0;
$cr->set_source_rgb($r, $g, $b);
$cr->rectangle(0, 0, $w, $h);
$cr->fill();
return FALSE;
});
$box->pack_end($area, 1, 1, 0);
}
}
# Replaced by Gtk2 Boxes:
#sub draw_node {
# my ($n, $cr, $x, $y, $w, $h) = @_;
#
# $cr->set_source_rgb(1.0, 1.0, 1.0);
# $cr->rectangle($x, $y, $w/2, $h/2);
# $cr->fill();
#}
my $json_prev = "";
my $layout_sw = Gtk2::ScrolledWindow->new(undef, undef);
my $layout_container = Gtk2::HBox->new(0, 0);
$layout_sw->add_with_viewport($layout_container);
sub copy_tree {
my $tree = $i3->get_tree->recv;
# convert the tree back to json so we only rebuild/redraw when the tree is changed
my $json = encode_json($tree);
if ($json ne $json_prev) {
$json_prev = $json;
# rebuild the tree and the layout
$tree_store->clear();
if(defined($layout_box)) {
$layout_container->remove($layout_box);
}
copy_node($tree);
$layout_container->add($layout_box);
$layout_container->show_all();
# keep things expanded, otherwise the tree collapses every reload which is more annoying then this :-)
$tree_view->expand_all();
}
return(TRUE);
}
sub new_column {
my $tree_column = Gtk2::TreeViewColumn->new();
$tree_column->set_title(shift);
my $renderer = Gtk2::CellRendererText->new();
$tree_column->pack_start($renderer, FALSE);
$tree_column->add_attribute($renderer, text => shift);
return($tree_column);
}
my $col = 0;
$tree_view->append_column(new_column("Name", $col++));
$tree_view->append_column(new_column("Window", $col++));
$tree_view->append_column(new_column("Orientation", $col++));
$tree_view->append_column(new_column("ID", $col++));
$tree_view->append_column(new_column("Urgent", $col++));
$tree_view->append_column(new_column("Focused", $col++));
$tree_view->append_column(new_column("Layout", $col++));
$tree_view->append_column(new_column("Rect", $col++));
$tree_view->set_grid_lines("both");
my $tree_sw = Gtk2::ScrolledWindow->new(undef, undef);
$tree_sw->add($tree_view);
# Replaced by Gtk2 Boxes:
#my $area = Gtk2::DrawingArea->new();
#$area->signal_connect(expose_event => sub {
# my ($widget, $event) = @_;
#
# # fetch a cairo context and it width/height to start drawing nodes
# my $cr = Gtk2::Gdk::Cairo::Context->create($widget->window());
#
# my $w = $widget->allocation->width;
# my $h = $widget->allocation->height;
#
# draw_node($gtree, $cr, 0, 0, $w, $h);
#
# return FALSE;
#});
sub menu_export {
print("TODO: EXPORT\n");
}
my $menu_tree = [
_File => {
item_type => '<Branch>',
children => [
_Export => {
callback => \&menu_export,
accelerator => '<ctrl>E',
},
_Quit => {
callback => sub { Gtk2->main_quit; },
accelerator => '<ctrl>Q',
},
],
},
];
my $menu = Gtk2::SimpleMenu->new(menu_tree => $menu_tree);
my $vbox = Gtk2::VBox->new(0, 0);
$vbox->pack_start($menu->{widget}, 0, 0, 0);
$vbox->pack_end($tree_sw, 1, 1, 0);
$vbox->pack_end($layout_sw, 1, 1, 0);
$window->add($vbox);
$window->show_all();
$window->set_size_request(500,500);
Glib::Timeout->add(1000, "copy_tree", undef, Glib::G_PRIORITY_DEFAULT);
copy_tree();
Gtk2->main();

42
i3-config-wizard/Makefile Normal file
View File

@ -0,0 +1,42 @@
# Default value so one can compile i3-input standalone
TOPDIR=..
include $(TOPDIR)/common.mk
# Depend on the object files of all source-files in src/*.c and on all header files
AUTOGENERATED:=cfgparse.tab.c cfgparse.yy.c
FILES:=$(patsubst %.c,%.o,$(filter-out $(AUTOGENERATED),$(wildcard *.c)))
HEADERS:=$(wildcard *.h)
# Depend on the specific file (.c for each .o) and on all headers
%.o: %.c ${HEADERS}
echo "CC $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
all: i3-config-wizard
i3-config-wizard: cfgparse.y.o cfgparse.yy.o ${FILES}
echo "LINK i3-config-wizard"
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
cfgparse.yy.o: cfgparse.l cfgparse.y.o ${HEADERS}
echo "LEX $<"
flex -i -o$(@:.o=.c) $<
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(@:.o=.c)
cfgparse.y.o: cfgparse.y ${HEADERS}
echo "YACC $<"
bison --debug --verbose -b $(basename $< .y) -d $<
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(<:.y=.tab.c)
install: all
echo "INSTALL"
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
$(INSTALL) -m 0755 i3-config-wizard $(DESTDIR)$(PREFIX)/bin/
clean:
rm -f *.o cfgparse.tab.{c,h} cfgparse.output cfgparse.yy.c
distclean: clean
rm -f i3-config-wizard

View File

@ -0,0 +1,6 @@
xmacro(_NET_WM_NAME)
xmacro(_NET_WM_WINDOW_TYPE)
xmacro(_NET_WM_WINDOW_TYPE_DIALOG)
xmacro(ATOM)
xmacro(CARDINAL)
xmacro(UTF8_STRING)

105
i3-config-wizard/cfgparse.l Normal file
View File

@ -0,0 +1,105 @@
%option nounput
%option noinput
%option noyy_top_state
%option stack
%{
/*
* vim:ts=8:expandtab
*
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "cfgparse.tab.h"
int yycolumn = 1;
struct context {
int line_number;
char *line_copy;
char *compact_error;
/* These are the same as in YYLTYPE */
int first_column;
int last_column;
};
#define YY_DECL int yylex (struct context *context)
#define YY_USER_ACTION { \
context->first_column = yycolumn; \
context->last_column = yycolumn+yyleng-1; \
yycolumn += yyleng; \
}
%}
EOL (\r?\n)
%s BINDCODE_COND
%s BIND_AWS_COND
%s BIND_A2WS_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; }
[0-9]+ { yylval.number = atoi(yytext); return NUMBER; }
bind { BEGIN(BINDCODE_COND); return TOKBINDCODE; }
bindcode { BEGIN(BINDCODE_COND); return TOKBINDCODE; }
Mod1 { yylval.number = (1 << 3); return MODIFIER; }
Mod2 { yylval.number = (1 << 4); return MODIFIER; }
Mod3 { yylval.number = (1 << 5); return MODIFIER; }
Mod4 { yylval.number = (1 << 6); return MODIFIER; }
Mod5 { yylval.number = (1 << 7); return MODIFIER; }
Mode_switch { yylval.number = (1 << 8); return MODIFIER; }
$mod { yylval.number = (1 << 9); return TOKMODVAR; }
control { return TOKCONTROL; }
ctrl { return TOKCONTROL; }
shift { return TOKSHIFT; }
{EOL} {
if (context->line_copy) {
free(context->line_copy);
context->line_copy = NULL;
}
context->line_number++;
BEGIN(INITIAL);
yy_push_state(BUFFER_LINE);
}
<BINDCODE_COND>[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; }
<BIND_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
[ \t]+ { return WHITESPACE; }
. { return (int)yytext[0]; }
<<EOF>> {
while (yy_start_stack_ptr > 0)
yy_pop_state();
yyterminate();
}
%%

161
i3-config-wizard/cfgparse.y Normal file
View File

@ -0,0 +1,161 @@
%{
/*
* vim:ts=4:sw=4:expandtab
*
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
extern Display *dpy;
struct context {
int line_number;
char *line_copy;
char *compact_error;
/* These are the same as in YYLTYPE */
int first_column;
int last_column;
char *result;
};
typedef struct yy_buffer_state *YY_BUFFER_STATE;
extern int yylex(struct context *context);
extern int yyparse(void);
extern FILE *yyin;
YY_BUFFER_STATE yy_scan_string(const char *);
static struct context *context;
/* 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 *error_message) {
fprintf(stderr, "\n");
fprintf(stderr, "CONFIG: %s\n", error_message);
fprintf(stderr, "CONFIG: line %d:\n",
context->line_number);
fprintf(stderr, "CONFIG: %s\n", context->line_copy);
fprintf(stderr, "CONFIG: ");
for (int c = 1; c <= context->last_column; c++)
if (c >= context->first_column)
fprintf(stderr, "^");
else fprintf(stderr, " ");
fprintf(stderr, "\n");
fprintf(stderr, "\n");
}
int yywrap() {
return 1;
}
char *rewrite_binding(const char *bindingline) {
char *result = NULL;
context = calloc(sizeof(struct context), 1);
yy_scan_string(bindingline);
if (yyparse() != 0) {
fprintf(stderr, "Could not parse configfile\n");
exit(1);
}
result = context->result;
if (context->line_copy)
free(context->line_copy);
free(context);
return result;
}
/* XXX: does not work for combinations of modifiers yet */
static char *modifier_to_string(int modifiers) {
//printf("should convert %d to string\n", modifiers);
if (modifiers == (1 << 3))
return strdup("$mod+");
else if (modifiers == ((1 << 3) | (1 << 0)))
return strdup("$mod+Shift+");
else if (modifiers == (1 << 9))
return strdup("$mod+");
else if (modifiers == ((1 << 9) | (1 << 0)))
return strdup("$mod+Shift+");
else if (modifiers == (1 << 0))
return strdup("Shift+");
else return strdup("");
}
%}
%error-verbose
%lex-param { struct context *context }
%union {
int number;
char *string;
}
%token <number>NUMBER "<number>"
%token <string>STR "<string>"
%token TOKBINDCODE
%token TOKMODVAR "$mod"
%token MODIFIER "<modifier>"
%token TOKCONTROL "control"
%token TOKSHIFT "shift"
%token WHITESPACE "<whitespace>"
%%
lines: /* empty */
| lines WHITESPACE bindcode
| lines error
| lines bindcode
;
bindcode:
TOKBINDCODE WHITESPACE binding_modifiers NUMBER WHITESPACE STR
{
//printf("\tFound keycode binding mod%d with key %d and command %s\n", $<number>3, $4, $<string>6);
int level = 0;
if (($<number>3 & (1 << 0))) {
/* When shift is included, we really need to use the second-level
* symbol (upper-case). The lower-case symbol could be on a
* different key than the upper-case one (unlikely for letters, but
* more likely for special characters). */
level = 1;
}
KeySym sym = XKeycodeToKeysym(dpy, $4, level);
char *str = XKeysymToString(sym);
char *modifiers = modifier_to_string($<number>3);
// TODO: modifier to string
asprintf(&(context->result), "bindsym %s%s %s\n", modifiers, str, $<string>6);
free(modifiers);
}
;
binding_modifiers:
/* NULL */ { $<number>$ = 0; }
| binding_modifier
| binding_modifiers '+' binding_modifier { $<number>$ = $<number>1 | $<number>3; }
| binding_modifiers '+' { $<number>$ = $<number>1; }
;
binding_modifier:
MODIFIER { $<number>$ = $<number>1; }
| TOKMODVAR { $<number>$ = $<number>1; }
| TOKCONTROL { $<number>$ = (1 << 2); }
| TOKSHIFT { $<number>$ = (1 << 0); }
;

68
i3-config-wizard/ipc.c Normal file
View File

@ -0,0 +1,68 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <err.h>
/*
* Formats a message (payload) of the given size and type and sends it to i3 via
* the given socket file descriptor.
*
*/
void ipc_send_message(int sockfd, uint32_t message_size,
uint32_t message_type, uint8_t *payload) {
int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
char msg[buffer_size];
char *walk = msg;
strcpy(walk, "i3-ipc");
walk += strlen("i3-ipc");
memcpy(walk, &message_size, sizeof(uint32_t));
walk += sizeof(uint32_t);
memcpy(walk, &message_type, sizeof(uint32_t));
walk += sizeof(uint32_t);
memcpy(walk, payload, message_size);
int sent_bytes = 0;
int bytes_to_go = buffer_size;
while (sent_bytes < bytes_to_go) {
int n = write(sockfd, msg + sent_bytes, bytes_to_go);
if (n == -1)
err(EXIT_FAILURE, "write() failed");
sent_bytes += n;
bytes_to_go -= n;
}
}
/*
* Connects to the i3 IPC socket and returns the file descriptor for the
* socket. die()s if anything goes wrong.
*
*/
int connect_ipc(char *socket_path) {
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd == -1)
err(EXIT_FAILURE, "Could not create socket");
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, socket_path);
if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
err(EXIT_FAILURE, "Could not connect to i3");
return sockfd;
}

9
i3-config-wizard/ipc.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef _IPC_H
#define _IPC_H
void ipc_send_message(int sockfd, uint32_t message_size,
uint32_t message_type, uint8_t *payload);
int connect_ipc(char *socket_path);
#endif

546
i3-config-wizard/main.c Normal file
View File

@ -0,0 +1,546 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2011 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* i3-config-wizard: Program to convert configs using keycodes to configs using
* keysyms.
*
*/
#include <ev.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <err.h>
#include <stdint.h>
#include <getopt.h>
#include <limits.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <glob.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_event.h>
#include <xcb/xcb_keysyms.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
/* We need SYSCONFDIR for the path to the keycode config template, so raise an
* error if its not defined for whatever reason */
#ifndef SYSCONFDIR
#error "SYSCONFDIR not defined"
#endif
#define FREE(pointer) do { \
if (pointer != NULL) { \
free(pointer); \
pointer = NULL; \
} \
} \
while (0)
#include "xcb.h"
#include "ipc.h"
enum { STEP_WELCOME, STEP_GENERATE } current_step = STEP_WELCOME;
enum { MOD_ALT, MOD_SUPER } modifier = MOD_SUPER;
static char *config_path;
static xcb_connection_t *conn;
static uint32_t font_id;
static uint32_t font_bold_id;
static char *socket_path;
static int font_height;
static int font_bold_height;
static xcb_window_t win;
static xcb_pixmap_t pixmap;
static xcb_gcontext_t pixmap_gc;
static xcb_key_symbols_t *symbols;
xcb_window_t root;
Display *dpy;
char *rewrite_binding(const char *bindingline);
static void finish();
#if defined(__APPLE__)
/*
* Taken from FreeBSD
* Returns a pointer to a new string which is a duplicate of the
* string, but only copies at most n characters.
*
*/
char *strndup(const char *str, size_t n) {
size_t len;
char *copy;
for (len = 0; len < n && str[len]; len++)
continue;
if ((copy = malloc(len + 1)) == NULL)
return (NULL);
memcpy(copy, str, len);
copy[len] = '\0';
return (copy);
}
#endif
/*
* This function resolves ~ in pathnames.
* It may resolve wildcards in the first part of the path, but if no match
* or multiple matches are found, it just returns a copy of path as given.
*
*/
static char *resolve_tilde(const char *path) {
static glob_t globbuf;
char *head, *tail, *result;
tail = strchr(path, '/');
head = strndup(path, tail ? tail - path : strlen(path));
int res = glob(head, GLOB_TILDE, NULL, &globbuf);
free(head);
/* no match, or many wildcard matches are bad */
if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1)
result = strdup(path);
else if (res != 0) {
err(1, "glob() failed");
} else {
head = globbuf.gl_pathv[0];
result = calloc(1, strlen(head) + (tail ? strlen(tail) : 0) + 1);
strncpy(result, head, strlen(head));
if (tail)
strncat(result, tail, strlen(tail));
}
globfree(&globbuf);
return result;
}
/*
* Try to get the socket path from X11 and return NULL if it doesnt work.
* As i3-msg is a short-running tool, we dont bother with cleaning up the
* connection and leave it up to the operating system on exit.
*
*/
static char *socket_path_from_x11() {
xcb_connection_t *conn;
int screen;
if ((conn = xcb_connect(NULL, &screen)) == NULL ||
xcb_connection_has_error(conn))
return NULL;
xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
xcb_window_t root = root_screen->root;
xcb_intern_atom_cookie_t atom_cookie;
xcb_intern_atom_reply_t *atom_reply;
atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH");
atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL);
if (atom_reply == NULL)
return NULL;
xcb_get_property_cookie_t prop_cookie;
xcb_get_property_reply_t *prop_reply;
prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
return NULL;
if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply),
(char*)xcb_get_property_value(prop_reply)) == -1)
return NULL;
return socket_path;
}
/*
* Handles expose events, that is, draws the window contents.
*
*/
static int handle_expose() {
/* re-draw the background */
xcb_rectangle_t border = {0, 0, 300, (15*font_height) + 8};
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
#define txt(x, row, text) xcb_image_text_8(conn, strlen(text), pixmap, pixmap_gc, x, (row * font_height) + 2, text)
if (current_step == STEP_WELCOME) {
/* restore font color */
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
txt(10, 2, "You have not configured i3 yet.");
txt(10, 3, "Do you want me to generate ~/.i3/config?");
txt(85, 5, "Yes, generate ~/.i3/config");
txt(85, 7, "No, I will use the defaults");
/* green */
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#00FF00"));
txt(25, 5, "<Enter>");
/* red */
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
txt(31, 7, "<ESC>");
}
if (current_step == STEP_GENERATE) {
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
txt(10, 2, "Please choose either:");
txt(85, 4, "Win as default modifier");
txt(85, 5, "Alt as default modifier");
txt(10, 7, "Afterwards, press");
txt(85, 9, "to write ~/.i3/config");
txt(85, 10, "to abort");
/* the not-selected modifier */
if (modifier == MOD_SUPER)
txt(31, 5, "<Alt>");
else txt(31, 4, "<Win>");
/* the selected modifier */
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_bold_id);
if (modifier == MOD_SUPER)
txt(31, 4, "<Win>");
else txt(31, 5, "<Alt>");
/* green */
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_FONT;
uint32_t values[] = { get_colorpixel(conn, "#00FF00"), font_id };
xcb_change_gc(conn, pixmap_gc, mask, values);
txt(25, 9, "<Enter>");
/* red */
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
txt(31, 10, "<ESC>");
}
/* Copy the contents of the pixmap to the real window */
xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, 500);
xcb_flush(conn);
return 1;
}
static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
printf("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);
/* Only use the lower 8 bits of the state (modifier masks) so that mouse
* button masks are filtered out */
state_filtered &= 0xFF;
xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, state_filtered);
printf("sym = %c (%d)\n", sym, sym);
if (sym == XK_Return || sym == XK_KP_Enter) {
if (current_step == STEP_WELCOME) {
current_step = STEP_GENERATE;
/* Set window title */
xcb_change_property(conn,
XCB_PROP_MODE_REPLACE,
win,
A__NET_WM_NAME,
A_UTF8_STRING,
8,
strlen("i3: generate config"),
"i3: generate config");
xcb_flush(conn);
}
else finish();
}
/* cancel any time */
if (sym == XK_Escape)
exit(0);
if (sym == XK_Alt_L)
modifier = MOD_ALT;
if (sym == XK_Super_L)
modifier = MOD_SUPER;
handle_expose();
return 1;
}
/*
* Creates the config file and tells i3 to reload.
*
*/
static void finish() {
printf("creating \"%s\"...\n", config_path);
if (!(dpy = XOpenDisplay(NULL)))
errx(1, "Could not connect to X11");
FILE *kc_config = fopen(SYSCONFDIR "/i3/config.keycodes", "r");
if (kc_config == NULL)
err(1, "Could not open input file \"%s\"", SYSCONFDIR "/i3/config.keycodes");
FILE *ks_config = fopen(config_path, "w");
if (ks_config == NULL)
err(1, "Could not open output config file \"%s\"", config_path);
char *line = NULL;
size_t len = 0;
#if !defined(__APPLE__)
ssize_t read;
#endif
bool head_of_file = true;
/* write a header about auto-generation to the output file */
fputs("# This file has been auto-generated by i3-config-wizard(1).\n", ks_config);
fputs("# It will not be overwritten, so edit it as you like.\n", ks_config);
fputs("#\n", ks_config);
fputs("# Should you change your keyboard layout somewhen, delete\n", ks_config);
fputs("# this file and re-run i3-config-wizard(1).\n", ks_config);
fputs("#\n", ks_config);
#if defined(__APPLE__)
while ((line = fgetln(kc_config, &len)) != NULL) {
#else
while ((read = getline(&line, &len, kc_config)) != -1) {
#endif
/* skip the warning block at the beginning of the input file */
if (head_of_file &&
strncmp("# WARNING", line, strlen("# WARNING")) == 0)
continue;
head_of_file = false;
/* Skip leading whitespace */
char *walk = line;
while (isspace(*walk) && walk < (line + len))
walk++;
/* Set the modifier the user chose */
if (strncmp(walk, "set $mod ", strlen("set $mod ")) == 0) {
if (modifier == MOD_ALT)
fputs("set $mod Mod1\n", ks_config);
else fputs("set $mod Mod4\n", ks_config);
continue;
}
/* Check for 'bindcode'. If its not a bindcode line, we
* just copy it to the output file */
if (strncmp(walk, "bindcode", strlen("bindcode")) != 0) {
fputs(line, ks_config);
continue;
}
char *result = rewrite_binding(walk);
fputs(result, ks_config);
free(result);
}
/* sync to do our best in order to have the file really stored on disk */
fflush(ks_config);
fsync(fileno(ks_config));
free(line);
fclose(kc_config);
fclose(ks_config);
/* tell i3 to reload the config file */
int sockfd = connect_ipc(socket_path);
ipc_send_message(sockfd, strlen("reload"), 0, (uint8_t*)"reload");
close(sockfd);
exit(0);
}
int main(int argc, char *argv[]) {
config_path = resolve_tilde("~/.i3/config");
socket_path = getenv("I3SOCK");
char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
char *patternbold = "-misc-fixed-bold-r-normal--13-120-75-75-C-70-iso10646-1";
int o, option_index = 0;
static struct option long_options[] = {
{"socket", required_argument, 0, 's'},
{"version", no_argument, 0, 'v'},
{"limit", required_argument, 0, 'l'},
{"prompt", required_argument, 0, 'P'},
{"prefix", required_argument, 0, 'p'},
{"font", required_argument, 0, 'f'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
char *options_string = "s:vh";
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
switch (o) {
case 's':
FREE(socket_path);
socket_path = strdup(optarg);
break;
case 'v':
printf("i3-config-wizard " I3_VERSION "\n");
return 0;
case 'h':
printf("i3-config-wizard " I3_VERSION "\n");
printf("i3-config-wizard [-s <socket>] [-v]\n");
return 0;
}
}
/* Check if the destination config file does not exist but the path is
* writable. If not, exit now, this program is not useful in that case. */
struct stat stbuf;
if (stat(config_path, &stbuf) == 0) {
printf("The config file \"%s\" already exists. Exiting.\n", config_path);
return 0;
}
/* Create ~/.i3 if it does not yet exist */
char *config_dir = resolve_tilde("~/.i3");
if (stat(config_dir, &stbuf) != 0)
if (mkdir(config_dir, 0755) == -1)
err(1, "mkdir(%s) failed", config_dir);
free(config_dir);
int fd;
if ((fd = open(config_path, O_CREAT | O_RDWR, 0644)) == -1) {
printf("Cannot open file \"%s\" for writing: %s. Exiting.\n", config_path, strerror(errno));
return 0;
}
close(fd);
unlink(config_path);
if (socket_path == NULL)
socket_path = socket_path_from_x11();
if (socket_path == NULL)
socket_path = "/tmp/i3-ipc.sock";
int screens;
if ((conn = xcb_connect(NULL, &screens)) == NULL ||
xcb_connection_has_error(conn))
errx(1, "Cannot open display\n");
/* Place requests for the atoms we need as soon as possible */
#define xmacro(atom) \
xcb_intern_atom_cookie_t atom ## _cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
#include "atoms.xmacro"
#undef xmacro
xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
root = root_screen->root;
xcb_get_numlock_mask(conn);
symbols = xcb_key_symbols_alloc(conn);
font_id = get_font_id(conn, pattern, &font_height);
font_bold_id = get_font_id(conn, patternbold, &font_bold_height);
/* Open an input window */
win = open_input_window(conn, 300, 205);
/* Setup NetWM atoms */
#define xmacro(name) \
do { \
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name ## _cookie, NULL); \
if (!reply) \
errx(EXIT_FAILURE, "Could not get atom " # name "\n"); \
\
A_ ## name = reply->atom; \
free(reply); \
} while (0);
#include "atoms.xmacro"
#undef xmacro
/* Set dock mode */
xcb_change_property(conn,
XCB_PROP_MODE_REPLACE,
win,
A__NET_WM_WINDOW_TYPE,
A_ATOM,
32,
1,
(unsigned char*) &A__NET_WM_WINDOW_TYPE_DIALOG);
/* Set window title */
xcb_change_property(conn,
XCB_PROP_MODE_REPLACE,
win,
A__NET_WM_NAME,
A_UTF8_STRING,
8,
strlen("i3: first configuration"),
"i3: first configuration");
/* Create pixmap */
pixmap = xcb_generate_id(conn);
pixmap_gc = xcb_generate_id(conn);
xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, 500);
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
/* Grab the keyboard to get all input */
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);
xcb_generic_event_t *event;
while ((event = xcb_wait_for_event(conn)) != NULL) {
if (event->response_type == 0) {
fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
continue;
}
/* Strip off the highest bit (set if the event is generated) */
int type = (event->response_type & 0x7F);
switch (type) {
case XCB_KEY_PRESS:
handle_key_press(NULL, conn, (xcb_key_press_event_t*)event);
break;
/* TODO: handle mappingnotify */
case XCB_EXPOSE:
handle_expose();
break;
}
free(event);
}
return 0;
}

221
i3-config-wizard/xcb.c Normal file
View File

@ -0,0 +1,221 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <err.h>
#include <xcb/xcb.h>
#include <xcb/xcb_keysyms.h>
#include <X11/keysym.h>
#include "xcb.h"
extern xcb_window_t root;
unsigned int xcb_numlock_mask;
/*
* Convenience-wrapper around xcb_change_gc which saves us declaring a variable
*
*/
void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
xcb_change_gc(conn, gc, mask, &value);
}
/*
* Returns the colorpixel to use for the given hex color (think of HTML).
*
* The hex_color has to start with #, for example #FF00FF.
*
* NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
* This has to be done by the caller.
*
*/
uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
char strgroups[3][3] = {{hex[1], hex[2], '\0'},
{hex[3], hex[4], '\0'},
{hex[5], hex[6], '\0'}};
uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
(strtol(strgroups[1], NULL, 16)),
(strtol(strgroups[2], NULL, 16))};
return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
}
/*
* Returns the mask for Mode_switch (to be used for looking up keysymbols by
* keycode).
*
*/
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, keycode);
if (modeswitchcodes == NULL)
return 0;
modmap_r = xcb_get_modifier_mapping_reply(conn, xcb_get_modifier_mapping(conn), NULL);
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++) {
kc = modmap[i * modmap_r->keycodes_per_modifier + j];
for (xcb_keycode_t *ktest = modeswitchcodes; *ktest; ktest++) {
if (*ktest != kc)
continue;
free(modeswitchcodes);
free(modmap_r);
return (1 << i);
}
}
return 0;
}
/*
* Opens the window we use for input/output and maps it
*
*/
xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) {
xcb_window_t win = xcb_generate_id(conn);
//xcb_cursor_t cursor_id = xcb_generate_id(conn);
#if 0
/* 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);
}
#endif
uint32_t mask = 0;
uint32_t values[3];
mask |= XCB_CW_BACK_PIXEL;
values[0] = 0;
mask |= XCB_CW_EVENT_MASK;
values[1] = XCB_EVENT_MASK_EXPOSURE;
xcb_create_window(conn,
XCB_COPY_FROM_PARENT,
win, /* the window id */
root, /* parent == root */
490, 297, 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);
#if 0
if (cursor > -1)
xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
#endif
/* Map the window (= make it visible) */
xcb_map_window(conn, win);
return win;
}
/*
* Returns the ID of the font matching the given pattern and stores the height
* of the font (in pixels) in *font_height. die()s if no font matches.
*
*/
int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height) {
xcb_void_cookie_t font_cookie;
xcb_list_fonts_with_info_cookie_t info_cookie;
/* Send all our requests first */
int result;
result = xcb_generate_id(conn);
font_cookie = xcb_open_font_checked(conn, result, strlen(pattern), pattern);
info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
if (error != NULL) {
fprintf(stderr, "ERROR: Could not open font: %d\n", error->error_code);
exit(1);
}
/* Get information (height/name) for this font */
xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
if (reply == NULL)
errx(1, "Could not load font \"%s\"\n", pattern);
*font_height = reply->font_ascent + reply->font_descent;
return result;
}
/*
* Finds out which modifier mask is the one for numlock, as the user may change this.
*
*/
void xcb_get_numlock_mask(xcb_connection_t *conn) {
xcb_key_symbols_t *keysyms;
xcb_get_modifier_mapping_cookie_t cookie;
xcb_get_modifier_mapping_reply_t *reply;
xcb_keycode_t *modmap;
int mask, i;
const int masks[8] = { XCB_MOD_MASK_SHIFT,
XCB_MOD_MASK_LOCK,
XCB_MOD_MASK_CONTROL,
XCB_MOD_MASK_1,
XCB_MOD_MASK_2,
XCB_MOD_MASK_3,
XCB_MOD_MASK_4,
XCB_MOD_MASK_5 };
/* Request the modifier map */
cookie = xcb_get_modifier_mapping_unchecked(conn);
/* Get the keysymbols */
keysyms = xcb_key_symbols_alloc(conn);
if ((reply = xcb_get_modifier_mapping_reply(conn, cookie, NULL)) == NULL) {
xcb_key_symbols_free(keysyms);
return;
}
modmap = xcb_get_modifier_mapping_keycodes(reply);
/* Get the keycode for numlock */
#ifdef OLD_XCB_KEYSYMS_API
xcb_keycode_t numlock = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
#else
/* For now, we only use the first keysymbol. */
xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
if (numlock_syms == NULL)
return;
xcb_keycode_t numlock = *numlock_syms;
free(numlock_syms);
#endif
/* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */
for (mask = 0; mask < 8; mask++)
for (i = 0; i < reply->keycodes_per_modifier; i++)
if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock)
xcb_numlock_mask = masks[mask];
xcb_key_symbols_free(keysyms);
free(reply);
}

24
i3-config-wizard/xcb.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef _XCB_H
#define _XCB_H
/* from X11/keysymdef.h */
#define XCB_NUM_LOCK 0xff7f
#define xmacro(atom) xcb_atom_t A_ ## atom;
#include "atoms.xmacro"
#undef xmacro
extern unsigned int xcb_numlock_mask;
void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value);
uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode);
xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height);
int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height);
/**
* Finds out which modifier mask is the one for numlock, as the user may change this.
*
*/
void xcb_get_numlock_mask(xcb_connection_t *conn);
#endif

View File

@ -10,11 +10,13 @@ HEADERS=$(wildcard *.h)
# Depend on the specific file (.c for each .o) and on all headers
%.o: %.c ${HEADERS}
echo "CC $<"
$(CC) $(CFLAGS) -c -o $@ $<
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
all: ${FILES}
all: i3-input
i3-input: ${FILES}
echo "LINK i3-input"
$(CC) -o i3-input ${FILES} $(LDFLAGS)
$(CC) $(LDFLAGS) -o $@ ${FILES} $(LIBS)
install: all
echo "INSTALL"

View File

@ -4,6 +4,15 @@
#include <err.h>
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
#define FREE(pointer) do { \
if (pointer != NULL) { \
free(pointer); \
pointer = NULL; \
} \
} \
while (0)
extern xcb_window_t root;
char *convert_ucs_to_utf8(char *input);
char *convert_utf8_to_ucs2(char *input, int *real_strlen);

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2010 Michael Stapelberg and contributors
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -22,7 +22,7 @@
#include <err.h>
#include <stdint.h>
#include <getopt.h>
#include <glob.h>
#include <limits.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
@ -35,6 +35,7 @@
#include "i3-input.h"
static char *socket_path;
static int sockfd;
static xcb_key_symbols_t *symbols;
static int modeswitchmask;
@ -51,20 +52,42 @@ static char *command_prefix;
static char *prompt;
static int prompt_len;
static int limit;
xcb_window_t root;
/*
* This function resolves ~ in pathnames (and more, see glob(3)).
* Try to get the socket path from X11 and return NULL if it doesnt work.
* As i3-msg is a short-running tool, we dont bother with cleaning up the
* connection and leave it up to the operating system on exit.
*
*/
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;
static char *socket_path_from_x11() {
xcb_connection_t *conn;
int screen;
if ((conn = xcb_connect(NULL, &screen)) == NULL ||
xcb_connection_has_error(conn))
return NULL;
xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
xcb_window_t root = root_screen->root;
xcb_intern_atom_cookie_t atom_cookie;
xcb_intern_atom_reply_t *atom_reply;
atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH");
atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL);
if (atom_reply == NULL)
return NULL;
xcb_get_property_cookie_t prop_cookie;
xcb_get_property_reply_t *prop_reply;
prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
return NULL;
if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply),
(char*)xcb_get_property_value(prop_reply)) == -1)
return NULL;
return socket_path;
}
/*
@ -257,7 +280,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
}
int main(int argc, char *argv[]) {
char *socket_path = glob_path("~/.i3/ipc.sock");
socket_path = getenv("I3SOCK");
char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
int o, option_index = 0;
@ -277,21 +300,25 @@ 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 = glob_path(optarg);
FREE(socket_path);
socket_path = strdup(optarg);
break;
case 'v':
printf("i3-input " I3_VERSION);
return 0;
case 'p':
FREE(command_prefix);
command_prefix = strdup(optarg);
break;
case 'l':
limit = atoi(optarg);
break;
case 'P':
FREE(prompt);
prompt = strdup(optarg);
break;
case 'f':
FREE(pattern);
pattern = strdup(optarg);
break;
case 'h':
@ -301,6 +328,12 @@ int main(int argc, char *argv[]) {
}
}
if (socket_path == NULL)
socket_path = socket_path_from_x11();
if (socket_path == NULL)
socket_path = "/tmp/i3-ipc.sock";
sockfd = connect_ipc(socket_path);
if (prompt != NULL)
@ -311,13 +344,8 @@ int main(int argc, char *argv[]) {
if (xcb_connection_has_error(conn))
die("Cannot open display\n");
/* Set up event handlers for key press and key release */
xcb_event_handlers_t evenths;
memset(&evenths, 0, sizeof(xcb_event_handlers_t));
xcb_event_handlers_init(conn, &evenths);
xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
xcb_event_set_expose_handler(&evenths, handle_expose, NULL);
xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
root = root_screen->root;
modeswitchmask = get_mod_mask(conn, XK_Mode_switch);
numlockmask = get_mod_mask(conn, XK_Num_Lock);
@ -329,8 +357,6 @@ int main(int argc, char *argv[]) {
win = open_input_window(conn, 500, font_height + 8);
/* Create pixmap */
xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
pixmap = xcb_generate_id(conn);
pixmap_gc = xcb_generate_id(conn);
xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8);
@ -366,7 +392,32 @@ int main(int argc, char *argv[]) {
xcb_flush(conn);
xcb_event_wait_for_event_loop(&evenths);
xcb_generic_event_t *event;
while ((event = xcb_wait_for_event(conn)) != NULL) {
if (event->response_type == 0) {
fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
continue;
}
/* Strip off the highest bit (set if the event is generated) */
int type = (event->response_type & 0x7F);
switch (type) {
case XCB_KEY_PRESS:
handle_key_press(NULL, conn, (xcb_key_press_event_t*)event);
break;
case XCB_KEY_RELEASE:
handle_key_release(NULL, conn, (xcb_key_release_event_t*)event);
break;
case XCB_EXPOSE:
handle_expose(NULL, conn, (xcb_expose_event_t*)event);
break;
}
free(event);
}
return 0;
}

View File

@ -86,7 +86,6 @@ uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode) {
*
*/
xcb_window_t open_input_window(xcb_connection_t *conn, 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);
//xcb_cursor_t cursor_id = xcb_generate_id(conn);

358
i3-migrate-config-to-v4.pl Executable file
View File

@ -0,0 +1,358 @@
#!/usr/bin/env perl
# vim:ts=4:sw=4:expandtab
#
# script to migrate an old config file (i3 < 4.0) to the new format (>= 4.0)
# this script only uses modules which come with perl 5.10
#
# reads an i3 v3 config from stdin and spits out a v4 config on stdout
# exit codes:
# 0 = the input was a v3 config
# 1 = the input was already a v4 config
# (the config is printed to stdout nevertheless)
#
# © 2011 Michael Stapelberg and contributors, see LICENSE
use strict;
use warnings;
use Getopt::Long;
use v5.10;
# is this a version 3 config file? disables auto-detection
my $v3 = 0;
my $result = GetOptions('v3' => \$v3);
# reads stdin
sub slurp {
local $/;
<>;
}
my @unchanged = qw(
font
set
mode
exec
assign
floating_modifier
focus_follows_mouse
ipc-socket
ipc_socket
client.focused
client.focused_inactive
client.unfocused
client.urgent
client.background
);
my %workspace_names;
my $workspace_bar = 1;
my $input = slurp();
my @lines = split /\n/, $input;
# remove whitespaces in the beginning of lines
@lines = map { s/^[ \t]*//g; $_ } @lines;
# Try to auto-detect if this is a v3 config file.
sub need_to_convert {
# If the user passed --v3, we need to convert in any case
return 1 if $v3;
for my $line (@lines) {
# only v4 configfiles can use bindcode or workspace_layout
return 0 if $line =~ /^bindcode/ ||
$line =~ /^workspace_layout/ ||
$line =~ /^force_focus_wrapping/;
# have a look at bindings
next unless $line =~ /^bind/;
my ($statement, $key, $command) = ($line =~ /([a-zA-Z_-]+)[ \t]+([^ \t]+)[ \t]+(.*)/);
return 0 if $command =~ /^layout/ ||
$command =~ /^floating/ ||
$command =~ /^workspace/ ||
$command =~ /^focus (left|right|up|down)/ ||
$command =~ /^border (normal|1pixel|none)/;
}
return 1;
}
if (!need_to_convert()) {
# If this is already a v4 config file, we will spit out the lines
# and exit with return code 1
print $input;
exit 1;
}
# first pass: get workspace names
for my $line (@lines) {
next if $line =~ /^#/ || $line =~ /^$/;
my ($statement, $parameters) = ($line =~ /([a-zA-Z._-]+)(.*)/);
# skip everything but workspace lines
next unless defined($statement) and $statement eq 'workspace';
my ($number, $params) = ($parameters =~ /[ \t]+([0-9]+) (.+)/);
# save workspace name (unless the line is actually a workspace assignment)
$workspace_names{$number} = $params unless $params =~ /^output/;
}
for my $line (@lines) {
# directly use comments and empty lines
if ($line =~ /^#/ || $line =~ /^$/) {
print "$line\n";
next;
}
my ($statement, $parameters) = ($line =~ /([a-zA-Z._-]+)(.*)/);
# directly use lines which have not changed between 3.x and 4.x
if (!defined($statement) || (lc $statement ~~ @unchanged)) {
print "$line\n";
next;
}
# new_container changed only the statement name to workspace_layout
if ($statement eq 'new_container') {
# TODO: new_container stack-limit
print "workspace_layout$parameters\n";
next;
}
# workspace_bar is gone, you should use i3bar now
if ($statement eq 'workspace_bar') {
$workspace_bar = ($parameters =~ /[ \t+](yes|true|on|enable|active)/);
print "# XXX: REMOVED workspace_bar line. There is no internal workspace bar in v4.\n";
next;
}
# new_window changed the parameters from bb to none etc.
if ($statement eq 'new_window') {
if ($parameters =~ /bb/) {
print "new_window none\n";
} elsif ($parameters =~ /bp/) {
print "new_window 1pixel\n";
} elsif ($parameters =~ /bn/) {
print "new_window normal\n";
} else {
print "# XXX: Invalid parameter for new_window, not touching line:\n";
print "$line\n";
}
next;
}
# bar colors are obsolete, need to be configured in i3bar
if ($statement =~ /^bar\./) {
print "# XXX: REMOVED $statement, configure i3bar instead.\n";
print "# Old line: $line\n";
next;
}
# one form of this is still ok (workspace assignments), the other (named workspaces) isnt
if ($statement eq 'workspace') {
my ($number, $params) = ($parameters =~ /[ \t]+([0-9]+) (.+)/);
if ($params =~ /^output/) {
print "$line\n";
next;
} else {
print "# XXX: workspace name will end up in the corresponding bindings.\n";
next;
}
}
if ($statement eq 'bind' || $statement eq 'bindsym') {
convert_command($line);
next;
}
print "# XXX: migration script could not handle line: $line\n";
}
#
# Converts a command (after bind/bindsym)
#
sub convert_command {
my ($line) = @_;
my @unchanged_cmds = qw(
exec
mark
kill
restart
reload
exit
stack-limit
);
my ($statement, $key, $command) = ($line =~ /([a-zA-Z_-]+)[ \t]+([^ \t]+)[ \t]+(.*)/);
# turn 'bind' to 'bindcode'
$statement = 'bindcode' if $statement eq 'bind';
# check if its one of the unchanged commands
my ($cmd) = ($command =~ /([a-zA-Z_-]+)/);
if ($cmd ~~ @unchanged_cmds) {
print "$statement $key $command\n";
return;
}
# simple replacements
my @replace = (
qr/^s/ => 'layout stacking',
qr/^d/ => 'layout default',
qr/^T/ => 'layout tabbed',
qr/^f($|[^go])/ => 'fullscreen',
qr/^fg/ => 'fullscreen global',
qr/^t/ => 'floating toggle',
qr/^h/ => 'focus left',
qr/^j($|[^u])/ => 'focus down',
qr/^k/ => 'focus up',
qr/^l/ => 'focus right',
qr/^mh/ => 'move left',
qr/^mj/ => 'move down',
qr/^mk/ => 'move up',
qr/^ml/ => 'move right',
qr/^bn/ => 'border normal',
qr/^bp/ => 'border 1pixel',
qr/^bb/ => 'border none',
qr/^bt/ => 'border toggle',
qr/^pw/ => 'workspace prev',
qr/^nw/ => 'workspace next',
);
my $replaced = 0;
for (my $c = 0; $c < @replace; $c += 2) {
if ($command =~ $replace[$c]) {
$command = $replace[$c+1];
$replaced = 1;
last;
}
}
# goto command is now obsolete due to criteria + focus command
if ($command =~ /^goto/) {
my ($mark) = ($command =~ /^goto (.*)/);
print qq|$statement $key [con_mark="$mark"] focus\n|;
return;
}
# the jump command is also obsolete due to criteria + focus
if ($command =~ /^jump/) {
my ($params) = ($command =~ /^jump (.*)/);
if ($params =~ /^"/) {
# jump ["]window class[/window title]["]
($params) = ($params =~ /^"([^"]+)"/);
# check if a window title was specified
if ($params =~ m,/,) {
my ($class, $title) = ($params =~ m,([^/]+)/(.+),);
print qq|$statement $key [class="$class" title="$title"] focus\n|;
} else {
print qq|$statement $key [class="$params"] focus\n|;
}
return;
} else {
# jump <workspace> [ column row ]
print "# XXX: jump workspace is obsolete in 4.x: $line\n";
return;
}
}
if (!$replaced && $command =~ /^focus/) {
my ($what) = ($command =~ /^focus (.*)/);
$what =~ s/[ \t]//g;
if ($what eq 'ft') {
$what = 'mode_toggle';
} elsif ($what eq 'floating' || $what eq 'tiling') {
# those are unchanged
} else {
print "# XXX: focus <number> is obsolete in 4.x: $line\n";
return;
}
print qq|$statement $key focus $what\n|;
return;
}
if ($command =~ /^mode/) {
my ($parameters) = ($command =~ /^mode (.*)/);
print qq|$statement $key mode "$parameters"\n|;
return;
}
# the parameters of the resize command have changed
if ($command =~ /^resize/) {
# OLD: resize <left|right|top|bottom> [+|-]<pixels>\n")
# NEW: resize <grow|shrink> <direction> [<px> px] [or <ppt> ppt]
my ($direction, $sign, $px) = ($command =~ /^resize (left|right|top|bottom) ([+-])([0-9]+)/);
my $cmd = 'resize ';
$cmd .= ($sign eq '+' ? 'grow' : 'shrink') . ' ';
$cmd .= "$direction ";
$cmd .= "$px px";
print qq|$statement $key $cmd\n|;
return;
}
# switch workspace
if ($command =~ /^[0-9]+/) {
my ($number) = ($command =~ /^([0-9]+)/);
if (exists $workspace_names{$number}) {
print qq|$statement $key workspace $workspace_names{$number}\n|;
return;
} else {
print qq|$statement $key workspace $number\n|;
return;
}
}
# move to workspace
if ($command =~ /^m[0-9]+/) {
my ($number) = ($command =~ /^m([0-9]+)/);
if (exists $workspace_names{$number}) {
print qq|$statement $key move workspace $workspace_names{$number}\n|;
return;
} else {
print qq|$statement $key move workspace $number\n|;
return;
}
}
# With Container-commands are now obsolete due to different abstraction
if ($command =~ /^wc/) {
$command =~ s/^wc//g;
my $wc_replaced = 0;
for (my $c = 0; $c < @replace; $c += 2) {
if ($command =~ $replace[$c]) {
$command = $replace[$c+1];
$wc_replaced = 1;
last;
}
}
if (!$wc_replaced) {
print "# XXX: migration script could not handle command: $line\n";
} else {
# NOTE: This is not 100% accurate, as it only works for one level
# of nested containers. As this is a common use case, we use 'focus
# parent; $command' nevertheless. For advanced use cases, the user
# has to modify his config.
print "$statement $key focus parent; $command\n";
}
return;
}
if ($replaced) {
print "$statement $key $command\n";
} else {
print "# XXX: migration script could not handle command: $line\n";
}
}
# add an i3bar invocation automatically if no 'workspace_bar no' was found
if ($workspace_bar) {
print "\n";
print "# XXX: Automatically added a call to i3bar to provide a workspace bar\n";
print "exec i3status | i3bar -d\n";
}

View File

@ -12,11 +12,13 @@ HEADERS=$(wildcard *.h)
# Depend on the specific file (.c for each .o) and on all headers
%.o: %.c ${HEADERS}
echo "CC $<"
$(CC) $(CFLAGS) -c -o $@ $<
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
all: ${FILES}
all: i3-msg
i3-msg: ${FILES}
echo "LINK i3-msg"
$(CC) -o i3-msg ${FILES} $(LDFLAGS)
$(CC) $(LDFLAGS) -o i3-msg ${FILES} $(LIBS)
install: all
echo "INSTALL"

View File

@ -1,5 +1,5 @@
/*
* vim:ts=8:expandtab
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
*
@ -27,23 +27,49 @@
#include <err.h>
#include <stdint.h>
#include <getopt.h>
#include <glob.h>
#include <limits.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <i3/ipc.h>
static char *socket_path;
/*
* This function resolves ~ in pathnames (and more, see glob(3)).
* Try to get the socket path from X11 and return NULL if it doesnt work.
* As i3-msg is a short-running tool, we dont bother with cleaning up the
* connection and leave it up to the operating system on exit.
*
*/
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;
static char *socket_path_from_x11() {
xcb_connection_t *conn;
int screen;
if ((conn = xcb_connect(NULL, &screen)) == NULL ||
xcb_connection_has_error(conn))
return NULL;
xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
xcb_window_t root = root_screen->root;
xcb_intern_atom_cookie_t atom_cookie;
xcb_intern_atom_reply_t *atom_reply;
atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH");
atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL);
if (atom_reply == NULL)
return NULL;
xcb_get_property_cookie_t prop_cookie;
xcb_get_property_reply_t *prop_reply;
prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
return NULL;
if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply),
(char*)xcb_get_property_value(prop_reply)) == -1)
return NULL;
return socket_path;
}
/*
@ -53,144 +79,174 @@ static char *glob_path(const char *path) {
*/
static void ipc_send_message(int sockfd, uint32_t message_size,
uint32_t message_type, uint8_t *payload) {
int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
char msg[buffer_size];
char *walk = msg;
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_MAGIC);
walk += strlen(I3_IPC_MAGIC);
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);
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));
walk += sizeof(uint32_t);
memcpy(walk, payload, message_size);
int sent_bytes = 0;
int bytes_to_go = buffer_size;
while (sent_bytes < bytes_to_go) {
int n = write(sockfd, msg + sent_bytes, bytes_to_go);
if (n == -1)
err(EXIT_FAILURE, "write() failed");
int sent_bytes = 0;
int bytes_to_go = buffer_size;
while (sent_bytes < bytes_to_go) {
int n = write(sockfd, msg + sent_bytes, bytes_to_go);
if (n == -1)
err(EXIT_FAILURE, "write() failed");
sent_bytes += n;
bytes_to_go -= n;
}
sent_bytes += n;
bytes_to_go -= n;
}
}
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;
/* 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");
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;
}
read_bytes += n;
to_read -= n;
}
if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0)
errx(EXIT_FAILURE, "invalid magic in reply");
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);
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");
*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");
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;
}
read_bytes += n;
to_read -= n;
}
}
int main(int argc, char *argv[]) {
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;
socket_path = getenv("I3SOCK");
int o, option_index = 0;
int message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
char *payload = NULL;
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}
};
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:vhq";
char *options_string = "s:t:vhq";
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
if (o == 's') {
socket_path = glob_path(optarg);
} else if (o == 't') {
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;
} else if (o == 'h') {
printf("i3-msg " I3_VERSION);
printf("i3-msg [-s <socket>] [-t <type>] <message>\n");
return 0;
}
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
if (o == 's') {
if (socket_path != NULL)
free(socket_path);
socket_path = strdup(optarg);
} else if (o == 't') {
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 if (strcasecmp(optarg, "get_outputs") == 0)
message_type = I3_IPC_MESSAGE_TYPE_GET_OUTPUTS;
else if (strcasecmp(optarg, "get_tree") == 0)
message_type = I3_IPC_MESSAGE_TYPE_GET_TREE;
else {
printf("Unknown message type\n");
printf("Known types: command, get_workspaces, get_outputs, get_tree\n");
exit(EXIT_FAILURE);
}
} else if (o == 'q') {
quiet = true;
} else if (o == 'v') {
printf("i3-msg " I3_VERSION "\n");
return 0;
} else if (o == 'h') {
printf("i3-msg " I3_VERSION "\n");
printf("i3-msg [-s <socket>] [-t <type>] <message>\n");
return 0;
}
}
if (optind < argc)
payload = argv[optind];
if (socket_path == NULL)
socket_path = socket_path_from_x11();
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd == -1)
err(EXIT_FAILURE, "Could not create socket");
/* Fall back to the default socket path */
if (socket_path == NULL)
socket_path = strdup("/tmp/i3-ipc.sock");
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
err(EXIT_FAILURE, "Could not connect to i3");
/* Use all arguments, separated by whitespace, as payload.
* This way, you dont have to do i3-msg 'mark foo', you can use
* i3-msg mark foo */
while (optind < argc) {
if (!payload) {
if (!(payload = strdup(argv[optind])))
err(EXIT_FAILURE, "strdup(argv[optind])");
} else {
char *both;
if (asprintf(&both, "%s %s", payload, argv[optind]) == -1)
err(EXIT_FAILURE, "asprintf");
free(payload);
payload = both;
}
optind++;
}
ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload);
if (!payload)
payload = "";
if (quiet)
return 0;
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd == -1)
err(EXIT_FAILURE, "Could not create socket");
uint32_t reply_length;
uint8_t *reply;
ipc_recv_message(sockfd, message_type, &reply_length, &reply);
printf("%.*s", reply_length, reply);
free(reply);
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
err(EXIT_FAILURE, "Could not connect to i3");
close(sockfd);
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);
return 0;
}

30
i3-nagbar/Makefile Normal file
View File

@ -0,0 +1,30 @@
# Default value so one can compile i3-nagbar standalone
TOPDIR=..
include $(TOPDIR)/common.mk
# Depend on the object files of all source-files in src/*.c and on all header files
FILES=$(patsubst %.c,%.o,$(wildcard *.c))
HEADERS=$(wildcard *.h)
# Depend on the specific file (.c for each .o) and on all headers
%.o: %.c ${HEADERS}
echo "CC $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
all: i3-nagbar
i3-nagbar: ${FILES}
echo "LINK i3-nagbar"
$(CC) $(LDFLAGS) -o $@ ${FILES} $(LIBS)
install: all
echo "INSTALL"
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
$(INSTALL) -m 0755 i3-nagbar $(DESTDIR)$(PREFIX)/bin/
clean:
rm -f *.o
distclean: clean
rm -f i3-nagbar

6
i3-nagbar/atoms.xmacro Normal file
View File

@ -0,0 +1,6 @@
xmacro(_NET_WM_WINDOW_TYPE)
xmacro(_NET_WM_WINDOW_TYPE_DOCK)
xmacro(_NET_WM_STRUT_PARTIAL)
xmacro(I3_SOCKET_PATH)
xmacro(ATOM)
xmacro(CARDINAL)

26
i3-nagbar/i3-nagbar.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef _I3_NAGBAR
#define _I3_NAGBAR
#include <err.h>
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
#define FREE(pointer) do { \
if (pointer != NULL) { \
free(pointer); \
pointer = NULL; \
} \
} \
while (0)
#define xmacro(atom) xcb_atom_t A_ ## atom;
#include "atoms.xmacro"
#undef xmacro
extern xcb_window_t root;
uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height);
int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height);
void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value);
#endif

395
i3-nagbar/main.c Normal file
View File

@ -0,0 +1,395 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2011 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* i3-nagbar is a utility which displays a nag message.
*
*/
#include <ev.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <err.h>
#include <stdint.h>
#include <getopt.h>
#include <limits.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_event.h>
#include "i3-nagbar.h"
typedef struct {
char *label;
char *action;
int16_t x;
uint16_t width;
} button_t;
static xcb_window_t win;
static xcb_pixmap_t pixmap;
static xcb_gcontext_t pixmap_gc;
static xcb_rectangle_t rect = { 0, 0, 600, 20 };
static int font_height;
static char *prompt = "Please do not run this program.";
static button_t *buttons;
static int buttoncnt;
xcb_window_t root;
/*
* Starts the given application by passing it through a shell. We use double fork
* to avoid zombie processes. As the started applications parent exits (immediately),
* the application is reparented to init (process-id 1), which correctly handles
* childs, so we dont have to do it :-).
*
* The shell is determined by looking for the SHELL environment variable. If it
* does not exist, /bin/sh is used.
*
*/
static void start_application(const char *command) {
printf("executing: %s\n", command);
if (fork() == 0) {
/* Child process */
setsid();
if (fork() == 0) {
/* Stores the path of the shell */
static const char *shell = NULL;
if (shell == NULL)
if ((shell = getenv("SHELL")) == NULL)
shell = "/bin/sh";
/* This is the child */
execl(shell, shell, "-c", command, (void*)NULL);
/* not reached */
}
exit(0);
}
wait(0);
}
static button_t *get_button_at(int16_t x, int16_t y) {
for (int c = 0; c < buttoncnt; c++)
if (x >= (buttons[c].x) && x <= (buttons[c].x + buttons[c].width))
return &buttons[c];
return NULL;
}
static void handle_button_press(xcb_connection_t *conn, xcb_button_press_event_t *event) {
printf("button pressed on x = %d, y = %d\n",
event->event_x, event->event_y);
/* TODO: set a flag for the button, re-render */
}
/*
* Called when the user releases the mouse button. Checks whether the
* coordinates are over a button and executes the appropriate action.
*
*/
static void handle_button_release(xcb_connection_t *conn, xcb_button_release_event_t *event) {
printf("button released on x = %d, y = %d\n",
event->event_x, event->event_y);
/* If the user hits the close button, we exit(0) */
if (event->event_x >= (rect.width - 32))
exit(0);
button_t *button = get_button_at(event->event_x, event->event_y);
if (!button)
return;
start_application(button->action);
/* TODO: unset flag, re-render */
}
/*
* Handles expose events (redraws of the window) and rendering in general. Will
* be called from the code with event == NULL or from X with event != NULL.
*
*/
static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
printf("expose!\n");
/* re-draw the background */
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#900000"));
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect);
/* restore font color */
uint32_t values[3];
values[0] = get_colorpixel(conn, "#FFFFFF");
values[1] = get_colorpixel(conn, "#900000");
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values);
xcb_image_text_8(conn, strlen(prompt), pixmap, pixmap_gc, 4 + 4/* X */,
font_height + 2 + 4 /* Y = baseline of font */, prompt);
/* render close button */
int line_width = 4;
int w = 20;
int y = rect.width;
values[0] = get_colorpixel(conn, "#680a0a");
values[1] = line_width;
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
xcb_rectangle_t close = { y - w - (2 * line_width), 0, w + (2 * line_width), rect.height };
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424"));
xcb_point_t points[] = {
{ y - w - (2 * line_width), line_width / 2 },
{ y - (line_width / 2), line_width / 2 },
{ y - (line_width / 2), (rect.height - (line_width / 2)) - 2 },
{ y - w - (2 * line_width), (rect.height - (line_width / 2)) - 2 },
{ y - w - (2 * line_width), line_width / 2 }
};
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points);
values[0] = get_colorpixel(conn, "#ffffff");
values[1] = get_colorpixel(conn, "#680a0a");
values[2] = 1;
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH, values);
xcb_image_text_8(conn, strlen("x"), pixmap, pixmap_gc, y - w - line_width + (w / 2) - 4/* X */,
font_height + 2 + 4 - 1/* Y = baseline of font */, "X");
y -= w;
y -= 20;
/* render custom buttons */
line_width = 1;
for (int c = 0; c < buttoncnt; c++) {
/* TODO: make w = text extents of the label */
w = 90;
y -= 30;
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#680a0a"));
close = (xcb_rectangle_t){ y - w - (2 * line_width), 2, w + (2 * line_width), rect.height - 6 };
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424"));
buttons[c].x = y - w - (2 * line_width);
buttons[c].width = w;
xcb_point_t points2[] = {
{ y - w - (2 * line_width), (line_width / 2) + 2 },
{ y - (line_width / 2), (line_width / 2) + 2 },
{ y - (line_width / 2), (rect.height - 4 - (line_width / 2)) },
{ y - w - (2 * line_width), (rect.height - 4 - (line_width / 2)) },
{ y - w - (2 * line_width), (line_width / 2) + 2 }
};
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2);
values[0] = get_colorpixel(conn, "#ffffff");
values[1] = get_colorpixel(conn, "#680a0a");
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values);
xcb_image_text_8(conn, strlen(buttons[c].label), pixmap, pixmap_gc, y - w - line_width + 6/* X */,
font_height + 2 + 3/* Y = baseline of font */, buttons[c].label);
y -= w;
}
/* border line at the bottom */
line_width = 2;
values[0] = get_colorpixel(conn, "#470909");
values[1] = line_width;
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
xcb_point_t bottom[] = {
{ 0, rect.height - 0 },
{ rect.width, rect.height - 0 }
};
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 2, bottom);
/* Copy the contents of the pixmap to the real window */
xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, rect.width, rect.height);
xcb_flush(conn);
return 1;
}
int main(int argc, char *argv[]) {
char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
int o, option_index = 0;
static struct option long_options[] = {
{"version", no_argument, 0, 'v'},
{"font", required_argument, 0, 'f'},
{"button", required_argument, 0, 'b'},
{"help", no_argument, 0, 'h'},
{"message", no_argument, 0, 'm'},
{0, 0, 0, 0}
};
char *options_string = "b:f:m:vh";
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
switch (o) {
case 'v':
printf("i3-nagbar " I3_VERSION);
return 0;
case 'f':
FREE(pattern);
pattern = strdup(optarg);
break;
case 'm':
prompt = strdup(optarg);
break;
case 'h':
printf("i3-nagbar " I3_VERSION "\n");
printf("i3-nagbar [-m <message>] [-b <button> <action>] [-f <font>] [-v]\n");
return 0;
case 'b':
buttons = realloc(buttons, sizeof(button_t) * (buttoncnt + 1));
buttons[buttoncnt].label = optarg;
buttons[buttoncnt].action = argv[optind];
printf("button with label *%s* and action *%s*\n",
buttons[buttoncnt].label,
buttons[buttoncnt].action);
buttoncnt++;
printf("now %d buttons\n", buttoncnt);
if (optind < argc)
optind++;
break;
}
}
int screens;
xcb_connection_t *conn;
if ((conn = xcb_connect(NULL, &screens)) == NULL ||
xcb_connection_has_error(conn))
die("Cannot open display\n");
/* Place requests for the atoms we need as soon as possible */
#define xmacro(atom) \
xcb_intern_atom_cookie_t atom ## _cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
#include "atoms.xmacro"
#undef xmacro
xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
root = root_screen->root;
uint32_t font_id = get_font_id(conn, pattern, &font_height);
/* Open an input window */
win = open_input_window(conn, 500, font_height + 8 + 8 /* 8px padding */);
/* Setup NetWM atoms */
#define xmacro(name) \
do { \
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name ## _cookie, NULL); \
if (!reply) \
die("Could not get atom " # name "\n"); \
\
A_ ## name = reply->atom; \
free(reply); \
} while (0);
#include "atoms.xmacro"
#undef xmacro
/* Set dock mode */
xcb_change_property(conn,
XCB_PROP_MODE_REPLACE,
win,
A__NET_WM_WINDOW_TYPE,
A_ATOM,
32,
1,
(unsigned char*) &A__NET_WM_WINDOW_TYPE_DOCK);
/* Reserve some space at the top of the screen */
struct {
uint32_t left;
uint32_t right;
uint32_t top;
uint32_t bottom;
uint32_t left_start_y;
uint32_t left_end_y;
uint32_t right_start_y;
uint32_t right_end_y;
uint32_t top_start_x;
uint32_t top_end_x;
uint32_t bottom_start_x;
uint32_t bottom_end_x;
} __attribute__((__packed__)) strut_partial = {0,};
strut_partial.top = font_height + 6;
strut_partial.top_start_x = 0;
strut_partial.top_end_x = 800;
xcb_change_property(conn,
XCB_PROP_MODE_REPLACE,
win,
A__NET_WM_STRUT_PARTIAL,
A_CARDINAL,
32,
12,
&strut_partial);
/* Create pixmap */
pixmap = xcb_generate_id(conn);
pixmap_gc = xcb_generate_id(conn);
xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8);
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_flush(conn);
xcb_generic_event_t *event;
while ((event = xcb_wait_for_event(conn)) != NULL) {
if (event->response_type == 0) {
fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
continue;
}
/* Strip off the highest bit (set if the event is generated) */
int type = (event->response_type & 0x7F);
switch (type) {
case XCB_EXPOSE:
handle_expose(conn, (xcb_expose_event_t*)event);
break;
case XCB_BUTTON_PRESS:
handle_button_press(conn, (xcb_button_press_event_t*)event);
break;
case XCB_BUTTON_RELEASE:
handle_button_release(conn, (xcb_button_release_event_t*)event);
break;
case XCB_CONFIGURE_NOTIFY: {
xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t*)event;
rect = (xcb_rectangle_t){
configure_notify->x,
configure_notify->y,
configure_notify->width,
configure_notify->height
};
/* Recreate the pixmap / gc */
xcb_free_pixmap(conn, pixmap);
xcb_free_gc(conn, pixmap_gc);
xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, rect.width, rect.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);
break;
}
}
free(event);
}
return 0;
}

132
i3-nagbar/xcb.c Normal file
View File

@ -0,0 +1,132 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <xcb/xcb.h>
#include <xcb/xcb_keysyms.h>
#include <X11/keysym.h>
#include "i3-nagbar.h"
/*
* Convenience-wrapper around xcb_change_gc which saves us declaring a variable
*
*/
void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
xcb_change_gc(conn, gc, mask, &value);
}
/*
* Returns the colorpixel to use for the given hex color (think of HTML).
*
* The hex_color has to start with #, for example #FF00FF.
*
* NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
* This has to be done by the caller.
*
*/
uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
char strgroups[3][3] = {{hex[1], hex[2], '\0'},
{hex[3], hex[4], '\0'},
{hex[5], hex[6], '\0'}};
uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
(strtol(strgroups[1], NULL, 16)),
(strtol(strgroups[2], NULL, 16))};
return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
}
/*
* Opens the window we use for input/output and maps it
*
*/
xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) {
xcb_window_t win = xcb_generate_id(conn);
//xcb_cursor_t cursor_id = xcb_generate_id(conn);
#if 0
/* 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);
}
#endif
uint32_t mask = 0;
uint32_t values[3];
mask |= XCB_CW_BACK_PIXEL;
values[0] = 0;
mask |= XCB_CW_EVENT_MASK;
values[1] = XCB_EVENT_MASK_EXPOSURE |
XCB_EVENT_MASK_STRUCTURE_NOTIFY |
XCB_EVENT_MASK_BUTTON_PRESS |
XCB_EVENT_MASK_BUTTON_RELEASE;
xcb_create_window(conn,
XCB_COPY_FROM_PARENT,
win, /* the window id */
root, /* parent == root */
50, 50, 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);
#if 0
if (cursor > -1)
xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
#endif
/* Map the window (= make it visible) */
xcb_map_window(conn, win);
return win;
}
/*
* Returns the ID of the font matching the given pattern and stores the height
* of the font (in pixels) in *font_height. die()s if no font matches.
*
*/
int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height) {
xcb_void_cookie_t font_cookie;
xcb_list_fonts_with_info_cookie_t info_cookie;
/* Send all our requests first */
int result;
result = xcb_generate_id(conn);
font_cookie = xcb_open_font_checked(conn, result, strlen(pattern), pattern);
info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
if (error != NULL) {
fprintf(stderr, "ERROR: Could not open font: %d\n", error->error_code);
exit(1);
}
/* Get information (height/name) for this font */
xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
if (reply == NULL)
die("Could not load font \"%s\"\n", pattern);
*font_height = reply->font_ascent + reply->font_descent;
return result;
}

239
i3.config
View File

@ -1,126 +1,161 @@
# 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)
# i3 config file (v4)
#
# Please see http://i3wm.org/docs/userguide.html for a complete reference!
#
# This config file uses keycodes (bindsym) and was written for the QWERTY
# layout.
#
# To get a config file with the same key positions, but for your current
# layout, use the i3-config-wizard
#
# ISO 10646 = Unicode
# font for window titles. ISO 10646 = Unicode
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# Use Mouse+Mod1 to drag floating windows to their wanted position
# use Mouse+Mod1 to drag floating windows to their wanted position
floating_modifier Mod1
# Fullscreen (Mod1+f)
bind Mod1+41 f
# start a terminal
bindsym Mod1+Return exec /usr/bin/urxvt
# Stacking (Mod1+h)
bind Mod1+43 s
# kill focused window
bindsym Mod1+Shift+q kill
# Tabbed (Mod1+w)
bind Mod1+25 T
# start dmenu (a program launcher)
bindsym Mod1+d exec /usr/bin/dmenu_run
# Default (Mod1+e)
bind Mod1+26 d
# change focus
bindsym Mod1+j focus left
bindsym Mod1+k focus down
bindsym Mod1+l focus up
bindsym Mod1+semicolon focus right
# Toggle tiling/floating of the current window (Mod1+Shift+Space)
bind Mod1+Shift+65 t
# alternatively, you can use the cursor keys:
bindsym Mod1+Left focus left
bindsym Mod1+Down focus down
bindsym Mod1+Up focus up
bindsym Mod1+Right focus right
# Go into the tiling layer / floating layer, depending on whether
# the current window is tiling / floating (Mod1+t)
bind Mod1+28 focus ft
# move focused window
bindsym Mod1+Shift+j move left
bindsym Mod1+Shift+k move down
bindsym Mod1+Shift+l move up
bindsym Mod1+Shift+semicolon move right
# Focus (Mod1+j/k/l/;)
bind Mod1+44 h
bind Mod1+45 j
bind Mod1+46 k
bind Mod1+47 l
# (alternatively, you can use the cursor keys:)
bindsym Mod1+Left h
bindsym Mod1+Down j
bindsym Mod1+Up k
bindsym Mod1+Right l
# alternatively, you can use the cursor keys:
bindsym Mod1+Shift+Left move left
bindsym Mod1+Shift+Down move down
bindsym Mod1+Shift+Up move up
bindsym Mod1+Shift+Right move right
# Focus Container (Mod3+j/k/l/;)
bind Mod3+44 wch
bind Mod3+45 wcj
bind Mod3+46 wck
bind Mod3+47 wcl
# (alternatively, you can use the cursor keys:)
bindsym Mod3+Left wch
bindsym Mod3+Down wcj
bindsym Mod3+Up wck
bindsym Mod3+Right wcl
# split in horizontal orientation
bindsym Mod1+h split h
# Snap (Mod1+Control+j/k/l/;)
bind Mod1+Control+44 sh
bind Mod1+Control+45 sj
bind Mod1+Control+46 sk
bind Mod1+Control+47 sl
# (alternatively, you can use the cursor keys:)
bindsym Mod1+Control+Left sh
bindsym Mod1+Control+Down sj
bindsym Mod1+Control+Up sk
bindsym Mod1+Control+Right sl
# split in vertical orientation
bindsym Mod1+v split v
# Move (Mod1+Shift+j/k/l/;)
bind Mod1+Shift+44 mh
bind Mod1+Shift+45 mj
bind Mod1+Shift+46 mk
bind Mod1+Shift+47 ml
# (alternatively, you can use the cursor keys:)
bindsym Mod1+Shift+Left mh
bindsym Mod1+Shift+Down mj
bindsym Mod1+Shift+Up mk
bindsym Mod1+Shift+Right ml
# enter fullscreen mode for the focused container
bindsym Mod1+f fullscreen
# Move Container (Mod3+Shift+j/k/l/;)
bind Mod3+Shift+44 wcmh
bind Mod3+Shift+45 wcmj
bind Mod3+Shift+46 wcmk
bind Mod3+Shift+47 wcml
# change container layout (stacked, tabbed, default)
bindsym Mod1+s layout stacking
bindsym Mod1+w layout tabbed
bindsym Mod1+e layout default
# Workspaces (Mod1+1/2/…)
bind Mod1+10 1
bind Mod1+11 2
bind Mod1+12 3
bind Mod1+13 4
bind Mod1+14 5
bind Mod1+15 6
bind Mod1+16 7
bind Mod1+17 8
bind Mod1+18 9
bind Mod1+19 10
# toggle tiling / floating
bindsym Mod1+Shift+space floating toggle
# Move to Workspaces
bind Mod1+Shift+10 m1
bind Mod1+Shift+11 m2
bind Mod1+Shift+12 m3
bind Mod1+Shift+13 m4
bind Mod1+Shift+14 m5
bind Mod1+Shift+15 m6
bind Mod1+Shift+16 m7
bind Mod1+Shift+17 m8
bind Mod1+Shift+18 m9
bind Mod1+Shift+19 m10
# change focus between tiling / floating windows
bindsym Mod1+space focus mode_toggle
# Mod1+Enter starts a new terminal
bind Mod1+36 exec /usr/bin/urxvt
# focus the parent container
bindsym Mod1+a focus parent
# Mod1+Shift+q kills the current client
bind Mod1+Shift+24 kill
# focus the child container
#bindsym Mod1+d focus child
# Mod1+v starts dmenu and launches the selected application
# for now, we dont have a launcher of our own.
bind Mod1+55 exec /usr/bin/dmenu_run
# switch to workspace
bindsym Mod1+1 workspace 1
bindsym Mod1+2 workspace 2
bindsym Mod1+3 workspace 3
bindsym Mod1+4 workspace 4
bindsym Mod1+5 workspace 5
bindsym Mod1+6 workspace 6
bindsym Mod1+7 workspace 7
bindsym Mod1+8 workspace 8
bindsym Mod1+9 workspace 9
bindsym Mod1+0 workspace 10
# Mod1+Shift+e exits i3
bind Mod1+Shift+26 exit
# move focused container to workspace
bindsym Mod1+Shift+1 move workspace 1
bindsym Mod1+Shift+2 move workspace 2
bindsym Mod1+Shift+3 move workspace 3
bindsym Mod1+Shift+4 move workspace 4
bindsym Mod1+Shift+5 move workspace 5
bindsym Mod1+Shift+6 move workspace 6
bindsym Mod1+Shift+7 move workspace 7
bindsym Mod1+Shift+8 move workspace 8
bindsym Mod1+Shift+9 move workspace 9
bindsym Mod1+Shift+0 move workspace 10
# Mod1+Shift+r restarts i3 inplace
bind Mod1+Shift+27 restart
# reload the configuration file
bindsym Mod1+Shift+c reload
# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
bindsym Mod1+Shift+r restart
# exit i3 (logs you out of your X session)
bindsym Mod1+Shift+e exit
# 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
# resize window (you can also use the mouse for that)
mode "resize" {
# These bindings trigger as soon as you enter the resize mode
#############################################################
# DELETE THE FOLLOWING LINES TO DISABLE THE WELCOME MESSAGE #
#############################################################
exec xmessage -file /etc/i3/welcome
# They resize the border in the direction you pressed, e.g.
# when pressing left, the window is resized so that it has
# more space on its left
bindsym j resize shrink left 10 px or 10 ppt
bindsym Shift+j resize grow left 10 px or 10 ppt
bindsym k resize shrink down 10 px or 10 ppt
bindsym Shift+k resize grow down 10 px or 10 ppt
bindsym l resize shrink up 10 px or 10 ppt
bindsym Shift+l resize grow up 10 px or 10 ppt
bindsym semicolon resize shrink right 10 px or 10 ppt
bindsym Shift+semicolon resize grow right 10 px or 10 ppt
# same bindings, but for the arrow keys
bindsym Left resize shrink left 10 px or 10 ppt
bindsym Shift+Left resize grow left 10 px or 10 ppt
bindsym Down resize shrink down 10 px or 10 ppt
bindsym Shift+Down resize grow down 10 px or 10 ppt
bindsym Up resize shrink up 10 px or 10 ppt
bindsym Shift+Up resize grow up 10 px or 10 ppt
bindsym Right resize shrink right 10 px or 10 ppt
bindsym Shift+Right resize grow right 10 px or 10 ppt
# back to normal: Enter or Escape
bindsym Return mode "default"
bindsym Escape mode "default"
}
bindsym Mod1+r mode "resize"
# Start i3bar to display a workspace bar (plus the system information i3status
# finds out, if available)
exec i3status | i3bar -d
#######################################################################
# automatically start i3-config-wizard to offer the user to create a
# keysym-based config which used his favorite modifier (alt or windows)
#
# i3-config-wizard will not launch if there already is a config file
# in ~/.i3/config.
#
# Please remove the following exec line:
#######################################################################
exec i3-config-wizard

147
i3.config.keycodes Normal file
View File

@ -0,0 +1,147 @@
# WARNING
# WARNING: This configuration file is a template for the i3-config-wizard to
# WARNING: generate a config which uses keysyms in your current layout. It does
# WARNING: not get loaded by i3. Please do not change it.
# WARNING
# i3 config file (v4)
#
# Please see http://i3wm.org/docs/userguide.html for a complete reference!
set $mod Mod1
# font for window titles. ISO 10646 = Unicode
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# Use Mouse+$mod to drag floating windows to their wanted position
floating_modifier $mod
# start a terminal
bindcode $mod+36 exec /usr/bin/urxvt
# kill focused window
bindcode $mod+Shift+24 kill
# start dmenu (a program launcher)
bindcode $mod+40 exec /usr/bin/dmenu_run
# change focus
bindcode $mod+44 focus left
bindcode $mod+45 focus down
bindcode $mod+46 focus up
bindcode $mod+47 focus right
# alternatively, you can use the cursor keys:
bindcode $mod+113 focus left
bindcode $mod+116 focus down
bindcode $mod+111 focus up
bindcode $mod+114 focus right
# move focused window
bindcode $mod+Shift+44 move left
bindcode $mod+Shift+45 move down
bindcode $mod+Shift+46 move up
bindcode $mod+Shift+47 move right
# alternatively, you can use the cursor keys:
bindcode $mod+Shift+113 move left
bindcode $mod+Shift+116 move down
bindcode $mod+Shift+111 move up
bindcode $mod+Shift+114 move right
# split in horizontal orientation
bindcode $mod+43 split h
# split in vertical orientation
bindcode $mod+55 split v
# enter fullscreen mode for the focused container
bindcode $mod+41 fullscreen
# change container layout (stacked, tabbed, default)
bindcode $mod+39 layout stacking
bindcode $mod+25 layout tabbed
bindcode $mod+26 layout default
# toggle tiling / floating
bindcode $mod+Shift+65 floating toggle
# change focus between tiling / floating windows
bindcode $mod+65 focus mode_toggle
# focus the parent container
bindcode $mod+38 focus parent
# focus the child container
#bindcode $mod+d focus child
# switch to workspace
bindcode $mod+10 workspace 1
bindcode $mod+11 workspace 2
bindcode $mod+12 workspace 3
bindcode $mod+13 workspace 4
bindcode $mod+14 workspace 5
bindcode $mod+15 workspace 6
bindcode $mod+16 workspace 7
bindcode $mod+17 workspace 8
bindcode $mod+18 workspace 9
bindcode $mod+19 workspace 10
# move focused container to workspace
bindcode $mod+Shift+10 move workspace 1
bindcode $mod+Shift+11 move workspace 2
bindcode $mod+Shift+12 move workspace 3
bindcode $mod+Shift+13 move workspace 4
bindcode $mod+Shift+14 move workspace 5
bindcode $mod+Shift+15 move workspace 6
bindcode $mod+Shift+16 move workspace 7
bindcode $mod+Shift+17 move workspace 8
bindcode $mod+Shift+18 move workspace 9
bindcode $mod+Shift+19 move workspace 10
# reload the configuration file
bindcode $mod+Shift+54 reload
# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
bindcode $mod+Shift+27 restart
# exit i3 (logs you out of your X session)
bindcode $mod+Shift+26 exit
# resize window (you can also use the mouse for that)
mode "resize" {
# These bindings trigger as soon as you enter the resize mode
# They resize the border in the direction you pressed, e.g.
# when pressing left, the window is resized so that it has
# more space on its left
bindcode 44 resize shrink left 10 px or 10 ppt
bindcode Shift+44 resize grow left 10 px or 10 ppt
bindcode 45 resize shrink down 10 px or 10 ppt
bindcode Shift+45 resize grow down 10 px or 10 ppt
bindcode 46 resize shrink up 10 px or 10 ppt
bindcode Shift+46 resize grow up 10 px or 10 ppt
bindcode 47 resize shrink right 10 px or 10 ppt
bindcode Shift+47 resize grow right 10 px or 10 ppt
# same bindings, but for the arrow keys
bindcode 113 resize shrink left 10 px or 10 ppt
bindcode Shift+113 resize grow left 10 px or 10 ppt
bindcode 116 resize shrink down 10 px or 10 ppt
bindcode Shift+116 resize grow down 10 px or 10 ppt
bindcode 111 resize shrink up 10 px or 10 ppt
bindcode Shift+111 resize grow up 10 px or 10 ppt
bindcode 114 resize shrink right 10 px or 10 ppt
bindcode Shift+114 resize grow right 10 px or 10 ppt
# back to normal: Enter or Escape
bindcode 36 mode "default"
bindcode 9 mode "default"
}
bindcode $mod+27 mode "resize"

67
include/all.h Normal file
View File

@ -0,0 +1,67 @@
/*
* This header file includes all relevant files of i3 and the most often used
* system header files. This reduces boilerplate (the amount of code duplicated
* at the beginning of each source file) and is not significantly slower at
* compile-time.
*
*/
#ifndef _ALL_H
#define _ALL_H
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <getopt.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <glob.h>
#include <errno.h>
#include <err.h>
#include <stdint.h>
#include <math.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/xcb_icccm.h>
/* Contains compatibility definitions for old libxcb versions */
#ifdef XCB_COMPAT
#include "xcb_compat.h"
#endif
#include "data.h"
#include "util.h"
#include "ipc.h"
#include "tree.h"
#include "log.h"
#include "xcb.h"
#include "manage.h"
#include "workspace.h"
#include "i3.h"
#include "x.h"
#include "click.h"
#include "floating.h"
#include "config.h"
#include "handlers.h"
#include "randr.h"
#include "xinerama.h"
#include "con.h"
#include "load_layout.h"
#include "render.h"
#include "window.h"
#include "match.h"
#include "cmdparse.h"
#include "xcursor.h"
#include "resize.h"
#include "sighandler.h"
#include "move.h"
#include "output.h"
#include "ewmh.h"
#include "assignments.h"
#endif

21
include/assignments.h Normal file
View File

@ -0,0 +1,21 @@
/*
* vim:ts=4:sw=4:expandtab
*
*/
#ifndef _ASSIGNMENTS_H
#define _ASSIGNMENTS_H
/**
* Checks the list of assignments for the given window and runs all matching
* ones (unless they have already been run for this specific window).
*
*/
void run_assignments(i3Window *window);
/**
* Returns the first matching assignment for the given window.
*
*/
Assignment *assignment_for(i3Window *window, int type);
#endif

33
include/atoms.xmacro Normal file
View File

@ -0,0 +1,33 @@
xmacro(_NET_SUPPORTED)
xmacro(_NET_SUPPORTING_WM_CHECK)
xmacro(_NET_WM_NAME)
xmacro(_NET_WM_STATE_FULLSCREEN)
xmacro(_NET_WM_STATE)
xmacro(_NET_WM_WINDOW_TYPE)
xmacro(_NET_WM_WINDOW_TYPE_DOCK)
xmacro(_NET_WM_WINDOW_TYPE_DIALOG)
xmacro(_NET_WM_WINDOW_TYPE_UTILITY)
xmacro(_NET_WM_WINDOW_TYPE_TOOLBAR)
xmacro(_NET_WM_WINDOW_TYPE_SPLASH)
xmacro(_NET_WM_DESKTOP)
xmacro(_NET_WM_STRUT_PARTIAL)
xmacro(_NET_CURRENT_DESKTOP)
xmacro(_NET_ACTIVE_WINDOW)
xmacro(_NET_WORKAREA)
xmacro(WM_PROTOCOLS)
xmacro(WM_DELETE_WINDOW)
xmacro(UTF8_STRING)
xmacro(WM_STATE)
xmacro(WM_CLIENT_LEADER)
xmacro(WM_TAKE_FOCUS)
xmacro(WM_HINTS)
xmacro(WM_NORMAL_HINTS)
xmacro(WM_TRANSIENT_FOR)
xmacro(ATOM)
xmacro(WINDOW)
xmacro(WM_NAME)
xmacro(WM_CLASS)
xmacro(STRING)
xmacro(CARDINAL)
xmacro(I3_SOCKET_PATH)
xmacro(I3_CONFIG_PATH)

View File

@ -1,9 +1,9 @@
/*
* vim:ts=8:expandtab
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
* © 2009-2011 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -11,6 +11,14 @@
#ifndef _CLICK_H
#define _CLICK_H
int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event);
/**
* The button press X callback. This function determines whether the floating
* modifier is pressed and where the user clicked (decoration, border, inside
* the window).
*
* Then, route_click is called on the appropriate con.
*
*/
int handle_button_press(xcb_button_press_event_t *event);
#endif

View File

@ -1,155 +0,0 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include <xcb/xcb.h>
#include "data.h"
#ifndef _CLIENT_H
#define _CLIENT_H
/**
* Removes the given client from the container, either because it will be
* inserted into another one or because it was unmapped
*
*/
void client_remove_from_container(xcb_connection_t *conn, Client *client,
Container *container,
bool remove_from_focusstack);
/**
* Warps the pointer into the given client (in the middle of it, to be
* specific), therefore selecting it
*
*/
void client_warp_pointer_into(xcb_connection_t *conn, Client *client);
/**
* Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
*
*/
void client_kill(xcb_connection_t *conn, Client *window);
/**
* Checks if the given window class and title match the given client Window
* title is passed as "normal" string and as UCS-2 converted string for
* matching _NET_WM_NAME capable clients as well as those using legacy hints.
*
*/
bool client_matches_class_name(Client *client, char *to_class, char *to_title,
char *to_title_ucs, int to_title_ucs_len);
/**
* Enters fullscreen mode for the given client. This is called by toggle_fullscreen
* and when moving a fullscreen client to another screen.
*
*/
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.
*
*/
void client_leave_fullscreen(xcb_connection_t *conn, Client *client);
/**
* Toggles fullscreen mode for the given client. It updates the data
* structures and reconfigures (= resizes/moves) the client and its frame to
* the full size of the screen. When leaving fullscreen, re-rendering the
* layout is forced.
*
*/
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
* first floating client, so that floating windows are always on top.
*
*/
void client_set_below_floating(xcb_connection_t *conn, Client *client);
/**
* Returns true if the client is floating. Makes the code more beatiful, as
* floating is not simply a boolean, but also saves whether the user selected
* the current state or whether it was automatically set.
*
*/
bool client_is_floating(Client *client);
/**
* Change the border type for the given client to normal (n), 1px border (p) or
* completely borderless (b).
*
*/
void client_change_border(xcb_connection_t *conn, Client *client, char border_type);
/**
* Change the border type for the given client to normal (n), 1px border (p) or
* completely borderless (b) without actually re-rendering the layout. Useful
* for calling it when initializing a new client.
*
*/
bool client_init_border(xcb_connection_t *conn, Client *client, char border_type);
/**
* Unmap the client, correctly setting any state which is needed.
*
*/
void client_unmap(xcb_connection_t *conn, Client *client);
/**
* Map the client, correctly restoring any state needed.
*
*/
void client_map(xcb_connection_t *conn, Client *client);
/**
* Set the given mark for this client. Used for jumping to the client
* afterwards (like m<mark> and '<mark> in vim).
*
*/
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 { \
DLOG("Window: frame 0x%08x, child 0x%08x\n", client->frame, client->child); \
} while (0)
#endif

6
include/cmdparse.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef _CMDPARSE_H
#define _CMDPARSE_H
char *parse_cmd(const char *new);
#endif

View File

@ -1,22 +0,0 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#ifndef _COMMANDS_H
#define _COMMANDS_H
#include <xcb/xcb.h>
bool focus_window_in_container(xcb_connection_t *conn, Container *container,
direction_t direction);
/** Parses a command, see file CMDMODE for more information */
void parse_command(xcb_connection_t *conn, const char *command);
#endif

215
include/con.h Normal file
View File

@ -0,0 +1,215 @@
#ifndef _CON_H
#define _CON_H
/**
* Create a new container (and attach it to the given parent, if not NULL).
* This function initializes the data structures and creates the appropriate
* X11 IDs using x_con_init().
*
*/
Con *con_new(Con *parent, i3Window *window);
/**
* Sets input focus to the given container. Will be updated in X11 in the next
* run of x_push_changes().
*
*/
void con_focus(Con *con);
/**
* Returns true when this node is a leaf node (has no children)
*
*/
bool con_is_leaf(Con *con);
/**
* Returns true if this node accepts a window (if the node swallows windows,
* it might already have swallowed enough and cannot hold any more).
*
*/
bool con_accepts_window(Con *con);
/**
* Gets the output container (first container with CT_OUTPUT in hierarchy) this
* node is on.
*
*/
Con *con_get_output(Con *con);
/**
* Gets the workspace container this node is on.
*
*/
Con *con_get_workspace(Con *con);
/**
* Searches parenst of the given 'con' until it reaches one with the specified
* 'orientation'. Aborts when it comes across a floating_con.
*
*/
Con *con_parent_with_orientation(Con *con, orientation_t orientation);
/**
* Returns the first fullscreen node below this node.
*
*/
Con *con_get_fullscreen_con(Con *con, int fullscreen_mode);
/**
* Returns true if the node is floating.
*
*/
bool con_is_floating(Con *con);
/**
* Checks if the given container is either floating or inside some floating
* container. It returns the FLOATING_CON container.
*
*/
Con *con_inside_floating(Con *con);
/**
* Returns the container with the given client window ID or NULL if no such
* container exists.
*
*/
Con *con_by_window_id(xcb_window_t window);
/**
* Returns the container with the given frame ID or NULL if no such container
* exists.
*
*/
Con *con_by_frame_id(xcb_window_t frame);
/**
* Returns the first container below 'con' which wants to swallow this window
* TODO: priority
*
*/
Con *con_for_window(Con *con, i3Window *window, Match **store_match);
/**
* Returns the number of children of this container.
*
*/
int con_num_children(Con *con);
/**
* Attaches the given container to the given parent. This happens when moving
* a container or when inserting a new container at a specific place in the
* tree.
*
* ignore_focus is to just insert the Con at the end (useful when creating a
* new split container *around* some containers, that is, detaching and
* attaching them in order without wanting to mess with the focus in between).
*
*/
void con_attach(Con *con, Con *parent, bool ignore_focus);
/**
* Detaches the given container from its current parent
*
*/
void con_detach(Con *con);
/**
* Updates the percent attribute of the children of the given container. This
* function needs to be called when a window is added or removed from a
* container.
*
*/
void con_fix_percent(Con *con);
/**
* Toggles fullscreen mode for the given container. Fullscreen mode will not be
* entered when there already is a fullscreen container on this workspace.
*
*/
void con_toggle_fullscreen(Con *con, int fullscreen_mode);
/**
* Moves the given container to the currently focused container on the given
* workspace.
* TODO: is there a better place for this function?
*
*/
void con_move_to_workspace(Con *con, Con *workspace);
/**
* Returns the orientation of the given container (for stacked containers,
* vertical orientation is used regardless of the actual orientation of the
* container).
*
*/
int con_orientation(Con *con);
/**
* Returns the container which will be focused next when the given container
* is not available anymore. Called in tree_close and con_move_to_workspace
* to properly restore focus.
*
*/
Con *con_next_focused(Con *con);
/**
* Get the next/previous container in the specified orientation. This may
* travel up until it finds a container with suitable orientation.
*
*/
Con *con_get_next(Con *con, char way, orientation_t orientation);
/**
* Returns the focused con inside this client, descending the tree as far as
* possible. This comes in handy when attaching a con to a workspace at the
* currently focused position, for example.
*
*/
Con *con_descend_focused(Con *con);
/**
* Returns the focused con inside this client, descending the tree as far as
* possible. This comes in handy when attaching a con to a workspace at the
* currently focused position, for example.
*
* Works like con_descend_focused but considers only tiling cons.
*
*/
Con *con_descend_tiling_focused(Con *con);
/**
* Returns a "relative" Rect which contains the amount of pixels that need to
* be added to the original Rect to get the final position (obviously the
* amount of pixels for normal, 1pixel and borderless are different).
*
*/
Rect con_border_style_rect(Con *con);
/**
* Use this function to get a containers border style. This is important
* because when inside a stack, the border style is always BS_NORMAL.
* For tabbed mode, the same applies, with one exception: when the container is
* borderless and the only element in the tabbed container, the border is not
* rendered.
*
* For children of a CT_DOCKAREA, the border style is always none.
*
*/
int con_border_style(Con *con);
/**
* This function changes the layout of a given container. Use it to handle
* special cases like changing a whole workspace to stacked/tabbed (creates a
* new split container before).
*
*/
void con_set_layout(Con *con, int layout);
/**
* Determines the minimum size of the given con by looking at its children (for
* split/stacked/tabbed cons). Will be called when resizing floating cons
*
*/
Rect con_minimum_size(Con *con);
#endif

View File

@ -22,6 +22,7 @@
#include "i3.h"
typedef struct Config Config;
extern char *current_configpath;
extern Config config;
extern SLIST_HEAD(modes_head, Mode) modes;
@ -31,10 +32,14 @@ extern SLIST_HEAD(modes_head, Mode) modes;
*
*/
struct context {
bool has_errors;
int line_number;
char *line_copy;
const char *filename;
char *compact_error;
/* These are the same as in YYLTYPE */
int first_column;
int last_column;
@ -84,14 +89,18 @@ struct Mode {
*/
struct Config {
const char *terminal;
const char *font;
i3Font font;
const char *ipc_socket_path;
char *ipc_socket_path;
const char *restart_state_path;
int container_mode;
int default_layout;
int container_stack_limit;
int container_stack_limit_value;
/** Default orientation for new containers */
int default_orientation;
/** 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.
@ -104,7 +113,18 @@ struct Config {
* comes with i3. Thus, you can turn it off entirely. */
bool disable_workspace_bar;
const char *default_border;
/** Think of the following layout: Horizontal workspace with a tabbed
* con on the left of the screen and a terminal on the right of the
* screen. You are in the second container in the tabbed container and
* focus to the right. By default, i3 will set focus to the terminal on
* the right. If you are in the first container in the tabbed container
* however, focusing to the left will wrap. This option forces i3 to
* always wrap, which will result in you having to use "focus parent"
* more often. */
bool force_focus_wrapping;
/** The default border style for new windows. */
border_style_t default_border;
/** The modifier which needs to be pressed in combination with your mouse
* buttons to do things with floating windows (move, resize) */
@ -123,22 +143,14 @@ struct Config {
struct Colortriple unfocused;
struct Colortriple urgent;
} bar;
/** What should happen when a new popup is opened during fullscreen mode */
enum {
PDF_LEAVE_FULLSCREEN = 0,
PDF_IGNORE = 1
} popup_during_fullscreen;
};
/**
* This function resolves ~ in pathnames.
* It may resolve wildcards in the first part of the path, but if no match
* or multiple matches are found, it just returns a copy of path as given.
*
*/
char *resolve_tilde(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.
*
@ -171,7 +183,7 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch);
* Switches the key bindings to the given mode, if the mode exists
*
*/
void switch_mode(xcb_connection_t *conn, const char *new_mode);
void switch_mode(const char *new_mode);
/**
* Returns a pointer to the Binding with the specified modifiers and keycode
@ -180,6 +192,17 @@ void switch_mode(xcb_connection_t *conn, const char *new_mode);
*/
Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode);
/**
* Kills the configerror i3-nagbar process, if any.
*
* Called when reloading/restarting.
*
* If wait_for_it is set (restarting), this function will waitpid(), otherwise,
* ev is assumed to handle it (reloading).
*
*/
void kill_configerror_nagbar(bool wait_for_it);
/* prototype for src/cfgparse.y */
void parse_file(const char *f);

View File

@ -1,26 +0,0 @@
/*
* 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

@ -1,16 +1,12 @@
/*
* vim:ts=8:expandtab
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
* include/data.h: This file defines all data structures used by i3
*
*/
#include <xcb/xcb.h>
#include <xcb/randr.h>
#include <xcb/xcb_atom.h>
#include <stdbool.h>
@ -26,53 +22,42 @@
*
* Lets start from the biggest to the smallest:
*
* - 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 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.
*
* - Each Workspace has a table, which is our layout abstraction. You manage
* your windows by moving them around in your table. It grows as necessary.
*
* - Each cell of the table has a container, which can be in default or
* stacking mode. In default mode, each client is given equally much space
* in the container. In stacking mode, only one client is shown at a time,
* but all the titlebars are rendered at the top.
*
* - Inside the container are clients, which is X11-speak for a window.
* TODO
*
*/
/* Forward definitions */
typedef struct Cell Cell;
typedef struct Font i3Font;
typedef struct Container Container;
typedef struct Client Client;
typedef struct Binding Binding;
typedef struct Workspace Workspace;
typedef struct Rect Rect;
typedef struct xoutput Output;
typedef struct Con Con;
typedef struct Match Match;
typedef struct Assignment Assignment;
typedef struct Window i3Window;
/******************************************************************************
* Helper types
*****************************************************************************/
typedef enum { D_LEFT, D_RIGHT, D_UP, D_DOWN } direction_t;
typedef enum { NO_ORIENTATION = 0, HORIZ, VERT } orientation_t;
typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_1PIXEL = 2 } border_style_t;
/** parameter to specify whether tree_close() and x_window_kill() should kill
* only this specific window or the whole X11 client */
typedef enum { DONT_KILL_WINDOW = 0, KILL_WINDOW = 1, KILL_CLIENT = 2 } kill_window_t;
enum {
BIND_NONE = 0,
BIND_SHIFT = XCB_MOD_MASK_SHIFT, /* (1 << 0) */
BIND_CONTROL = XCB_MOD_MASK_CONTROL, /* (1 << 2) */
BIND_MOD1 = XCB_MOD_MASK_1, /* (1 << 3) */
BIND_MOD2 = XCB_MOD_MASK_2, /* (1 << 4) */
BIND_MOD3 = XCB_MOD_MASK_3, /* (1 << 5) */
BIND_MOD4 = XCB_MOD_MASK_4, /* (1 << 6) */
BIND_MOD5 = XCB_MOD_MASK_5, /* (1 << 7) */
BIND_MODE_SWITCH = (1 << 8)
BIND_NONE = 0,
BIND_SHIFT = XCB_MOD_MASK_SHIFT, /* (1 << 0) */
BIND_CONTROL = XCB_MOD_MASK_CONTROL, /* (1 << 2) */
BIND_MOD1 = XCB_MOD_MASK_1, /* (1 << 3) */
BIND_MOD2 = XCB_MOD_MASK_2, /* (1 << 4) */
BIND_MOD3 = XCB_MOD_MASK_3, /* (1 << 5) */
BIND_MOD4 = XCB_MOD_MASK_4, /* (1 << 6) */
BIND_MOD5 = XCB_MOD_MASK_5, /* (1 << 7) */
BIND_MODE_SWITCH = (1 << 8)
};
/**
@ -86,234 +71,120 @@ enum {
* _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
* 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.
*
*/
struct Rect {
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
} __attribute__((packed));
/**
* Defines a position in the table
* Stores the reserved pixels on each screen edge read from a
* _NET_WM_STRUT_PARTIAL.
*
*/
struct Cell {
int row;
int column;
struct reservedpx {
uint32_t left;
uint32_t right;
uint32_t top;
uint32_t bottom;
};
/**
* Used for the cache of colorpixels.
* Stores a width/height pair, used as part of deco_render_params to check
* whether the rects width/height have changed.
*
*/
struct Colorpixel {
uint32_t pixel;
char *hex;
SLIST_ENTRY(Colorpixel) colorpixels;
};
struct Cached_Pixmap {
xcb_pixmap_t id;
/* Were going to paint on it, so a graphics context will be needed */
xcb_gcontext_t gc;
/* The rect with which the pixmap was created */
Rect rect;
/* The rect of the object to which this pixmap belongs. Necessary to
* find out when we need to re-create the pixmap. */
Rect *referred_rect;
xcb_drawable_t referred_drawable;
struct width_height {
uint32_t w;
uint32_t h;
};
/**
* Contains data for the windows needed to draw the titlebars on in stacking
* mode
* Stores the parameters for rendering a window decoration. This structure is
* cached in every Con and no re-rendering will be done if the parameters have
* not changed (only the pixmaps will be copied).
*
*/
struct Stack_Window {
xcb_window_t window;
struct Cached_Pixmap pixmap;
Rect rect;
struct deco_render_params {
struct Colortriple *color;
int border_style;
struct width_height con_rect;
struct width_height con_window_rect;
Rect con_deco_rect;
uint32_t background;
bool con_is_leaf;
xcb_font_t font;
};
/** Backpointer to the container this stack window is in */
Container *container;
/**
* Stores which workspace (by name) goes to which output.
*
*/
struct Workspace_Assignment {
char *name;
char *output;
SLIST_ENTRY(Stack_Window) stack_windows;
TAILQ_ENTRY(Workspace_Assignment) ws_assignments;
};
struct Ignore_Event {
int sequence;
time_t added;
int sequence;
int response_type;
time_t added;
SLIST_ENTRY(Ignore_Event) ignore_events;
};
/**
* Emulates the behaviour of tables of libxcb-wm, which in libxcb 0.3.4
* suddenly vanished.
*
*/
struct keyvalue_element {
uint32_t key;
void *value;
TAILQ_ENTRY(keyvalue_element) elements;
SLIST_ENTRY(Ignore_Event) ignore_events;
};
/******************************************************************************
* Major types
*****************************************************************************/
/**
* The concept of Workspaces is known from various other window
* managers. Basically, a workspace is a specific set of windows, usually
* grouped thematically (irc, www, work, ). You can switch between these.
*
*/
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;
/** Length of the workspaces name (in glyphs) */
int name_len;
/** Width of the workspaces name (in pixels) rendered in config.font */
int text_width;
/** x, y, width, height */
Rect rect;
/** table dimensions */
int cols;
/** table dimensions */
int rows;
/** These are stored here only while this workspace is _not_ shown
* (see show_workspace()) */
int current_row;
/** These are stored here only while this workspace is _not_ shown
* (see show_workspace()) */
int current_col;
/** Should clients on this workspace be automatically floating? */
bool auto_float;
/** Are the floating clients on this workspace currently hidden? */
bool floating_hidden;
/** 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;
/** the client who is started in fullscreen mode on this workspace,
* NULL if there is none */
Client *fullscreen_client;
/** The focus stack contains the clients in the correct order of focus
so that the focus can be reverted correctly when a client is
closed */
SLIST_HEAD(focus_stack_head, Client) focus_stack;
/** This tail queue contains the floating clients in order of when
* they were first set to floating (new floating clients are just
* appended) */
TAILQ_HEAD(floating_clients_head, Client) floating_clients;
/** 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
* programmer :) */
Container ***table;
/** width_factor and height_factor contain the amount of space
* (percentage) a column/row has of all the space which is available
* for resized windows. This ensures that non-resized windows (newly
* opened, for example) have the same size as always */
float *width_factor;
float *height_factor;
TAILQ_ENTRY(Workspace) workspaces;
};
/**
* Holds a keybinding, consisting of a keycode combined with modifiers and the
* command which is executed as soon as the key is pressed (see src/command.c)
*
*/
struct Binding {
/** Symbol the user specified in configfile, if any. This needs to be
* stored with the binding to be able to re-convert it into a keycode
* if the keyboard mapping changes (using Xmodmap for example) */
char *symbol;
/** Symbol the user specified in configfile, if any. This needs to be
* stored with the binding to be able to re-convert it into a keycode
* if the keyboard mapping changes (using Xmodmap for example) */
char *symbol;
/** Only in use if symbol != NULL. Gets set to the value to which the
* symbol got translated when binding. Useful for unbinding and
* checking which binding was used when a key press event comes in.
*
* This is an array of number_keycodes size. */
xcb_keycode_t *translated_to;
/** Only in use if symbol != NULL. Gets set to the value to which the
* symbol got translated when binding. Useful for unbinding and
* checking which binding was used when a key press event comes in.
*
* This is an array of number_keycodes size. */
xcb_keycode_t *translated_to;
uint32_t number_keycodes;
uint32_t number_keycodes;
/** Keycode to bind */
uint32_t keycode;
/** Keycode to bind */
uint32_t keycode;
/** Bitmask consisting of BIND_MOD_1, BIND_MODE_SWITCH, … */
uint32_t mods;
/** Bitmask consisting of BIND_MOD_1, BIND_MODE_SWITCH, … */
uint32_t mods;
/** Command, like in command mode */
char *command;
/** Command, like in command mode */
char *command;
TAILQ_ENTRY(Binding) bindings;
TAILQ_ENTRY(Binding) bindings;
};
/**
* Holds a command specified by an exec-line in the config (see src/config.c)
* Holds a command specified by either an:
* - exec-line
* - exec_always-line
* in the config (see src/config.c)
*
*/
struct Autostart {
/** Command, like in command mode */
char *command;
TAILQ_ENTRY(Autostart) autostarts;
};
/**
* Holds an assignment for a given window class/title to a specific workspace
* (see src/config.c)
*
*/
struct Assignment {
char *windowclass_title;
/** floating is true if this was an assignment to the special
* workspace "~". Matching clients will be put into floating mode
* automatically. */
enum {
ASSIGN_FLOATING_NO, /* dont float, but put on a workspace */
ASSIGN_FLOATING_ONLY, /* float, but dont assign on a workspace */
ASSIGN_FLOATING /* float and put on a workspace */
} floating;
/** The number of the workspace to assign to. */
int workspace;
TAILQ_ENTRY(Assignment) assignments;
/** Command, like in command mode */
char *command;
TAILQ_ENTRY(Autostart) autostarts;
TAILQ_ENTRY(Autostart) autostarts_always;
};
/**
@ -323,184 +194,12 @@ struct Assignment {
*
*/
struct Font {
/** The name of the font, that is what the pattern resolves to */
char *name;
/** A copy of the pattern to build a cache */
char *pattern;
/** The height of the font, built from font_ascent + font_descent */
int height;
/** The xcb-id for the font */
xcb_font_t id;
TAILQ_ENTRY(Font) fonts;
/** The height of the font, built from font_ascent + font_descent */
int height;
/** The xcb-id for the font */
xcb_font_t id;
};
/**
* A client is X11-speak for a window.
*
*/
struct Client {
/** initialized will be set to true if the client was fully
* initialized by manage_window() and all functions can be used
* normally */
bool initialized;
/** if you set a client to floating and set it back to managed, it
* does remember its old position and *tries* to get back there */
Cell old_position;
/** Backpointer. A client is inside a container */
Container *container;
/** Because dock clients dont have a container, we have this
* workspace-backpointer */
Workspace *workspace;
/** x, y, width, height of the frame */
Rect rect;
/** Position in floating mode and in tiling mode are saved
* separately */
Rect floating_rect;
/** x, y, width, height of the child (relative to its frame) */
Rect child_rect;
/** contains the size calculated from the hints set by the window or 0
* if the client did not send any hints */
int proportional_height;
int proportional_width;
int base_height;
int base_width;
/** The amount of pixels which X will draw around the client. */
int border_width;
/** contains the minimum increment size as specified for the window
* (in pixels). */
int width_increment;
int height_increment;
/** Height which was determined by reading the _NET_WM_STRUT_PARTIAL
* top/bottom of the screen reservation */
int desired_height;
/** Name (= window title) */
char *name;
/** name_len stores the real string length (glyphs) of the window
* title if the client uses _NET_WM_NAME. Otherwise, it is set to -1
* to indicate that name should be just passed to X as 8-bit string
* and therefore will not be rendered correctly. This behaviour is to
* support legacy applications which do not set _NET_WM_NAME */
int name_len;
/** This will be set to true as soon as the first _NET_WM_NAME comes
* in. If set to true, legacy window names are ignored. */
bool uses_net_wm_name;
/** Holds the WM_CLASS (which consists of two strings, the instance
* and the class), useful for matching the client in commands */
char *window_class_instance;
char *window_class_class;
/** Holds the clients mark, for vim-like jumping */
char *mark;
/** Holds the xcb_window_t (just an ID) for the leader window (logical
* parent for toolwindows and similar floating windows) */
xcb_window_t leader;
/** fullscreen is pretty obvious */
bool fullscreen;
/** floating? (= not in tiling layout) This cannot be simply a bool
* because we want to keep track of whether the status was set by the
* application (by setting WM_CLASS to tools for example) or by the
* user. The users choice overwrites automatic mode, of course. The
* order of the values is important because we check with >=
* FLOATING_AUTO_ON if a client is floating. */
enum { FLOATING_AUTO_OFF = 0, FLOATING_USER_OFF = 1, FLOATING_AUTO_ON = 2, FLOATING_USER_ON = 3 } floating;
/** Ensure TITLEBAR_TOP maps to 0 because we use calloc for
* initialization later */
enum { TITLEBAR_TOP = 0, TITLEBAR_LEFT, TITLEBAR_RIGHT, TITLEBAR_BOTTOM, TITLEBAR_OFF } titlebar_position;
/** Contains a bool specifying whether this window should not be drawn
* with the usual decorations */
bool borderless;
/** If a client is set as a dock, it is placed at the very bottom of
* the screen and its requested size is used */
bool dock;
/** True if the client set the urgency flag in its WM_HINTS property */
bool urgent;
/* After leaving fullscreen mode, a client needs to be reconfigured
* (configuration = setting X, Y, width and height). By setting the
* force_reconfigure flag, render_layout() will reconfigure the
* client. */
bool force_reconfigure;
/* When reparenting a window, an unmap-notify is sent. As we delete
* windows when theyre unmapped, we need to ignore that
* one. Therefore, this flag is set when reparenting. */
bool awaiting_useless_unmap;
/* XCB contexts */
xcb_window_t frame; /**< Our window: The frame around the
* client */
xcb_gcontext_t titlegc; /**< The titlebars graphic context
* inside the frame */
xcb_window_t child; /**< The clients window */
/** The following entry provides the necessary list pointers to use
* Client with LIST_* macros */
CIRCLEQ_ENTRY(Client) clients;
SLIST_ENTRY(Client) dock_clients;
SLIST_ENTRY(Client) focus_clients;
TAILQ_ENTRY(Client) floating_clients;
};
/**
* A container is either in default, stacking or tabbed mode. There is one for
* each cell of the table.
*
*/
struct Container {
/* Those are speaking for themselves: */
Client *currently_focused;
int colspan;
int rowspan;
/* Position of the container inside our table */
int row;
int col;
/* Xinerama: X/Y of the container */
int x;
int y;
/* Width/Height of the container. Changeable by the user */
int width;
int height;
/* When in stacking mode, we draw the titlebars of each client onto a
* separate window */
struct Stack_Window stack_win;
/* Backpointer to the workspace this container is in */
Workspace *workspace;
/* Ensure MODE_DEFAULT maps to 0 because we use calloc for
* initialization later */
enum { MODE_DEFAULT = 0, MODE_STACK, MODE_TABBED } mode;
/* When in stacking, one can either have unlimited windows inside the
* container or set a limit for the rows or columns the stack window
* should display to use the screen more efficiently. */
enum { STACK_LIMIT_NONE = 0, STACK_LIMIT_COLS, STACK_LIMIT_ROWS } stack_limit;
/* The number of columns or rows to limit to, see stack_limit */
int stack_limit_value;
CIRCLEQ_HEAD(client_head, Client) clients;
};
/**
* An Output is a physical output on your graphics driver. Outputs which
@ -510,35 +209,259 @@ struct Container {
*
*/
struct xoutput {
/** Output id, so that we can requery the output directly later */
xcb_randr_output_t id;
/** Name of the output */
char *name;
/** 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;
/** Pointer to the Con which represents this output */
Con *con;
/** Internal flags, necessary for querying RandR screens (happens in
* two stages) */
bool changed;
bool to_be_disabled;
/** Whether the output is currently active (has a CRTC attached with a
* valid mode) */
bool active;
/** Current workspace selected on this virtual screen */
Workspace *current_workspace;
/** Internal flags, necessary for querying RandR screens (happens in
* two stages) */
bool changed;
bool to_be_disabled;
bool primary;
/** x, y, width, height */
Rect rect;
/** x, y, width, height */
Rect rect;
/** The bar window */
xcb_window_t bar;
xcb_gcontext_t bargc;
#if 0
/** The bar window */
xcb_window_t bar;
xcb_gcontext_t bargc;
/** Contains all clients with _NET_WM_WINDOW_TYPE ==
* _NET_WM_WINDOW_TYPE_DOCK */
SLIST_HEAD(dock_clients_head, Client) dock_clients;
/** Contains all clients with _NET_WM_WINDOW_TYPE ==
* _NET_WM_WINDOW_TYPE_DOCK */
SLIST_HEAD(dock_clients_head, Client) dock_clients;
#endif
TAILQ_ENTRY(xoutput) outputs;
TAILQ_ENTRY(xoutput) outputs;
};
struct Window {
xcb_window_t id;
/** Holds the xcb_window_t (just an ID) for the leader window (logical
* parent for toolwindows and similar floating windows) */
xcb_window_t leader;
xcb_window_t transient_for;
char *class_class;
char *class_instance;
/** The name of the window as it will be passed to X11 (in UCS2 if the
* application supports _NET_WM_NAME, in COMPOUND_TEXT otherwise). */
char *name_x;
/** Flag to force re-rendering the decoration upon changes */
bool name_x_changed;
/** The name of the window as used in JSON (in UTF-8 if the application
* supports _NET_WM_NAME, in COMPOUND_TEXT otherwise) */
char *name_json;
/** The length of the name in glyphs (not bytes) */
int name_len;
/** Whether the application used _NET_WM_NAME */
bool uses_net_wm_name;
/** Whether the application needs to receive WM_TAKE_FOCUS */
bool needs_take_focus;
/** Whether the window says it is a dock window */
enum { W_NODOCK = 0, W_DOCK_TOP = 1, W_DOCK_BOTTOM = 2 } dock;
/** Pixels the window reserves. left/right/top/bottom */
struct reservedpx reserved;
/** Pointers to the Assignments which were already ran for this Window
* (assignments run only once) */
uint32_t nr_assignments;
Assignment **ran_assignments;
};
struct Match {
char *title;
int title_len;
char *application;
char *class;
char *instance;
char *mark;
enum {
M_DONTCHECK = -1,
M_NODOCK = 0,
M_DOCK_ANY = 1,
M_DOCK_TOP = 2,
M_DOCK_BOTTOM = 3
} dock;
xcb_window_t id;
Con *con_id;
enum { M_ANY = 0, M_TILING, M_FLOATING } floating;
/* Where the window looking for a match should be inserted:
*
* M_HERE = the matched container will be replaced by the window
* (layout saving)
* M_ASSIGN_WS = the matched container will be inserted in the target_ws.
* M_BELOW = the window will be inserted as a child of the matched container
* (dockareas)
*
*/
enum { M_HERE = 0, M_ASSIGN_WS, M_BELOW } insert_where;
TAILQ_ENTRY(Match) matches;
};
/**
* An Assignment makes specific windows go to a specific workspace/output or
* run a command for that window. With this mechanism, the user can -- for
* example -- make specific windows floating or assign his browser to workspace
* "www". Checking if a window is assigned works by comparing the Match data
* structure with the window (see match_matches_window()).
*
*/
struct Assignment {
/** type of this assignment:
*
* A_COMMAND = run the specified command for the matching window
* A_TO_WORKSPACE = assign the matching window to the specified workspace
* A_TO_OUTPUT = assign the matching window to the specified output
*
* While the type is a bitmask, only one value can be set at a time. It is
* a bitmask to allow filtering for multiple types, for example in the
* assignment_for() function.
*
*/
enum {
A_ANY = 0,
A_COMMAND = (1 << 0),
A_TO_WORKSPACE = (1 << 1),
A_TO_OUTPUT = (1 << 2)
} type;
/** the criteria to check if a window matches */
Match match;
/** destination workspace/output/command, depending on the type */
union {
char *command;
char *workspace;
char *output;
} dest;
TAILQ_ENTRY(Assignment) assignments;
};
struct Con {
bool mapped;
enum {
CT_ROOT = 0,
CT_OUTPUT = 1,
CT_CON = 2,
CT_FLOATING_CON = 3,
CT_WORKSPACE = 4,
CT_DOCKAREA = 5
} type;
orientation_t orientation;
struct Con *parent;
struct Rect rect;
struct Rect window_rect;
struct Rect deco_rect;
/** the geometry this window requested when getting mapped */
struct Rect geometry;
char *name;
/** the workspace number, if this Con is of type CT_WORKSPACE and the
* workspace is not a named workspace (for named workspaces, num == -1) */
int num;
/* a sticky-group is an identifier which bundles several containers to a
* group. The contents are shared between all of them, that is they are
* displayed on whichever of the containers is currently visible */
char *sticky_group;
/* user-definable mark to jump to this container later */
char *mark;
double percent;
/* proportional width/height, calculated from WM_NORMAL_HINTS, used to
* apply an aspect ratio to windows (think of MPlayer) */
int proportional_width;
int proportional_height;
/* the wanted size of the window, used in combination with size
* increments (see below). */
int base_width;
int base_height;
/* the x11 border pixel attribute */
int border_width;
/* minimum increment size specified for the window (in pixels) */
int width_increment;
int height_increment;
struct Window *window;
/* Should this container be marked urgent? This gets set when the window
* inside this container (if any) sets the urgency hint, for example. */
bool urgent;
/* ids/pixmap/graphics context for the frame window */
xcb_window_t frame;
xcb_pixmap_t pixmap;
xcb_gcontext_t pm_gc;
bool pixmap_recreated;
/** Cache for the decoration rendering */
struct deco_render_params *deco_render_params;
/* Only workspace-containers can have floating clients */
TAILQ_HEAD(floating_head, Con) floating_head;
TAILQ_HEAD(nodes_head, Con) nodes_head;
TAILQ_HEAD(focus_head, Con) focus_head;
TAILQ_HEAD(swallow_head, Match) swallow_head;
enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode;
enum { L_DEFAULT = 0, L_STACKED = 1, L_TABBED = 2, L_DOCKAREA = 3, L_OUTPUT = 4 } layout;
border_style_t border_style;
/** floating? (= not in tiling layout) This cannot be simply a bool
* because we want to keep track of whether the status was set by the
* application (by setting _NET_WM_WINDOW_TYPE appropriately) or by the
* user. The users choice overwrites automatic mode, of course. The
* order of the values is important because we check with >=
* FLOATING_AUTO_ON if a client is floating. */
enum {
FLOATING_AUTO_OFF = 0,
FLOATING_USER_OFF = 1,
FLOATING_AUTO_ON = 2,
FLOATING_USER_ON = 3
} floating;
/** This counter contains the number of UnmapNotify events for this
* container (or, more precisely, for its ->frame) which should be ignored.
* UnmapNotify events need to be ignored when they are caused by i3 itself,
* for example when reparenting or when unmapping the window on a workspace
* change. */
uint8_t ignore_unmap;
TAILQ_ENTRY(Con) nodes;
TAILQ_ENTRY(Con) focused;
TAILQ_ENTRY(Con) all_cons;
TAILQ_ENTRY(Con) floating_windows;
/** callbacks */
void(*on_remove_child)(Con *);
};
#endif

View File

@ -1,5 +1,5 @@
/*
* vim:ts=8:expandtab
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
*

View File

@ -11,14 +11,15 @@
#ifndef _FLOATING_H
#define _FLOATING_H
#include "tree.h"
/** Callback for dragging */
typedef void(*callback_t)(xcb_connection_t*, Client*, Rect*, uint32_t, uint32_t, void*);
typedef void(*callback_t)(Con*, 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)
static void name(Con *con, Rect *old_rect, uint32_t new_x, \
uint32_t new_y, void *extra)
/** On which border was the dragging initiated? */
typedef enum { BORDER_LEFT = (1 << 0),
@ -27,18 +28,45 @@ typedef enum { BORDER_LEFT = (1 << 0),
BORDER_BOTTOM = (1 << 3)} border_t;
/**
* Enters floating mode for the given client. Correctly takes care of the
* position/size (separately stored for tiling/floating mode) and
* repositions/resizes/redecorates the client.
* Enables floating mode for the given container by detaching it from its
* parent, creating a new container around it and storing this container in the
* floating_windows list of the workspace.
*
*/
void floating_enable(Con *con, bool automatic);
/**
* Disables floating mode for the given container by re-attaching the container
* to its old parent.
*
*/
void floating_disable(Con *con, bool automatic);
/**
* Calls floating_enable() for tiling containers and floating_disable() for
* floating containers.
*
* If the automatic flag is set to true, this was an automatic update by a
* change of the window class from the application which can be overwritten by
* the user.
*
*/
void toggle_floating_mode(xcb_connection_t *conn, Client *client,
bool automatic);
void toggle_floating_mode(Con *con, bool automatic);
/**
* Raises the given container in the list of floating containers
*
*/
void floating_raise_con(Con *con);
/**
* Checks if cons coordinates are within its workspace and re-assigns it to
* the actual workspace if not.
*
*/
bool floating_maybe_reassign_ws(Con *con);
#if 0
/**
* Removes the floating client from its workspace and attaches it to the new
* workspace. This is centralized here because it may happen if you move it
@ -56,13 +84,13 @@ 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);
#endif
/**
* Called when the user clicked on the titlebar of a floating window.
* Calls the drag_pointer function with the drag_window callback
*
*/
void floating_drag_window(xcb_connection_t *conn, Client *client,
xcb_button_press_event_t *event);
void floating_drag_window(Con *con, xcb_button_press_event_t *event);
/**
* Called when the user clicked on a floating window while holding the
@ -70,9 +98,9 @@ void floating_drag_window(xcb_connection_t *conn, Client *client,
* Calls the drag_pointer function with the resize_window callback
*
*/
void floating_resize_window(xcb_connection_t *conn, Client *client,
bool proportional, xcb_button_press_event_t *event);
void floating_resize_window(Con *con, bool proportional, xcb_button_press_event_t *event);
#if 0
/**
* Changes focus in the given direction for floating clients.
*
@ -97,6 +125,7 @@ void floating_move(xcb_connection_t *conn, Client *currently_focused,
*/
void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace);
#endif
/**
* This function grabs your pointer and lets you drag stuff around (borders).
* Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
@ -105,7 +134,7 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace);
* the event and the new coordinates (x, y).
*
*/
void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
void drag_pointer(Con *con, xcb_button_press_event_t *event,
xcb_window_t confine_to, border_t border, callback_t callback,
void *extra);

View File

@ -13,147 +13,48 @@
#include <xcb/randr.h>
/**
* 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);
extern int randr_base;
/**
* When the user moves the mouse pointer onto a window, this callback gets
* called.
* Adds the given sequence to the list of events which are ignored.
* If this ignore should only affect a specific response_type, pass
* response_type, otherwise, pass -1.
*
* Every ignored sequence number gets garbage collected after 5 seconds.
*
*/
int handle_enter_notify(void *ignored, xcb_connection_t *conn,
xcb_enter_notify_event_t *event);
void add_ignore_event(const int sequence, const int response_type);
/**
* When the user moves the mouse but does not change the active window
* (e.g. when having no windows opened but moving mouse on the root screen
* and crossing virtual screen boundaries), this callback gets called.
* Checks if the given sequence is ignored and returns true if so.
*
*/
int handle_motion_notify(void *ignored, xcb_connection_t *conn,
xcb_motion_notify_event_t *event);
bool event_is_ignored(const int sequence, const int response_type);
/**
* Called when the keyboard mapping changes (for example by using Xmodmap),
* we need to update our key bindings then (re-translate symbols).
* Takes an xcb_generic_event_t and calls the appropriate handler, based on the
* event type.
*
*/
int handle_mapping_notify(void *ignored, xcb_connection_t *conn,
xcb_mapping_notify_event_t *event);
void handle_event(int type, xcb_generic_event_t *event);
/**
* Checks if the button press was on a stack window, handles focus setting and
* returns true if so, or false otherwise.
* Sets the appropriate atoms for the property handlers after the atoms were
* received from X11
*
*/
int handle_button_press(void *ignored, xcb_connection_t *conn,
xcb_button_press_event_t *event);
/**
* A new window appeared on the screen (=was mapped), so lets manage it.
*
*/
int handle_map_request(void *prophs, xcb_connection_t *conn,
xcb_map_request_event_t *event);
void property_handlers_init();
#if 0
/**
* 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);
#endif
/**
* 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.
*
* We generate a synthethic configure notify event to signalize the client its
* "new" position.
*
*/
int handle_configure_request(void *prophs, xcb_connection_t *conn,
xcb_configure_request_event_t *event);
/**
* Our window decorations were unmapped. That means, the window will be killed
* now, so we better clean up before.
*
*/
int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event);
/**
* 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
*
*/
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);
/**
* We handle legacy window names (titles) which are in COMPOUND_TEXT
* encoding. However, we just pass them along, so when containing non-ASCII
* characters, those will be rendering incorrectly. In order to correctly
* render unicode window titles in i3, an application has to set _NET_WM_NAME,
* which is in UTF-8 encoding.
*
* On every update, a message is put out to the user, so he may improve the
* situation and update applications which display filenames in their title to
* correctly use _NET_WM_NAME and therefore support unicode.
*
*/
int handle_windowname_change_legacy(void *data, xcb_connection_t *conn,
uint8_t state, xcb_window_t window,
xcb_atom_t atom, xcb_get_property_reply_t
*prop);
/**
* Store the window classes for jumping to them later.
*
*/
int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom,
xcb_get_property_reply_t *prop);
/**
* Expose event means we should redraw our windows (= title bar)
*
*/
int handle_expose_event(void *data, xcb_connection_t *conn,
xcb_expose_event_t *event);
/**
* Handle client messages (EWMH)
*
*/
int handle_client_message(void *data, xcb_connection_t *conn,
xcb_client_message_event_t *event);
#if 0
/**
* Handles _NET_WM_WINDOW_TYPE changes
*
@ -161,44 +62,6 @@ int handle_client_message(void *data, xcb_connection_t *conn,
int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom,
xcb_get_property_reply_t *property);
/**
* Handles the size hints set by a window, but currently only the part
* necessary for displaying clients proportionally inside their frames
* (mplayer for example)
*
* See ICCCM 4.1.2.3 for more details
*
*/
int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t name,
xcb_get_property_reply_t *reply);
/**
* Handles the WM_HINTS property for extracting the urgency state of the window.
*
*/
int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
xcb_atom_t name, xcb_get_property_reply_t *reply);
/**
* Handles the transient for hints set by a window, signalizing that this
* window is a popup window for some other window.
*
* See ICCCM 4.1.2.6 for more details
*
*/
int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t name,
xcb_get_property_reply_t *reply);
/**
* Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a
* toolwindow (or similar) and to which window it belongs (logical parent).
*
*/
int handle_clientleader_change(void *data, xcb_connection_t *conn,
uint8_t state, xcb_window_t window,
xcb_atom_t name, xcb_get_property_reply_t *prop);
#endif
#endif

View File

@ -1,5 +1,5 @@
/*
* vim:ts=8:expandtab
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
*
@ -8,34 +8,32 @@
* See file LICENSE for license information.
*
*/
#include <xcb/xcb.h>
#include <xcb/xcb_property.h>
#include <xcb/xcb_event.h>
#include <xcb/xcb_keysyms.h>
#include <X11/XKBlib.h>
#include "queue.h"
#include "data.h"
#include "xcb.h"
#ifndef _I3_H
#define _I3_H
#define NUM_ATOMS 21
extern xcb_connection_t *global_conn;
extern xcb_connection_t *conn;
extern xcb_key_symbols_t *keysyms;
extern char **start_argv;
extern Display *xkbdpy;
extern Display *xlibdpy, *xkbdpy;
extern int xkb_current_group;
extern TAILQ_HEAD(bindings_head, Binding) *bindings;
extern TAILQ_HEAD(autostarts_head, Autostart) autostarts;
extern TAILQ_HEAD(autostarts_always_head, Autostart) autostarts_always;
extern TAILQ_HEAD(ws_assignments_head, Workspace_Assignment) ws_assignments;
extern TAILQ_HEAD(assignments_head, Assignment) assignments;
extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins;
extern xcb_event_handlers_t evenths;
extern xcb_screen_t *root_screen;
extern uint8_t root_depth;
extern bool xkb_supported;
extern xcb_atom_t atoms[NUM_ATOMS];
extern bool xcursor_supported, xkb_supported;
extern xcb_window_t root;
extern struct ev_loop *main_loop;
#endif

View File

@ -35,6 +35,10 @@
/** Requests the current outputs from i3 */
#define I3_IPC_MESSAGE_TYPE_GET_OUTPUTS 3
/** Requests the tree layout from i3 */
#define I3_IPC_MESSAGE_TYPE_GET_TREE 4
/*
* Messages from i3 to clients
*
@ -52,6 +56,10 @@
/** Outputs reply type */
#define I3_IPC_REPLY_TYPE_OUTPUTS 3
/** Tree reply type */
#define I3_IPC_REPLY_TYPE_TREE 4
/*
* Events from i3 to clients. Events have the first bit set high.
*

View File

@ -13,9 +13,17 @@
#define _IPC_H
#include <ev.h>
#include <stdbool.h>
#include <yajl/yajl_gen.h>
#include <yajl/yajl_parse.h>
#include "data.h"
#include "tree.h"
#include "i3/ipc.h"
extern char *current_socketpath;
typedef struct ipc_client {
int fd;
@ -74,4 +82,6 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa
*/
void ipc_shutdown();
void dump_node(yajl_gen gen, Con *con, bool inplace_restart);
#endif

View File

@ -1,94 +0,0 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include <xcb/xcb.h>
#ifndef _LAYOUT_H
#define _LAYOUT_H
/**
* Gets the unoccupied space (= space which is available for windows which
* were resized by the user) This is necessary to render both, customly
* resized windows and never touched windows correctly, meaning that the
* aspect ratio will be maintained when opening new windows.
*
*/
int get_unoccupied_x(Workspace *workspace);
/** See get_unoccupied_x */
int get_unoccupied_y(Workspace *workspace);
/**
* (Re-)draws window decorations for a given Client onto the given
* drawable/graphic context. When in stacking mode, the window decorations
* are drawn onto an own window.
*
*/
void decorate_window(xcb_connection_t *conn, Client *client,
xcb_drawable_t drawable, xcb_gcontext_t gc,
int offset_x, int offset_y);
/**
* Redecorates the given client correctly by checking if its in a stacking
* container and re-rendering the stack window or just calling decorate_window
* if its not in a stacking container.
*
*/
void redecorate_window(xcb_connection_t *conn, Client *client);
/**
* Pushes the clients x and y coordinates to X11
*
*/
void reposition_client(xcb_connection_t *conn, Client *client);
/**
* Pushes the clients width/height to X11 and resizes the child window. This
* function also updates the clients position, so if you work on tiling clients
* only, you can use this function instead of separate calls to reposition_client
* and resize_client to reduce flickering.
*
*/
void resize_client(xcb_connection_t *conn, Client *client);
/**
* Renders the given container. Is called by render_layout() or individually
* (for example when focus changes in a stacking container)
*
*/
void render_container(xcb_connection_t *conn, Container *container);
/**
* Modifies the event mask of all clients on the given workspace to either
* ignore or to handle enter notifies. It is handy to ignore notifies because
* they will be sent when a window is mapped under the cursor, thus when the
* user didnt enter the window actively at all.
*
*/
void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace,
bool ignore_enter_notify);
/**
* Renders the given workspace on the given screen
*
*/
void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws);
/**
* Renders the whole layout, that is: Go through each screen, each workspace,
* each container and render each client. This also renders the bars.
*
* If you dont need to render *everything*, you should call render_container
* on the container you want to refresh.
*
*/
void render_layout(xcb_connection_t *conn);
#endif

6
include/load_layout.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef _LOAD_LAYOUT_H
#define _LOAD_LAYOUT_H
void tree_append_json(const char *filename);
#endif

View File

@ -1,9 +1,9 @@
/*
* vim:ts=8:expandtab
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2010 Michael Stapelberg and contributors
* © 2009-2011 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -21,6 +21,14 @@
#define DLOG(fmt, ...) debuglog(LOGLEVEL, "%s:%s:%d - " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__)
extern char *loglevels[];
extern char *errorfilename;
/**
* Initializes logging by creating an error logfile in /tmp (or
* XDG_RUNTIME_DIR, see get_process_filename()).
*
*/
void init_logging();
/**
* Enables the given loglevel.
@ -41,7 +49,7 @@ void set_verbosity(bool _verbose);
* but only if the corresponding debug loglevel was activated.
*
*/
void debuglog(int lev, char *fmt, ...);
void debuglog(uint64_t lev, char *fmt, ...);
/**
* Logs the given message to stdout while prefixing the current time to it.

View File

@ -8,7 +8,6 @@
* See file LICENSE for license information.
*
*/
#include <xcb/xcb.h>
#include "data.h"
@ -20,8 +19,7 @@
* manage them
*
*/
void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t
*prophs, xcb_window_t root);
void manage_existing_windows(xcb_window_t root);
/**
* Restores the geometry of each window by reparenting it to the root window
@ -31,17 +29,17 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t
* side-effects which are to be expected when continuing to run i3.
*
*/
void restore_geometry(xcb_connection_t *conn);
void restore_geometry();
/**
* Do some sanity checks and then reparent the window.
*
*/
void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
xcb_window_t window,
void manage_window(xcb_window_t window,
xcb_get_window_attributes_cookie_t cookie,
bool needs_to_be_mapped);
#if 0
/**
* reparent_window() gets called when a new window was opened and becomes a
* child of the root window, or it gets called by us when we manage the
@ -56,3 +54,4 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
uint32_t border_width);
#endif
#endif

31
include/match.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef _MATCH_H
#define _MATCH_H
/*
* Initializes the Match data structure. This function is necessary because the
* members representing boolean values (like dock) need to be initialized with
* -1 instead of 0.
*
*/
void match_init(Match *match);
/**
* Check if a match is empty. This is necessary while parsing commands to see
* whether the user specified a match at all.
*
*/
bool match_is_empty(Match *match);
/**
* Copies the data of a match from src to dest.
*
*/
void match_copy(Match *dest, Match *src);
/**
* Check if a match data structure matches the given window.
*
*/
bool match_matches_window(Match *match, i3Window *window);
#endif

15
include/move.h Normal file
View File

@ -0,0 +1,15 @@
/*
* vim:ts=4:sw=4:expandtab
*/
#ifndef _MOVE_H
#define _MOVE_H
/**
* Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT,
* TOK_UP, TOK_DOWN from cmdparse.l)
*
*/
void tree_move(int direction);
#endif

14
include/output.h Normal file
View File

@ -0,0 +1,14 @@
/*
* vim:ts=4:sw=4:expandtab
*/
#ifndef _OUTPUT_H
#define _OUTPUT_H
/**
* Returns the output container below the given output container.
*
*/
Con *output_get_content(Con *output);
#endif

View File

@ -407,6 +407,19 @@ struct { \
_Q_INVALIDATE((elm)->field.tqe_next); \
} while (0)
/* Swaps two consecutive elements. 'second' *MUST* follow 'first' */
#define TAILQ_SWAP(first, second, head, field) do { \
*((first)->field.tqe_prev) = (second); \
(second)->field.tqe_prev = (first)->field.tqe_prev; \
(first)->field.tqe_prev = &((second)->field.tqe_next); \
(first)->field.tqe_next = (second)->field.tqe_next; \
if ((second)->field.tqe_next) \
(second)->field.tqe_next->field.tqe_prev = &((first)->field.tqe_next); \
(second)->field.tqe_next = first; \
if ((head)->tqh_last == &((second)->field.tqe_next)) \
(head)->tqh_last = &((first)->field.tqe_next); \
} while (0)
/*
* Circular queue definitions.
*/

View File

@ -22,7 +22,7 @@ extern struct outputs_head outputs;
* XRandR information to setup workspaces for each screen.
*
*/
void initialize_randr(xcb_connection_t *conn, int *event_base);
void randr_init(int *event_base);
/**
* Disables RandR support by creating exactly one output with the size of the
@ -31,17 +31,36 @@ void initialize_randr(xcb_connection_t *conn, int *event_base);
*/
void disable_randr(xcb_connection_t *conn);
/**
* Initializes a CT_OUTPUT Con (searches existing ones from inplace restart
* before) to use for the given Output.
*
*/
void output_init_con(Output *output);
/**
* Initializes at least one workspace for this output, trying the following
* steps until there is at least one workspace:
*
* Move existing workspaces, which are assigned to be on the given output, to
* the output.
* Create the first assigned workspace for this output.
* Create the first unused workspace.
*
*/
void init_ws_for_output(Output *output, Con *content);
/**
* Initializes the specified output, assigning the specified workspace to it.
*
*/
void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace);
//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);
void randr_query_outputs();
/**
* Returns the first output which is active.

18
include/render.h Normal file
View File

@ -0,0 +1,18 @@
/*
* vim:ts=4:sw=4:expandtab
*/
#ifndef _RENDER_H
#define _RENDER_H
/**
* "Renders" the given container (and its children), meaning that all rects are
* updated correctly. Note that this function does not call any xcb_*
* functions, so the changes are completely done in memory only (and
* side-effect free). As soon as you call x_push_changes(), the changes will be
* updated in X11.
*
*/
void render_con(Con *con, bool render_fullscreen);
#endif

View File

@ -1,36 +1,6 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#ifndef _RESIZE_H
#define _RESIZE_H
#include <xcb/xcb.h>
typedef enum { O_HORIZONTAL, O_VERTICAL } resize_orientation_t;
/**
* Renders the resize window between the first/second container and resizes
* the table column/row.
*
*/
int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first,
int second, resize_orientation_t orientation,
xcb_button_press_event_t *event);
/**
* Resizes a column/row by the given amount of pixels. Called by
* resize_graphical_handler (the user clicked) or parse_resize_command (the
* user issued the command)
*
*/
void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int second,
resize_orientation_t orientation, int pixels);
int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, xcb_button_press_event_t *event);
#endif

View File

@ -1,5 +1,5 @@
/*
* vim:ts=8:expandtab
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
*

View File

@ -1,72 +0,0 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include <stdbool.h>
#include <xcb/xcb.h>
#include "data.h"
#ifndef _TABLE_H
#define _TABLE_H
#define CUR_TABLE (c_ws->table)
#define CUR_CELL (CUR_TABLE[current_col][current_row])
extern Workspace *c_ws;
extern TAILQ_HEAD(workspaces_head, Workspace) *workspaces;
//extern int num_workspaces;
extern int current_col;
extern int current_row;
/** Initialize table */
void init_table();
/** Add one row to the table */
void expand_table_rows(Workspace *workspace);
/** Adds one row at the head of the table */
void expand_table_rows_at_head(Workspace *workspace);
/** Add one column to the table */
void expand_table_cols(Workspace *workspace);
/**
* Inserts one column at the tables head
*
*/
void expand_table_cols_at_head(Workspace *workspace);
/**
* Performs simple bounds checking for the given column/row
*
*/
bool cell_exists(Workspace *ws, int col, int row);
/**
* Shrinks the table by "compacting" it, that is, removing completely empty
* rows/columns
*
*/
void cleanup_table(xcb_connection_t *conn, Workspace *workspace);
/**
* Fixes col/rowspan (makes sure there are no overlapping windows)
*
*/
void fix_colrowspan(xcb_connection_t *conn, Workspace *workspace);
/**
* Prints the tables contents in human-readable form for debugging
*
*/
void dump_table(xcb_connection_t *conn, Workspace *workspace);
#endif

97
include/tree.h Normal file
View File

@ -0,0 +1,97 @@
/*
* vim:ts=4:sw=4:expandtab
*/
#ifndef _TREE_H
#define _TREE_H
extern Con *croot;
/* TODO: i am not sure yet how much access to the focused container should
* be permitted to source files */
extern Con *focused;
TAILQ_HEAD(all_cons_head, Con);
extern struct all_cons_head all_cons;
/**
* Initializes the tree by creating the root node, adding all RandR outputs
* to the tree (that means randr_init() has to be called before) and
* assigning a workspace to each RandR output.
*
*/
void tree_init(xcb_get_geometry_reply_t *geometry);
/**
* Opens an empty container in the current container
*
*/
Con *tree_open_con(Con *con, i3Window *window);
/**
* Splits (horizontally or vertically) the given container by creating a new
* container which contains the old one and the future ones.
*
*/
void tree_split(Con *con, orientation_t orientation);
/**
* Moves focus one level up.
*
*/
void level_up();
/**
* Moves focus one level down.
*
*/
void level_down();
/**
* Renders the tree, that is rendering all outputs using render_con() and
* pushing the changes to X11 using x_push_changes().
*
*/
void tree_render();
/**
* Closes the current container using tree_close().
*
*/
void tree_close_con(kill_window_t kill_window);
/**
* Changes focus in the given way (next/previous) and given orientation
* (horizontal/vertical).
*
*/
void tree_next(char way, orientation_t orientation);
/**
* Closes the given container including all children.
* Returns true if the container was killed or false if just WM_DELETE was sent
* and the window is expected to kill itself.
*
*/
bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent);
/**
* Loads tree from ~/.i3/_restart.json (used for in-place restarts).
*
*/
bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry);
/**
* tree_flatten() removes pairs of redundant split containers, e.g.:
* [workspace, horizontal]
* [v-split] [child3]
* [h-split]
* [child1] [child2]
* In this example, the v-split and h-split container are redundant.
* Such a situation can be created by moving containers in a direction which is
* not the orientation of their parent container. i3 needs to create a new
* split container then and if you move containers this way multiple times,
* redundant chains of split-containers can be the result.
*
*/
void tree_flatten(Con *child);
#endif

View File

@ -8,7 +8,6 @@
* See file LICENSE for license information.
*
*/
#include <xcb/xcb.h>
#include <err.h>
#include "data.h"
@ -26,6 +25,21 @@
#define FOR_TABLE(workspace) \
for (int cols = 0; cols < (workspace)->cols; cols++) \
for (int rows = 0; rows < (workspace)->rows; rows++)
#define NODES_FOREACH(head) \
for (Con *child = (Con*)-1; (child == (Con*)-1) && ((child = 0), true);) \
TAILQ_FOREACH(child, &((head)->nodes_head), nodes)
/* greps the ->nodes of the given head and returns the first node that matches the given condition */
#define GREP_FIRST(dest, head, condition) \
NODES_FOREACH(head) { \
if (!(condition)) \
continue; \
\
(dest) = child; \
break; \
}
#define FREE(pointer) do { \
if (pointer != NULL) { \
free(pointer); \
@ -34,12 +48,12 @@
} \
while (0)
TAILQ_HEAD(keyvalue_table_head, keyvalue_element);
extern struct keyvalue_table_head by_parent;
extern struct keyvalue_table_head by_child;
#define CALL(obj, member, ...) obj->member(obj, ## __VA_ARGS__)
int min(int a, int b);
int max(int a, int b);
bool rect_contains(Rect rect, uint32_t x, uint32_t y);
Rect rect_add(Rect a, Rect b);
/**
* Updates *destination with new_value and returns true if it was changed or false
@ -62,6 +76,13 @@ void *smalloc(size_t size);
*/
void *scalloc(size_t size);
/**
* Safe-wrapper around realloc which exits if realloc returns NULL (meaning
* that there is no more memory available).
*
*/
void *srealloc(void *ptr, size_t size);
/**
* Safe-wrapper around strdup which exits if malloc returns NULL (meaning that
* there is no more memory available)
@ -69,26 +90,6 @@ void *scalloc(size_t size);
*/
char *sstrdup(const char *str);
/**
* Inserts an element into the given keyvalue-table using the given key.
*
*/
bool table_put(struct keyvalue_table_head *head, uint32_t key, void *value);
/**
* Removes the element from the given keyvalue-table with the given key and
* returns its value;
*
*/
void *table_remove(struct keyvalue_table_head *head, uint32_t key);
/**
* Returns the value of the element of the given keyvalue-table with the given
* key.
*
*/
void *table_get(struct keyvalue_table_head *head, uint32_t key);
/**
* Starts the given application by passing it through a shell. We use double
* fork to avoid zombie processes. As the started applications parent exits
@ -101,6 +102,23 @@ void *table_get(struct keyvalue_table_head *head, uint32_t key);
*/
void start_application(const char *command);
/**
* exec()s an i3 utility, for example the config file migration script or
* i3-nagbar. This function first searches $PATH for the given utility named,
* then falls back to the dirname() of the i3 executable path and then falls
* back to the dirname() of the target of /proc/self/exe (on linux).
*
* This function should be called after fork()ing.
*
* The first argument of the given argv vector will be overwritten with the
* executable name, so pass NULL.
*
* If the utility cannot be found in any of these locations, it exits with
* return code 2.
*
*/
void exec_i3_utility(char *name, char *argv[]);
/**
* Checks a generic cookie for errors and quits with the given message if
* there was an error.
@ -119,54 +137,54 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie,
char *convert_utf8_to_ucs2(char *input, int *real_strlen);
/**
* Returns the client which comes next in focus stack (= was selected before) for
* the given container, optionally excluding the given client.
* This function resolves ~ in pathnames.
* It may resolve wildcards in the first part of the path, but if no match
* or multiple matches are found, it just returns a copy of path as given.
*
*/
Client *get_last_focused_client(xcb_connection_t *conn, Container *container,
Client *exclude);
char *resolve_tilde(const char *path);
/**
* Sets the given client as focused by updating the data structures correctly,
* updating the X input focus and finally re-decorating both windows (to
* signalize the user the new focus situation)
* Checks if the given path exists by calling stat().
*
*/
void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways);
bool path_exists(const char *path);
/**
* Called when the user switches to another mode or when the container is
* destroyed and thus needs to be cleaned up.
* Returns the name of a temporary file with the specified prefix.
*
*/
void leave_stack_mode(xcb_connection_t *conn, Container *container);
char *get_process_filename(const char *prefix);
/**
* Switches the layout of the given container taking care of the necessary
* house-keeping
*
*/
void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode);
/**
* Gets the first matching client for the given window class/window title.
* If the paramater specific is set to a specific client, only this one
* will be checked.
*
*/
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();
void i3_restart(bool forget_layout);
#if defined(__OpenBSD__)
/* OpenBSD does not provide memmem(), so we provide FreeBSDs implementation */
#if defined(__OpenBSD__) || defined(__APPLE__)
/*
* Taken from FreeBSD
* Find the first occurrence of the byte string s in byte string l.
*
*/
void *memmem(const void *l, size_t l_len, const void *s, size_t s_len);
#endif
#if defined(__APPLE__)
/*
* Taken from FreeBSD
* Returns a pointer to a new string which is a duplicate of the
* string, but only copies at most n characters.
*
*/
char *strndup(const char *str, size_t n);
#endif
#endif

45
include/window.h Normal file
View File

@ -0,0 +1,45 @@
#ifndef _WINDOW_H
#define _WINDOW_H
/**
* Updates the WM_CLASS (consisting of the class and instance) for the
* given window.
*
*/
void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
/**
* Updates the name by using _NET_WM_NAME (encoded in UTF-8) for the given
* window. Further updates using window_update_name_legacy will be ignored.
*
*/
void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
/**
* Updates the name by using WM_NAME (encoded in COMPOUND_TEXT). We do not
* touch what the client sends us but pass it to xcb_image_text_8. To get
* proper unicode rendering, the application has to use _NET_WM_NAME (see
* window_update_name()).
*
*/
void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
/**
* Updates the CLIENT_LEADER (logical parent window).
*
*/
void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop);
/**
* Updates the TRANSIENT_FOR (logical parent window).
*
*/
void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop);
/**
* Updates the _NET_WM_STRUT_PARTIAL (reserved pixels at the screen edges)
*
*/
void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop);
#endif

View File

@ -8,9 +8,9 @@
* See file LICENSE for license information.
*
*/
#include <xcb/xcb.h>
#include "data.h"
#include "tree.h"
#include "randr.h"
#ifndef _WORKSPACE_H
@ -21,9 +21,13 @@
* creating the workspace if necessary (by allocating the necessary amount of
* memory and initializing the data structures correctly).
*
* If created is not NULL, *created will be set to whether or not the
* workspace has just been created.
*
*/
Workspace *workspace_get(int number);
Con *workspace_get(const char *num, bool *created);
#if 0
/**
* Sets the name (or just its number) for the given workspace. This has to
* be called for every workspace as the rendering function
@ -32,6 +36,7 @@ Workspace *workspace_get(int number);
*
*/
void workspace_set_name(Workspace *ws, const char *name);
#endif
/**
* Returns true if the workspace is currently visible. Especially important for
@ -39,11 +44,24 @@ void workspace_set_name(Workspace *ws, const char *name);
* workspaces.
*
*/
bool workspace_is_visible(Workspace *ws);
bool workspace_is_visible(Con *ws);
/** Switches to the given workspace */
void workspace_show(xcb_connection_t *conn, int workspace);
void workspace_show(const char *num);
/**
* Focuses the next workspace.
*
*/
void workspace_next();
/**
* Focuses the previous workspace.
*
*/
void workspace_prev();
#if 0
/**
* Assigns the given workspace to the given screen by correctly updating its
* state and reconfiguring all the clients on this workspace.
@ -86,25 +104,33 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws);
*
*/
void workspace_map_clients(xcb_connection_t *conn, Workspace *ws);
#endif
/**
* Goes through all clients on the given workspace and updates the workspaces
* urgent flag accordingly.
*
*/
void workspace_update_urgent_flag(Workspace *ws);
void workspace_update_urgent_flag(Con *ws);
/*
* Returns the width of the workspace.
/**
* 'Forces' workspace orientation by moving all cons into a new split-con with
* the same orientation as the workspace and then changing the workspace
* orientation.
*
*/
int workspace_width(Workspace *ws);
void ws_force_orientation(Con *ws, orientation_t orientation);
/*
* Returns the effective height of the workspace (without the internal bar and
* without dock clients).
/**
* Called when a new con (with a window, not an empty or split con) should be
* attached to the workspace (for example when managing a new window or when
* moving an existing window to the workspace level).
*
* Depending on the workspace_layout setting, this function either returns the
* workspace itself (default layout) or creates a new stacked/tabbed con and
* returns that.
*
*/
int workspace_height(Workspace *ws);
Con *workspace_attach_to(Con *ws);
#endif

107
include/x.h Normal file
View File

@ -0,0 +1,107 @@
/*
* vim:ts=4:sw=4:expandtab
*/
#ifndef _X_H
#define _X_H
/** Stores the X11 window ID of the currently focused window */
extern xcb_window_t focused_id;
/**
* Initializes the X11 part for the given container. Called exactly once for
* every container from con_new().
*
*/
void x_con_init(Con *con);
/**
* Moves a child window from Container src to Container dest.
*
*/
void x_move_win(Con *src, Con *dest);
/**
* Reparents the child window of the given container (necessary for sticky
* containers). The reparenting happens in the next call of x_push_changes().
*
*/
void x_reparent_child(Con *con, Con *old);
/**
* Re-initializes the associated X window state for this container. You have
* to call this when you assign a client to an empty container to ensure that
* its state gets updated correctly.
*
*/
void x_reinit(Con *con);
/**
* Kills the window decoration associated with the given container.
*
*/
void x_con_kill(Con *con);
/**
* Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
*
*/
bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom);
/**
* Kills the given X11 window using WM_DELETE_WINDOW (if supported).
*
*/
void x_window_kill(xcb_window_t window, kill_window_t kill_window);
/**
* Draws the decoration of the given container onto its parent.
*
*/
void x_draw_decoration(Con *con);
/**
* Recursively calls x_draw_decoration. This cannot be done in x_push_node
* because x_push_node uses focus order to recurse (see the comment above)
* while drawing the decoration needs to happen in the actual order.
*
*/
void x_deco_recurse(Con *con);
/**
* This function pushes the properties of each node of the layout tree to
* X11 if they have changed (like the map state, position of the window, ).
* It recursively traverses all children of the given node.
*
*/
void x_push_node(Con *con);
/**
* Pushes all changes (state of each node, see x_push_node() and the window
* stack) to X11.
*
*/
void x_push_changes(Con *con);
/**
* Raises the specified container in the internal stack of X windows. The
* next call to x_push_changes() will make the change visible in X11.
*
*/
void x_raise_con(Con *con);
/**
* Sets the WM_NAME property (so, no UTF8, but used only for debugging anyways)
* of the given name. Used for properly tagging the windows for easily spotting
* i3 windows in xwininfo -root -all.
*
*/
void x_set_name(Con *con, const char *name);
/**
* Sets up i3 specific atoms (I3_SOCKET_PATH and I3_CONFIG_PATH)
*
*/
void x_set_i3_atoms();
#endif

View File

@ -1,9 +1,9 @@
/*
* vim:ts=8:expandtab
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
* © 2009-2011 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -12,6 +12,7 @@
#define _XCB_H
#include "data.h"
#include "xcursor.h"
#define _NET_WM_STATE_REMOVE 0
#define _NET_WM_STATE_ADD 1
@ -32,49 +33,31 @@
/** The XCB_CW_EVENT_MASK for the child (= real window) */
#define CHILD_EVENT_MASK (XCB_EVENT_MASK_PROPERTY_CHANGE | \
XCB_EVENT_MASK_STRUCTURE_NOTIFY | \
XCB_EVENT_MASK_ENTER_WINDOW)
XCB_EVENT_MASK_FOCUS_CHANGE)
/** The XCB_CW_EVENT_MASK for its frame */
#define FRAME_EVENT_MASK (XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed/released */ \
XCB_EVENT_MASK_BUTTON_RELEASE | \
XCB_EVENT_MASK_POINTER_MOTION | /* …mouse is moved */ \
XCB_EVENT_MASK_EXPOSURE | /* …our window needs to be redrawn */ \
XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* …the frame gets destroyed */ \
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | /* …the application tries to resize itself */ \
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | /* …subwindows get notifies */ \
XCB_EVENT_MASK_ENTER_WINDOW) /* …user moves cursor inside our window */
enum { _NET_SUPPORTED = 0,
_NET_SUPPORTING_WM_CHECK,
_NET_WM_NAME,
_NET_WM_STATE_FULLSCREEN,
_NET_WM_STATE,
_NET_WM_WINDOW_TYPE,
_NET_WM_WINDOW_TYPE_DOCK,
_NET_WM_WINDOW_TYPE_DIALOG,
_NET_WM_WINDOW_TYPE_UTILITY,
_NET_WM_WINDOW_TYPE_TOOLBAR,
_NET_WM_WINDOW_TYPE_SPLASH,
_NET_WM_DESKTOP,
_NET_WM_STRUT_PARTIAL,
_NET_CURRENT_DESKTOP,
_NET_ACTIVE_WINDOW,
_NET_WORKAREA,
WM_PROTOCOLS,
WM_DELETE_WINDOW,
UTF8_STRING,
WM_STATE,
WM_CLIENT_LEADER
};
#define xmacro(atom) xcb_atom_t A_ ## atom;
#include "atoms.xmacro"
#undef xmacro
extern unsigned int xcb_numlock_mask;
/**
* Loads a font for usage, getting its height. This function is used very
* often, so it maintains a cache.
* Loads a font for usage, also getting its height. If fallback is true,
* i3 loads 'fixed' or '-misc-*' if the font cannot be found instead of
* exiting.
*
*/
i3Font *load_font(xcb_connection_t *conn, const char *pattern);
i3Font load_font(const char *pattern, bool fallback);
/**
* Returns the colorpixel to use for the given hex color (think of HTML).
@ -85,7 +68,7 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern);
* validity. This has to be done by the caller.
*
*/
uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
uint32_t get_colorpixel(char *hex);
/**
* Convenience wrapper around xcb_create_window which takes care of depth,
@ -93,7 +76,7 @@ uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
*
*/
xcb_window_t create_window(xcb_connection_t *conn, Rect r, uint16_t window_class,
int cursor, bool map, uint32_t mask, uint32_t *values);
enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values);
/**
* Changes a single value in the graphic context (so one doesnt have to
@ -132,7 +115,13 @@ void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window);
* the X root window, not to the clients frame) for the given client.
*
*/
void fake_absolute_configure_notify(xcb_connection_t *conn, Client *client);
void fake_absolute_configure_notify(Con *con);
/**
* Sends the WM_TAKE_FOCUS ClientMessage to the given window
*
*/
void send_take_focus(xcb_window_t window);
/**
* Finds out which modifier mask is the one for numlock, as the user may
@ -147,22 +136,12 @@ void xcb_get_numlock_mask(xcb_connection_t *conn);
*/
void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window);
/**
*
* Prepares the given Cached_Pixmap for usage (checks whether the size of the
* object this pixmap is related to (e.g. a window) has changed and re-creates
* the pixmap if so).
*
*/
void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap);
/**
* Calculate the width of the given text (16-bit characters, UCS) with given
* real length (amount of glyphs) using the given font.
*
*/
int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *text,
int length);
int predict_text_width(char *text, int length);
/**
* Configures the given window to have the size/position specified by given rect
@ -170,4 +149,7 @@ int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *t
*/
void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r);
bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom);
#endif

25
include/xcb_compat.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef _XCB_COMPAT_H
#define _XCB_COMPAT_H
#define xcb_icccm_get_wm_protocols_reply_t xcb_get_wm_protocols_reply_t
#define xcb_icccm_get_wm_protocols xcb_get_wm_protocols
#define xcb_icccm_get_wm_protocols_unchecked xcb_get_wm_protocols_unchecked
#define xcb_icccm_get_wm_protocols_reply xcb_get_wm_protocols_reply
#define xcb_icccm_get_wm_protocols_reply_wipe xcb_get_wm_protocols_reply_wipe
#define XCB_ICCCM_WM_STATE_NORMAL XCB_WM_STATE_NORMAL
#define XCB_ICCCM_WM_STATE_WITHDRAWN XCB_WM_STATE_WITHDRAWN
#define xcb_icccm_get_wm_size_hints_from_reply xcb_get_wm_size_hints_from_reply
#define xcb_icccm_get_wm_normal_hints_reply xcb_get_wm_normal_hints_reply
#define xcb_icccm_get_wm_normal_hints_unchecked xcb_get_wm_normal_hints_unchecked
#define XCB_ICCCM_SIZE_HINT_P_MIN_SIZE XCB_SIZE_HINT_P_MIN_SIZE
#define XCB_ICCCM_SIZE_HINT_P_RESIZE_INC XCB_SIZE_HINT_P_RESIZE_INC
#define XCB_ICCCM_SIZE_HINT_BASE_SIZE XCB_SIZE_HINT_BASE_SIZE
#define XCB_ICCCM_SIZE_HINT_P_ASPECT XCB_SIZE_HINT_P_ASPECT
#define xcb_icccm_wm_hints_t xcb_wm_hints_t
#define xcb_icccm_get_wm_hints_from_reply xcb_get_wm_hints_from_reply
#define xcb_icccm_get_wm_hints_reply xcb_get_wm_hints_reply
#define xcb_icccm_get_wm_hints_unchecked xcb_get_wm_hints_unchecked
#define xcb_icccm_wm_hints_get_urgency xcb_wm_hints_get_urgency
#define xcb_icccm_get_wm_transient_for_from_reply xcb_get_wm_transient_for_from_reply
#endif

33
include/xcursor.h Normal file
View File

@ -0,0 +1,33 @@
/*
* vim:ts=4:sw=4:expandtab
*/
#ifndef _XCURSOR_CURSOR_H
#define _XCURSOR_CURSOR_H
#include <X11/Xlib.h>
enum xcursor_cursor_t {
XCURSOR_CURSOR_POINTER = 0,
XCURSOR_CURSOR_RESIZE_HORIZONTAL,
XCURSOR_CURSOR_RESIZE_VERTICAL,
XCURSOR_CURSOR_MAX
};
void xcursor_load_cursors();
Cursor xcursor_get_cursor(enum xcursor_cursor_t c);
int xcursor_get_xcb_cursor(enum xcursor_cursor_t c);
/**
* Sets the cursor of the root window to the 'pointer' cursor.
*
* This function is called when i3 is initialized, because with some login
* managers, the root window will not have a cursor otherwise.
*
* We have a separate xcursor function to use the same X11 connection as the
* xcursor_load_cursors() function. If we mix the Xlib and the XCB connection,
* races might occur (even though we flush the Xlib connection).
*
*/
void xcursor_set_root_cursor();
#endif

View File

@ -18,6 +18,6 @@
* Xinerama information to setup workspaces for each screen.
*
*/
void initialize_xinerama(xcb_connection_t *conn);
void xinerama_init();
#endif

View File

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

View File

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

View File

@ -23,6 +23,14 @@ mark/goto command.
i3-input -p 'mark ' -l 1 -P 'Mark: '
------------------------------------------------
== ENVIRONMENT
=== I3SOCK
If no ipc-socket is specified on the commandline, this variable is used
to determine the path, at wich the unix domain socket is expected, on which
to connect to i3.
== SEE ALSO
i3(1)

View File

@ -24,6 +24,14 @@ future (staying backwards-compatible, of course).
i3-msg "bp" # Use 1-px border for current client
------------------------------------------------
== ENVIRONMENT
=== I3SOCK
If no ipc-socket is specified on the commandline, this variable is used
to determine the path, at wich the unix domain socket is expected, on which
to connect to i3.
== SEE ALSO
i3(1)

34
man/i3-nagbar.man Normal file
View File

@ -0,0 +1,34 @@
i3-nagbar(1)
============
Michael Stapelberg <michael+i3@stapelberg.de>
v4.0, July 2011
== NAME
i3-nagbar - displays an error bar on top of your screen
== SYNOPSIS
i3-nagbar -m 'message' -b 'label' 'action'
== DESCRIPTION
i3-nagbar is used by i3 to tell you about errors in your configuration file
(for example). While these errors are logged to the logfile (if any), the past
has proven that users are either not aware of their logfile or do not check it
after modifying the configuration file.
== EXAMPLE
------------------------------------------------
i3-nagbar -m 'You have an error in your i3 config file!' \
-b 'edit config' 'xterm $EDITOR ~/.i3/config'
------------------------------------------------
== SEE ALSO
i3(1)
== AUTHOR
Michael Stapelberg and contributors

View File

@ -156,10 +156,10 @@ Exits i3.
When starting, i3 looks for configuration files in the following order:
1. ~/.i3/config
2. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set)
3. /etc/i3/config
4. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set)
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
You can specify a custom path using the -c option.
@ -168,84 +168,84 @@ You can specify a custom path using the -c option.
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# Start terminal (Mod1+Enter)
bind Mod1+36 exec /usr/bin/urxvt
bindcode Mod1+36 exec /usr/bin/urxvt
# Start dmenu (Mod1+v)
bind Mod1+55 exec /usr/bin/dmenu_run
bindcode Mod1+55 exec /usr/bin/dmenu_run
# Kill current client (Mod1+Shift+q)
bind Mod1+Shift+24 kill
bindcode Mod1+Shift+24 kill
# Beamer on/off
bind Mod1+73 exec /home/michael/toggle_beamer.sh
bindcode Mod1+73 exec /home/michael/toggle_beamer.sh
# Screen locking
bind Mod1+68 exec /usr/bin/i3lock
bindcode Mod1+68 exec /usr/bin/i3lock
# Restart i3 inplace (Mod1+Shift+r)
bind Mod1+Shift+27 restart
bindcode Mod1+Shift+27 restart
# Exit i3 (Mod1+Shift+e)
bind Mod1+Shift+26 exit
bindcode Mod1+Shift+26 exit
# Brightness
bind Mod1+97 exec sudo sh -c "echo up > /proc/acpi/ibm/brightness"
bind Mod1+103 exec sudo sh -c "echo down > /proc/acpi/ibm/brightness"
bindcode Mod1+97 exec sudo sh -c "echo up > /proc/acpi/ibm/brightness"
bindcode Mod1+103 exec sudo sh -c "echo down > /proc/acpi/ibm/brightness"
# Fullscreen (Mod1+f)
bind Mod1+41 f
bindcode Mod1+41 f
# Stacking (Mod1+h)
bind Mod1+43 s
bindcode Mod1+43 s
# Default (Mod1+e)
bind Mod1+26 d
bindcode Mod1+26 d
# Toggle tiling/floating of the current window (Mod1+Shift+Space)
bind Mod1+Shift+65 t
bindcode Mod1+Shift+65 t
# Go into the tiling layer / floating layer, depending on whether
# the current window is tiling / floating (Mod1+t)
bind Mod1+28 focus ft
bindcode Mod1+28 focus ft
# Focus (Mod1+j/k/l/;)
bind Mod1+44 h
bind Mod1+45 j
bind Mod1+46 k
bind Mod1+47 l
bindcode Mod1+44 h
bindcode Mod1+45 j
bindcode Mod1+46 k
bindcode Mod1+47 l
# Focus Container (Mod3+j/k/l/;)
bind Mod3+44 wch
bind Mod3+45 wcj
bind Mod3+46 wck
bind Mod3+47 wcl
bindcode Mod3+44 wch
bindcode Mod3+45 wcj
bindcode Mod3+46 wck
bindcode Mod3+47 wcl
# Snap (Mod1+Control+j/k/l/;)
bind Mod1+Control+44 sh
bind Mod1+Control+45 sj
bind Mod1+Control+46 sk
bind Mod1+Control+47 sl
bindcode Mod1+Control+44 sh
bindcode Mod1+Control+45 sj
bindcode Mod1+Control+46 sk
bindcode Mod1+Control+47 sl
# Move (Mod1+Shift+j/k/l/;)
bind Mod1+Shift+44 mh
bind Mod1+Shift+45 mj
bind Mod1+Shift+46 mk
bind Mod1+Shift+47 ml
bindcode Mod1+Shift+44 mh
bindcode Mod1+Shift+45 mj
bindcode Mod1+Shift+46 mk
bindcode Mod1+Shift+47 ml
# Move Container (Mod3+Shift+j/k/l/;)
bind Mod3+Shift+44 wcmh
bind Mod3+Shift+45 wcmj
bind Mod3+Shift+46 wcmk
bind Mod3+Shift+47 wcml
bindcode Mod3+Shift+44 wcmh
bindcode Mod3+Shift+45 wcmj
bindcode Mod3+Shift+46 wcmk
bindcode Mod3+Shift+47 wcml
# Workspaces
bind Mod1+10 1
bind Mod1+11 2
bindcode Mod1+10 1
bindcode Mod1+11 2
...
# Move to Workspace
bind Mod1+Shift+10 1
bind Mod1+Shift+11 2
bindcode Mod1+Shift+10 1
bindcode Mod1+Shift+11 2
...
-------------------------------------------------------------
@ -294,6 +294,14 @@ echo "Starting at $(date)" >> ~/.i3/logfile
exec /usr/bin/i3 -V -d all >> ~/.i3/logfile
-------------------------------------------------------------
== ENVIRONMENT
=== I3SOCK
If no ipc-socket is specified in the configfile, this variable is used
to determine the path, at wich the unix domain socket is created, on which
i3 listenes to incoming connections.
== TODO
There is still lot of work to do. Please check our bugtracker for up-to-date

21
render-tree/Con.pm Normal file
View File

@ -0,0 +1,21 @@
# vim:ts=4:sw=4:expandtab
package Con;
use Moose;
use MooseX::AttributeHelpers;
use v5.10;
has 'name' => (is => 'ro', isa => 'Str');
has 'width' => (is => 'rw', isa => 'Int', default => 100);
has '_nodes' => (is => 'ro', metaclass => 'Collection::Array', isa => 'ArrayRef[Con]',
default => sub { [] },
provides => {
'push' => 'add_node',
elements => 'nodes',
}
);
has 'parent' => (is => 'rw', isa => 'Con', predicate => 'has_parent');
__PACKAGE__->meta->make_immutable;
1

125
render-tree/render.pl Executable file
View File

@ -0,0 +1,125 @@
#!/usr/bin/env perl
# vim:ts=4:sw=4:expandtab
# © 2011 Michael Stapelberg, see LICENSE
#
# Needs SVG (libsvg-perl), IO::All (libio-all-perl), JSON::XS (libjson-xs-perl) and Moose (libmoose-perl)
#
# XXX: unfinished proof-of-concept. awaits a json dump in my.tree, renders to test.svg
# XXX: needs more beautifying (in the SVG but also in the code)
# XXX: has some rendering differences between firefox and chromium. maybe inkscape makes the file look the same in both browsers
use strict;
use warnings;
use SVG;
use Data::Dumper;
use JSON::XS;
use IO::All;
use List::Util qw(sum);
use lib qw(.);
use Con;
use v5.10;
my $input = io('my.tree')->slurp;
my $tree = decode_json($input);
my $root = parse_tree($tree);
render_tree($root);
sub parse_tree {
my ($input, $parent) = @_;
my $con = Con->new(name => $input->{name});
$con->parent($parent) if defined($parent);
for my $node (@{$input->{nodes}}) {
$con->add_node(parse_tree($node, $con));
}
return $con;
}
sub render_tree {
my ($con) = @_;
say 'rendering con ' . $con->name;
my @nodes = $con->nodes;
for my $node (@nodes) {
render_tree($node);
}
# nothing to calculate when there are no children
return unless @nodes > 0;
$con->width((@nodes > 1 ? (@nodes - 1) * 20 : 0) + sum map { $_->width } @nodes);
say $con->name . ' has width ' . $con->width;
}
# TODO: figure out the height
my $svg = SVG->new(id => "tree", width => $root->width + 5, height => '1052');
my $l1 = $svg->group(id => 'layer1');
# gaussian blur (for drop shadows)
$svg->defs()->filter(id => 'dropshadow')->fe(-type => 'gaussianblur', stdDeviation => '2.19');
my $idcnt = 0;
my $y = 10;
render_svg($root, 0, 0);
sub render_svg {
my ($con, $level, $x) = @_;
my $indent = ' ' x $level;
say $indent . 'svg-rendering con ' . $con->name . ' on level ' . $level;
say $indent . 'width: ' . $con->width;
# render the dropshadow rect
$l1->rect(
id => 'outer_rect_shadow' . $idcnt,
style => 'opacity:1.0;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-opacity:1;stroke-miterlimit:4;filter:url(#dropshadow)',
width => "96",
height => '50',
#x => $x + ($con->has_parent ? ($con->parent->width - 100) / 2 : 0),
x => $x + ($con->width / 2) - (96 / 2) + 0,
y => 4 + $level * 70 + 0,
);
$idcnt++;
# render the main rect
$l1->rect(
id => 'outer_rect' . $idcnt,
style => 'opacity:1.0;fill:#c30000;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-opacity:1;stroke-miterlimit:4',
width => "96",
height => '50',
x => $x + ($con->width / 2) - (96 / 2),
y => 4 + $level * 70,
);
$idcnt++;
# render the text
$l1->text(
style => 'font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:left;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Trebuchet MS;-inkscape-font-specification:Trebuchet MS',
x => $x + ($con->width / 2) - (100/2) + 5,
y => 4 + 15 + $level * 70,
id => 'title_'.$idcnt,
)->tspan(style => 'text-align:start;text-anchor:start')->cdata($con->name);
$idcnt++;
$y = $y + 50;
my @nodes = $con->nodes;
my $startx = $x + ($con->width / 2);
for my $node (@nodes) {
render_svg($node, $level + 1, $x);
my $mid = $x + ($node->width / 2);
$l1->path(
d => 'M ' . $startx . ',' . (4 + $level * 70 + 50) . ' ' . $mid . ',' . (4 + ($level+1) * 70),
id => 'path' . $idcnt,
style => 'fill:none;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1'
);
$x += $node->width + 20;
$idcnt++;
}
}
$svg->render > io('test.svg');

68
src/assignments.c Normal file
View File

@ -0,0 +1,68 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#include "all.h"
/*
* Checks the list of assignments for the given window and runs all matching
* ones (unless they have already been run for this specific window).
*
*/
void run_assignments(i3Window *window) {
DLOG("Checking assignments...\n");
/* Check if any assignments match */
Assignment *current;
TAILQ_FOREACH(current, &assignments, assignments) {
if (!match_matches_window(&(current->match), window))
continue;
bool skip = false;
for (int c = 0; c < window->nr_assignments; c++) {
if (window->ran_assignments[c] != current)
continue;
DLOG("This assignment already ran for the given window, not executing it again.\n");
skip = true;
break;
}
if (skip)
continue;
DLOG("matching assignment, would do:\n");
if (current->type == A_COMMAND) {
DLOG("execute command %s\n", current->dest.command);
char *full_command;
asprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command);
parse_cmd(full_command);
}
/* Store that we ran this assignment to not execute it again */
window->nr_assignments++;
window->ran_assignments = srealloc(window->ran_assignments, sizeof(Assignment*) * window->nr_assignments);
window->ran_assignments[window->nr_assignments-1] = current;
}
}
/*
* Returns the first matching assignment for the given window.
*
*/
Assignment *assignment_for(i3Window *window, int type) {
Assignment *assignment;
TAILQ_FOREACH(assignment, &assignments, assignments) {
if ((type != A_ANY && (assignment->type & type) == 0) ||
!match_matches_window(&(assignment->match), window))
continue;
DLOG("got a matching assignment (to %s)\n", assignment->dest.workspace);
return assignment;
}
return NULL;
}

View File

@ -1,17 +1,16 @@
/*
* vim:ts=4:sw=4:expandtab
*
*/
%option nounput
%option noinput
%option noyy_top_state
%option stack
%{
/*
* vim:ts=8:expandtab
*
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h> /* Defines uint32_t, required by cfgparse.tab.h */
#include "cfgparse.tab.h"
#include <stdint.h>
#include <xcb/xcb.h>
#include "data.h"
@ -19,89 +18,136 @@
#include "log.h"
#include "util.h"
#include "cfgparse.tab.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; \
context->first_column = yycolumn; \
context->last_column = yycolumn+yyleng-1; \
yycolumn += yyleng; \
}
/* macro to first eat whitespace, then expect a string */
#define WS_STRING do { \
yy_push_state(WANT_STRING); \
yy_push_state(EAT_WHITESPACE); \
} while (0)
%}
EOL (\r?\n)
EOL (\r?\n)
%s BIND_COND
%s WANT_STRING
%s WANT_QSTRING
%s BINDSYM_COND
%s BIND_AWS_COND
%s BINDSYM_AWS_COND
%s BIND_A2WS_COND
%s ASSIGN_COND
%s ASSIGN_TARGET_COND
%s COLOR_COND
%s OUTPUT_COND
%s OUTPUT_AWS_COND
%s FOR_WINDOW_COND
%s EAT_WHITESPACE
%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);
}
}
{
/* 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);
/* save whole line */
context->line_copy = sstrdup(yytext);
yyless(0);
yy_pop_state();
yy_set_bol(true);
yycolumn = 1;
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; }
<FOR_WINDOW_COND>"]" { yy_pop_state(); return ']'; }
<EAT_WHITESPACE>[ \t]* { yy_pop_state(); }
<WANT_QSTRING>\"[^\"]+\" {
yy_pop_state();
/* strip quotes */
char *copy = sstrdup(yytext+1);
copy[strlen(copy)-1] = '\0';
yylval.string = copy;
return STR;
}
<WANT_STRING>[^\n]+ { BEGIN(INITIAL); yylval.string = sstrdup(yytext); return STR; }
<OUTPUT_COND>[a-zA-Z0-9_-]+ { yylval.string = sstrdup(yytext); return OUTPUT; }
^[ \t]*#[^\n]* { return TOKCOMMENT; }
<COLOR_COND>[0-9a-fA-F]+ { yylval.string = strdup(yytext); return HEX; }
<COLOR_COND>[0-9a-fA-F]+ { yylval.string = sstrdup(yytext); return HEX; }
<ASSIGN_TARGET_COND>[ \t]*→[ \t]* { BEGIN(WANT_STRING); }
<ASSIGN_TARGET_COND>[ \t]+ { BEGIN(WANT_STRING); }
[0-9]+ { yylval.number = atoi(yytext); return NUMBER; }
mode { return TOKMODE; }
bind { BEGIN(BIND_COND); return TOKBIND; }
bindsym { BEGIN(BINDSYM_COND); return TOKBINDSYM; }
bind { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
bindcode { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
bindsym { yy_push_state(BINDSYM_COND); yy_push_state(EAT_WHITESPACE); return TOKBINDSYM; }
floating_modifier { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; }
workspace { BEGIN(INITIAL); return TOKWORKSPACE; }
output { BEGIN(OUTPUT_COND); return TOKOUTPUT; }
output { yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE); 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);
yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE);
return TOKOUTPUT;
}
terminal { BEGIN(BIND_AWS_COND); return TOKTERMINAL; }
font { BEGIN(BIND_AWS_COND); return TOKFONT; }
assign { BEGIN(ASSIGN_COND); return TOKASSIGN; }
terminal { WS_STRING; return TOKTERMINAL; }
font { WS_STRING; return TOKFONT; }
assign { yy_push_state(ASSIGN_TARGET_COND); yy_push_state(ASSIGN_COND); return TOKASSIGN; }
set[^\n]* { return TOKCOMMENT; }
ipc-socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; }
ipc_socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; }
new_container { return TOKNEWCONTAINER; }
ipc-socket { WS_STRING; return TOKIPCSOCKET; }
ipc_socket { WS_STRING; return TOKIPCSOCKET; }
restart_state { WS_STRING; return TOKRESTARTSTATE; }
default_orientation { return TOK_ORIENTATION; }
horizontal { return TOK_HORIZ; }
vertical { return TOK_VERT; }
auto { return TOK_AUTO; }
workspace_layout { return TOK_WORKSPACE_LAYOUT; }
new_window { return TOKNEWWINDOW; }
normal { return TOK_NORMAL; }
none { return TOK_NONE; }
1pixel { return TOK_1PIXEL; }
focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; }
force_focus_wrapping { return TOK_FORCE_FOCUS_WRAPPING; }
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; }
popup_during_fullscreen { return TOK_POPUP_DURING_FULLSCREEN; }
ignore { return TOK_IGNORE; }
leave_fullscreen { return TOK_LEAVE_FULLSCREEN; }
for_window {
/* Example: for_window [class="urxvt"] border none
*
* First, we wait for the ']' that finishes a match (FOR_WINDOW_COND)
* Then, we require a whitespace (EAT_WHITESPACE)
* And the rest of the line is parsed as a string
*/
yy_push_state(WANT_STRING);
yy_push_state(EAT_WHITESPACE);
yy_push_state(FOR_WINDOW_COND);
return TOK_FOR_WINDOW;
}
default { /* yylval.number = MODE_DEFAULT; */return TOK_DEFAULT; }
stacking { /* yylval.number = MODE_STACK; */return TOK_STACKING; }
stacked { return TOK_STACKING; }
tabbed { /* yylval.number = MODE_TABBED; */return TOK_TABBED; }
stack-limit { return TOKSTACKLIMIT; }
cols { yylval.number = STACK_LIMIT_COLS; return TOKSTACKLIMIT; }
rows { yylval.number = STACK_LIMIT_ROWS; return TOKSTACKLIMIT; }
exec { BEGIN(BIND_AWS_COND); return TOKEXEC; }
cols { /* yylval.number = STACK_LIMIT_COLS; */return TOKSTACKLIMIT; }
rows { /* yylval.number = STACK_LIMIT_ROWS; */return TOKSTACKLIMIT; }
exec { WS_STRING; return TOKEXEC; }
exec_always { WS_STRING; return TOKEXEC_ALWAYS; }
client.background { BEGIN(COLOR_COND); yylval.single_color = &config.client.background; return TOKSINGLECOLOR; }
client.focused { BEGIN(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; }
client.focused_inactive { BEGIN(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; }
@ -119,38 +165,42 @@ Mode_switch { yylval.number = BIND_MODE_SWITCH; return MODIF
control { return TOKCONTROL; }
ctrl { return TOKCONTROL; }
shift { return TOKSHIFT; }
→ { return TOKARROW; }
class { yy_push_state(WANT_QSTRING); return TOK_CLASS; }
id { yy_push_state(WANT_QSTRING); return TOK_ID; }
con_id { yy_push_state(WANT_QSTRING); return TOK_CON_ID; }
con_mark { yy_push_state(WANT_QSTRING); return TOK_MARK; }
title { yy_push_state(WANT_QSTRING); return TOK_TITLE; }
{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; }
<OUTPUT_COND>[ \t]+ { BEGIN(OUTPUT_AWS_COND); return WHITESPACE; }
<OUTPUT_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
[ \t]+ { return WHITESPACE; }
<BINDSYM_COND>[ \t]+ { BEGIN(WANT_STRING); }
<OUTPUT_COND>[ \t]+ { BEGIN(WANT_STRING); }
[ \t]+ { /* ignore whitespace */ ; }
\"[^\"]+\" {
/* if ASSIGN_COND then */
BEGIN(INITIAL);
if (yy_start_stack_ptr > 0)
yy_pop_state();
else BEGIN(INITIAL);
/* yylval will be the string, but without quotes */
char *copy = strdup(yytext+1);
char *copy = sstrdup(yytext+1);
copy[strlen(copy)-1] = '\0';
yylval.string = copy;
return QUOTEDSTRING;
}
<ASSIGN_COND>[^ \t]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR_NG; }
<BINDSYM_AWS_COND>[a-zA-Z0-9_]+ { yylval.string = strdup(yytext); return WORD; }
[a-zA-Z]+ { yylval.string = strdup(yytext); return WORD; }
<ASSIGN_COND>[^ \t\"]+ { BEGIN(ASSIGN_TARGET_COND); yylval.string = sstrdup(yytext); return STR_NG; }
<BINDSYM_COND>[a-zA-Z0-9_]+ { yylval.string = sstrdup(yytext); return WORD; }
[a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; }
. { return (int)yytext[0]; }
<<EOF>> {
while (yy_start_stack_ptr > 0)
yy_pop_state();
yyterminate();
while (yy_start_stack_ptr > 0)
yy_pop_state();
yyterminate();
}
%%

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
/*
* vim:ts=8:expandtab
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2010 Michael Stapelberg and contributors
* © 2009-2011 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -11,169 +11,66 @@
* because they are quite large.
*
*/
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>
#include <math.h>
#include <xcb/xcb.h>
#include <xcb/xcb_atom.h>
#include <xcb/xcb_icccm.h>
#include <X11/XKBlib.h>
#include "i3.h"
#include "queue.h"
#include "table.h"
#include "config.h"
#include "util.h"
#include "xcb.h"
#include "client.h"
#include "workspace.h"
#include "commands.h"
#include "floating.h"
#include "resize.h"
#include "log.h"
#include "randr.h"
#include "all.h"
static struct Stack_Window *get_stack_window(xcb_window_t window_id) {
struct Stack_Window *current;
SLIST_FOREACH(current, &stack_wins, stack_windows) {
if (current->window != window_id)
continue;
return current;
}
return NULL;
}
typedef enum { CLICK_BORDER = 0, CLICK_DECORATION = 1, CLICK_INSIDE = 2 } click_destination_t;
/*
* Checks if the button press was on a stack window, handles focus setting and returns true
* if so, or false otherwise.
* Finds the correct pair of first/second cons between the resize will take
* place according to the passed border position (top, left, right, bottom),
* then calls resize_graphical_handler().
*
*/
static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event_t *event) {
struct Stack_Window *stack_win;
static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event) {
DLOG("border = %d\n", border);
char way = (border == BORDER_TOP || border == BORDER_LEFT ? 'p' : 'n');
orientation_t orientation = (border == BORDER_TOP || border == BORDER_BOTTOM ? VERT : HORIZ);
/* If we find a corresponding stack window, we can handle the event */
if ((stack_win = get_stack_window(event->event)) == NULL)
return false;
/* look for a parent container with the right orientation */
Con *first = NULL, *second = NULL;
Con *resize_con = con;
while (resize_con->type != CT_WORKSPACE &&
resize_con->type != CT_FLOATING_CON &&
resize_con->parent->orientation != orientation)
resize_con = resize_con->parent;
/* A stack window was clicked, we check if it was button4 or button5
which are scroll up / scroll down. */
if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) {
direction_t direction = (event->detail == XCB_BUTTON_INDEX_4 ? D_UP : D_DOWN);
focus_window_in_container(conn, CUR_CELL, direction);
return true;
if (resize_con->type != CT_WORKSPACE &&
resize_con->type != CT_FLOATING_CON &&
resize_con->parent->orientation == orientation) {
first = resize_con;
second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes);
if (second == TAILQ_END(&(first->nodes_head))) {
second = NULL;
}
/* It was no scrolling, so we calculate the destination client by
dividing the Y position of the event through the height of a window
decoration and then set the focus to this client. */
i3Font *font = load_font(conn, config.font);
int decoration_height = (font->height + 2 + 2);
int destination = (event->event_y / decoration_height),
c = 0,
num_clients = 0;
Client *client;
Container *container = stack_win->container;
CIRCLEQ_FOREACH(client, &(container->clients), clients)
num_clients++;
/* If we dont have any clients in this container, we cannot do
* anything useful anyways. */
if (num_clients == 0)
return true;
if (container->mode == MODE_TABBED)
destination = (event->event_x / (container->width / num_clients));
else if (container->mode == MODE_STACK &&
container->stack_limit != STACK_LIMIT_NONE) {
if (container->stack_limit == STACK_LIMIT_COLS) {
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);
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);
DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
destination = (container->stack_limit_value * clicked_column) + clicked_row;
}
}
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);
return true;
}
return true;
}
/*
* Checks if the button press was on a bar, switches to the workspace and returns true
* if so, or false otherwise.
*
*/
static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *event) {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs) {
if (output->bar != event->event)
continue;
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->output == output) {
workspace_show(conn, ws->num + 1);
return true;
}
}
} else {
while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) {
if (ws->output == output) {
workspace_show(conn, ws->num + 1);
return true;
}
}
}
return true;
}
int drawn = 0;
/* 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->output != output)
continue;
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)) {
workspace_show(conn, ws->num + 1);
return true;
}
drawn += ws->text_width + 5 + 5 + 2;
}
return true;
else if (way == 'p') {
Con *tmp = first;
first = second;
second = tmp;
}
}
if (first == NULL || second == NULL) {
DLOG("Resize not possible\n");
return false;
}
else {
assert(first != second);
assert(first->parent == second->parent);
resize_graphical_handler(first, second, orientation, event);
}
DLOG("After resize handler, rendering\n");
tree_render();
return true;
}
/*
@ -184,231 +81,202 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e
* to the client).
*
*/
static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client,
xcb_button_press_event_t *event) {
/* Only the right mouse button is interesting for us at the moment */
if (event->detail != 3)
return false;
static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *event) {
/* The client is in tiling layout. We can still initiate a resize with the
* right mouse button, by chosing the border which is the most near one to
* the position of the mouse pointer */
int to_right = con->rect.width - event->event_x,
to_left = event->event_x,
to_top = event->event_y,
to_bottom = con->rect.height - event->event_y;
/* The client is in tiling layout. We can still
* initiate a resize with the right mouse button,
* by chosing the border which is the most near one
* to the position of the mouse pointer */
int to_right = client->rect.width - event->event_x,
to_left = event->event_x,
to_top = event->event_y,
to_bottom = client->rect.height - event->event_y;
resize_orientation_t orientation = O_VERTICAL;
Container *con = client->container;
Workspace *ws = con->workspace;
int first = 0, second = 0;
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);
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 &&
to_right < to_top &&
to_right < to_bottom)
return tiling_resize_for_border(con, BORDER_RIGHT, event);
if (to_right < to_left &&
to_right < to_top &&
to_right < to_bottom) {
/* …right border */
first = con->col + (con->colspan - 1);
DLOG("column %d\n", first);
if (to_left < to_right &&
to_left < to_top &&
to_left < to_bottom)
return tiling_resize_for_border(con, BORDER_LEFT, event);
if (!cell_exists(ws, first, con->row) ||
(first == (ws->cols-1)))
return false;
if (to_top < to_right &&
to_top < to_left &&
to_top < to_bottom)
return tiling_resize_for_border(con, BORDER_TOP, event);
second = first + 1;
} else if (to_left < to_right &&
to_left < to_top &&
to_left < to_bottom) {
/* …left border */
if (con->col == 0)
return false;
if (to_bottom < to_right &&
to_bottom < to_left &&
to_bottom < to_top)
return tiling_resize_for_border(con, BORDER_BOTTOM, event);
first = con->col - 1;
second = con->col;
} else if (to_top < to_right &&
to_top < to_left &&
to_top < to_bottom) {
/* This was a press on the top border */
if (con->row == 0)
return false;
first = con->row - 1;
second = con->row;
orientation = O_HORIZONTAL;
} else if (to_bottom < to_right &&
to_bottom < to_left &&
to_bottom < to_top) {
/* …bottom border */
first = con->row + (con->rowspan - 1);
if (!cell_exists(ws, con->col, first) ||
(first == (ws->rows-1)))
return false;
second = first + 1;
orientation = O_HORIZONTAL;
}
return resize_graphical_handler(conn, ws, first, second, orientation, event);
return false;
}
int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) {
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;
if (client == NULL) {
client = table_get(&by_parent, event->event);
border_click = true;
}
/* See if this was a click with the configured modifier. If so, we need
* 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) == config.floating_modifier) {
if (client == NULL) {
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) {
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)) {
DLOG("button %d pressed\n", event->detail);
if (event->detail == 1) {
DLOG("left mouse button, dragging\n");
floating_drag_window(conn, client, event);
} else if (event->detail == 3) {
bool proportional = (event->state & BIND_SHIFT);
DLOG("right mouse button\n");
floating_resize_window(conn, client, proportional, event);
}
return 1;
}
/*
* Finds out which border was clicked on and calls tiling_resize_for_border().
*
*/
static bool tiling_resize(Con *con, xcb_button_press_event_t *event, click_destination_t dest) {
/* check if this was a click on the window border (and on which one) */
Rect bsr = con_border_style_rect(con);
DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x\n",
event->event_x, event->event_y, con, event->event);
DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width);
if (dest == CLICK_DECORATION)
return tiling_resize_for_border(con, BORDER_TOP, event);
if (!floating_mod_on_tiled_client(conn, client, event)) {
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);
}
if (event->event_x >= 0 && event->event_x <= bsr.x &&
event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height)
return tiling_resize_for_border(con, BORDER_LEFT, event);
return 1;
}
if (event->event_x >= (con->window_rect.x + con->window_rect.width) &&
event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height)
return tiling_resize_for_border(con, BORDER_RIGHT, event);
if (client == NULL) {
/* The client was neither on a clients titlebar nor on a client itself, maybe on a stack_window? */
if (button_press_stackwin(conn, event))
return 1;
if (event->event_y >= (con->window_rect.y + con->window_rect.height))
return tiling_resize_for_border(con, BORDER_BOTTOM, event);
/* Or on a bar? */
if (button_press_bar(conn, event))
return 1;
DLOG("Could not handle this button press\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);
return 1;
}
/* Set focus in any case */
set_focus(conn, client, false);
/* Lets see if this was on the borders (= resize). If not, were done */
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) {
DLOG("dock. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);
return 1;
}
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
* reality is a border_click. Check for the position and fix state. */
if (border_click &&
event->event_x >= client->child_rect.x &&
event->event_x <= (client->child_rect.x + client->child_rect.width) &&
event->event_y >= client->child_rect.y &&
event->event_y <= (client->child_rect.y + client->child_rect.height)) {
DLOG("Fixing border_click = false because of click in child\n");
border_click = false;
}
if (!border_click) {
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))
xcb_raise_window(conn, client->frame);
xcb_flush(conn);
return 1;
}
/* 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)) {
DLOG("click on titlebar\n");
/* Floating clients can be dragged by grabbing their titlebar */
if (client_is_floating(client)) {
/* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */
xcb_raise_window(conn, client->frame);
xcb_flush(conn);
floating_drag_window(conn, client, event);
}
return 1;
}
if (client_is_floating(client))
return floating_border_click(conn, client, event);
Workspace *ws = con->workspace;
if (event->event_y < 2) {
/* This was a press on the top border */
if (con->row == 0)
return 1;
first = con->row - 1;
second = con->row;
orientation = O_HORIZONTAL;
} else if (event->event_y >= (client->rect.height - 2)) {
/* …bottom border */
first = con->row + (con->rowspan - 1);
if (!cell_exists(ws, con->col, first) ||
(first == (ws->rows-1)))
return 1;
second = first + 1;
orientation = O_HORIZONTAL;
} else if (event->event_x <= 2) {
/* …left border */
if (con->col == 0)
return 1;
first = con->col - 1;
second = con->col;
} else if (event->event_x > 2) {
/* …right border */
first = con->col + (con->colspan - 1);
DLOG("column %d\n", first);
if (!cell_exists(ws, first, con->row) ||
(first == (ws->cols-1)))
return 1;
second = first + 1;
}
return resize_graphical_handler(conn, ws, first, second, orientation, event);
return false;
}
/*
* Being called by handle_button_press, this function calls the appropriate
* functions for resizing/dragging.
*
*/
static int route_click(Con *con, xcb_button_press_event_t *event, bool mod_pressed, click_destination_t dest) {
DLOG("--> click properties: mod = %d, destination = %d\n", mod_pressed, dest);
DLOG("--> OUTCOME = %p\n", con);
DLOG("type = %d, name = %s\n", con->type, con->name);
/* dont handle dockarea cons, they must not be focused */
if (con->parent->type == CT_DOCKAREA)
goto done;
/* get the floating con */
Con *floatingcon = con_inside_floating(con);
const bool proportional = (event->state & BIND_SHIFT);
const bool in_stacked = (con->parent->layout == L_STACKED || con->parent->layout == L_TABBED);
/* 1: see if the user scrolled on the decoration of a stacked/tabbed con */
if (in_stacked &&
dest == CLICK_DECORATION &&
(event->detail == XCB_BUTTON_INDEX_4 ||
event->detail == XCB_BUTTON_INDEX_5)) {
DLOG("Scrolling on a window decoration\n");
orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ);
if (event->detail == XCB_BUTTON_INDEX_4)
tree_next('p', orientation);
else tree_next('n', orientation);
goto done;
}
/* 2: focus this con */
con_focus(con);
/* 3: for floating containers, we also want to raise them on click */
if (floatingcon != NULL) {
floating_raise_con(floatingcon);
/* 4: floating_modifier plus left mouse button drags */
if (mod_pressed && event->detail == 1) {
floating_drag_window(floatingcon, event);
return 1;
}
/* 5: resize (floating) if this was a click on the left/right/bottom
* border. also try resizing (tiling) if it was a click on the top
* border, but continue if that does not work */
if (mod_pressed && event->detail == 3) {
DLOG("floating resize due to floatingmodifier\n");
floating_resize_window(floatingcon, proportional, event);
return 1;
}
if (!in_stacked && dest == CLICK_DECORATION) {
/* try tiling resize, but continue if it doesnt work */
DLOG("tiling resize with fallback\n");
if (tiling_resize(con, event, dest))
goto done;
}
if (dest == CLICK_BORDER) {
DLOG("floating resize due to border click\n");
floating_resize_window(floatingcon, proportional, event);
return 1;
}
/* 6: dragging, if this was a click on a decoration (which did not lead
* to a resize) */
if (!in_stacked && dest == CLICK_DECORATION) {
floating_drag_window(floatingcon, event);
return 1;
}
goto done;
}
if (in_stacked) {
/* for stacked/tabbed cons, the resizing applies to the parent
* container */
con = con->parent;
}
/* 7: floating modifier pressed, initiate a resize */
if (mod_pressed && event->detail == 3) {
if (floating_mod_on_tiled_client(con, event))
return 1;
}
/* 8: otherwise, check for border/decoration clicks and resize */
else if (dest == CLICK_BORDER || dest == CLICK_DECORATION) {
DLOG("Trying to resize (tiling)\n");
tiling_resize(con, event, dest);
}
done:
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);
tree_render();
return 0;
}
/*
* The button press X callback. This function determines whether the floating
* modifier is pressed and where the user clicked (decoration, border, inside
* the window).
*
* Then, route_click is called on the appropriate con.
*
*/
int handle_button_press(xcb_button_press_event_t *event) {
Con *con;
DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event);
const uint32_t mod = config.floating_modifier;
bool mod_pressed = (mod != 0 && (event->state & mod) == mod);
DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail);
if ((con = con_by_window_id(event->event)))
return route_click(con, event, mod_pressed, CLICK_INSIDE);
if (!(con = con_by_frame_id(event->event))) {
ELOG("Clicked into unknown window?!\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);
return 0;
}
/* Check if the click was on the decoration of a child */
Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
if (!rect_contains(child->deco_rect, event->event_x, event->event_y))
continue;
return route_click(child, event, mod_pressed, CLICK_DECORATION);
}
return route_click(con, event, mod_pressed, CLICK_BORDER);
}

View File

@ -1,484 +0,0 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* client.c: holds all client-specific functions
*
*/
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <limits.h>
#include <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
#include "data.h"
#include "i3.h"
#include "xcb.h"
#include "util.h"
#include "queue.h"
#include "layout.h"
#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
* one or because it was unmapped
*
*/
void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container, bool remove_from_focusstack) {
CIRCLEQ_REMOVE(&(container->clients), client, clients);
if (remove_from_focusstack)
SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients);
/* If the container will be empty now and is in stacking mode, we need to
unmap the stack_win */
if (CIRCLEQ_EMPTY(&(container->clients)) &&
(container->mode == MODE_STACK ||
container->mode == MODE_TABBED)) {
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);
xcb_flush(conn);
}
}
/*
* Warps the pointer into the given client (in the middle of it, to be specific), therefore
* selecting it
*
*/
void client_warp_pointer_into(xcb_connection_t *conn, Client *client) {
int mid_x = client->rect.width / 2,
mid_y = client->rect.height / 2;
xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
}
/*
* Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
*
*/
static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
xcb_get_property_cookie_t cookie;
xcb_get_wm_protocols_reply_t protocols;
bool result = false;
cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
return false;
/* Check if the clients protocols have the requested atom set */
for (uint32_t i = 0; i < protocols.atoms_len; i++)
if (protocols.atoms[i] == atom)
result = true;
xcb_get_wm_protocols_reply_wipe(&protocols);
return result;
}
/*
* Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
*
*/
void client_kill(xcb_connection_t *conn, Client *window) {
/* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */
if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) {
LOG("Killing window the hard way\n");
xcb_kill_client(conn, window->child);
return;
}
xcb_client_message_event_t ev;
memset(&ev, 0, sizeof(xcb_client_message_event_t));
ev.response_type = XCB_CLIENT_MESSAGE;
ev.window = window->child;
ev.type = atoms[WM_PROTOCOLS];
ev.format = 32;
ev.data.data32[0] = atoms[WM_DELETE_WINDOW];
ev.data.data32[1] = XCB_CURRENT_TIME;
LOG("Sending WM_DELETE to the client\n");
xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
xcb_flush(conn);
}
/*
* Checks if the given window class and title match the given client
* Window title is passed as "normal" string and as UCS-2 converted string for
* matching _NET_WM_NAME capable clients as well as those using legacy hints.
*
*/
bool client_matches_class_name(Client *client, char *to_class, char *to_title,
char *to_title_ucs, int to_title_ucs_len) {
/* Check if the given class is part of the window class */
if ((client->window_class_instance == NULL ||
strcasestr(client->window_class_instance, to_class) == NULL) &&
(client->window_class_class == NULL ||
strcasestr(client->window_class_class, to_class) == NULL))
return false;
/* If no title was given, were done */
if (to_title == NULL)
return true;
if (client->name_len > -1) {
/* UCS-2 converted window titles */
if (client->name == NULL || memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL)
return false;
} else {
/* Legacy hints */
if (client->name == NULL || strcasestr(client->name, to_title) == NULL)
return false;
}
return true;
}
/*
* Enters fullscreen mode for the given client. This is called by toggle_fullscreen
* and when moving a fullscreen client to another screen.
*
*/
void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global) {
Workspace *workspace;
Output *output;
Rect r;
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;
/* We just entered fullscreen mode, lets configure the window */
DLOG("child itself will be at %dx%d with size %dx%d\n",
r.x, r.y, r.width, r.height);
xcb_set_window_rect(conn, client->frame, r);
/* Childs coordinates are relative to the parent (=frame) */
r.x = 0;
r.y = 0;
xcb_set_window_rect(conn, client->child, r);
/* Raise the window */
uint32_t values[] = { XCB_STACK_MODE_ABOVE };
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
/* Update _NET_WM_STATE */
values[0] = atoms[_NET_WM_STATE_FULLSCREEN];
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[_NET_WM_STATE], ATOM, 32, 1, values);
fake_configure_notify(conn, r, client->child);
xcb_flush(conn);
}
/*
* Leaves fullscreen mode for the current client. This is called by toggle_fullscreen.
*
*/
void client_leave_fullscreen(xcb_connection_t *conn, Client *client) {
LOG("leaving fullscreen mode\n");
client->fullscreen = false;
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces)
if (ws->fullscreen_client == client)
ws->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);
} 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);
}
/* Update _NET_WM_STATE */
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[_NET_WM_STATE], ATOM, 32, 0, NULL);
xcb_flush(conn);
}
/*
* Toggles fullscreen mode for the given client. It updates the data structures and
* reconfigures (= resizes/moves) the client and its frame to the full size of the
* screen. When leaving fullscreen, re-rendering the layout is forced.
*
*/
void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) {
/* dock clients cannot enter fullscreen mode */
assert(!client->dock);
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, true);
} else {
client_leave_fullscreen(conn, client);
}
}
/*
* Sets the position of the given client in the X stack to the highest (tiling layer is always
* on the same position, so this doesnt matter) below the first floating client, so that
* floating windows are always on top.
*
*/
void client_set_below_floating(xcb_connection_t *conn, Client *client) {
/* Ensure that it is below all floating clients */
Workspace *ws = client->workspace;
Client *first_floating = TAILQ_FIRST(&(ws->floating_clients));
if (first_floating == TAILQ_END(&(ws->floating_clients)))
return;
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;
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);
}
/*
* Returns true if the client is floating. Makes the code more beatiful, as floating
* is not simply a boolean, but also saves whether the user selected the current state
* or whether it was automatically set.
*
*/
bool client_is_floating(Client *client) {
return (client->floating >= FLOATING_AUTO_ON);
}
/*
* Change the border type for the given client to normal (n), 1px border (p) or
* completely borderless (b) without actually re-rendering the layout. Useful
* for calling it when initializing a new client.
*
*/
bool client_init_border(xcb_connection_t *conn, Client *client, char border_type) {
switch (border_type) {
case 'n':
LOG("Changing to normal border\n");
client->titlebar_position = TITLEBAR_TOP;
client->borderless = false;
return true;
case 'p':
LOG("Changing to 1px border\n");
client->titlebar_position = TITLEBAR_OFF;
client->borderless = false;
return true;
case 'b':
LOG("Changing to borderless\n");
client->titlebar_position = TITLEBAR_OFF;
client->borderless = true;
return true;
default:
LOG("Unknown border mode\n");
return false;
}
}
/*
* Change the border type for the given client to normal (n), 1px border (p) or
* completely borderless (b).
*
*/
void client_change_border(xcb_connection_t *conn, Client *client, char border_type) {
if (!client_init_border(conn, client, border_type))
return;
/* Ensure that the childs position inside our window gets updated */
client->force_reconfigure = true;
/* For clients inside a container, we can simply render the container */
if (client->container != NULL)
render_container(conn, client->container);
else {
/* If the client is floating, directly push its size */
if (client_is_floating(client))
resize_client(conn, client);
/* Otherwise, it may be a dock client, thus render the whole layout */
else render_layout(conn);
}
redecorate_window(conn, client);
}
/*
* Unmap the client, correctly setting any state which is needed.
*
*/
void client_unmap(xcb_connection_t *conn, Client *client) {
/* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */
long data[] = { XCB_WM_STATE_WITHDRAWN, XCB_NONE };
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
xcb_unmap_window(conn, client->frame);
}
/*
* Map the client, correctly restoring any state needed.
*
*/
void client_map(xcb_connection_t *conn, Client *client) {
/* Set WM_STATE_NORMAL because GTK applications dont want to drag & drop if we dont.
* Also, xprop(1) needs that to work. */
long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
xcb_map_window(conn, client->frame);
}
/*
* Set the given mark for this client. Used for jumping to the client
* afterwards (like m<mark> and '<mark> in vim).
*
*/
void client_mark(xcb_connection_t *conn, Client *client, const char *mark) {
if (client->mark != NULL)
free(client->mark);
client->mark = sstrdup(mark);
/* Make sure no other client has this mark set */
Client *current;
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces)
SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) {
if (current == client ||
current->mark == NULL ||
strcmp(current->mark, mark) != 0)
continue;
free(current->mark);
current->mark = NULL;
/* We can break here since there can only be one other
* client with this 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;
}

171
src/cmdparse.l Normal file
View File

@ -0,0 +1,171 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
* cmdparse.l: the lexer for commands you send to i3 (or bind on keys)
*
*/
%option nounput
%option noinput
%option noyy_top_state
%option stack
%{
#include <stdio.h>
#include <string.h>
#include "cmdparse.tab.h"
#include "config.h"
#include "util.h"
int cmdyycolumn = 1;
#define YY_DECL int yylex (struct context *context)
#define YY_USER_ACTION { \
context->first_column = cmdyycolumn; \
context->last_column = cmdyycolumn+yyleng-1; \
cmdyycolumn += yyleng; \
}
/* macro to first eat whitespace, then expect a string */
#define WS_STRING do { \
yy_push_state(WANT_STRING); \
yy_push_state(EAT_WHITESPACE); \
} while (0)
%}
EOL (\r?\n)
/* handle everything up to \n as a string */
%s WANT_STRING
/* eat a whitespace, then go to the next state on the stack */
%s EAT_WHITESPACE
/* handle a quoted string or everything up to the next whitespace */
%s WANT_QSTRING
%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 = sstrdup(yytext);
yyless(0);
yy_pop_state();
yy_set_bol(true);
cmdyycolumn = 1;
}
/* the next/prev tokens are here to recognize them *before* handling
* strings ('workspace' command) */
next { return TOK_NEXT; }
prev { return TOK_PREV; }
<WANT_STRING>\"[^\"]+\" {
BEGIN(INITIAL);
/* strip quotes */
char *copy = sstrdup(yytext+1);
copy[strlen(copy)-1] = '\0';
cmdyylval.string = copy;
return STR;
}
<WANT_QSTRING>\"[^\"]+\" {
BEGIN(INITIAL);
/* strip quotes */
char *copy = sstrdup(yytext+1);
copy[strlen(copy)-1] = '\0';
cmdyylval.string = copy;
return STR;
}
<WANT_STRING>[^;\n]+ { BEGIN(INITIAL); cmdyylval.string = sstrdup(yytext); return STR; }
<EAT_WHITESPACE>[;\n] { BEGIN(INITIAL); return ';'; }
<EAT_WHITESPACE>[ \t]* { yy_pop_state(); }
[ \t]* { /* ignore whitespace */ ; }
exec { WS_STRING; return TOK_EXEC; }
exit { return TOK_EXIT; }
reload { return TOK_RELOAD; }
restart { return TOK_RESTART; }
kill { return TOK_KILL; }
window { return TOK_WINDOW; }
client { return TOK_CLIENT; }
fullscreen { return TOK_FULLSCREEN; }
global { return TOK_GLOBAL; }
layout { return TOK_LAYOUT; }
default { return TOK_DEFAULT; }
stacked { return TOK_STACKED; }
stacking { return TOK_STACKED; }
tabbed { return TOK_TABBED; }
border { return TOK_BORDER; }
normal { return TOK_NORMAL; }
none { return TOK_NONE; }
1pixel { return TOK_1PIXEL; }
mode { BEGIN(WANT_QSTRING); return TOK_MODE; }
tiling { return TOK_TILING; }
floating { return TOK_FLOATING; }
toggle { return TOK_TOGGLE; }
mode_toggle { return TOK_MODE_TOGGLE; }
workspace { WS_STRING; return TOK_WORKSPACE; }
focus { return TOK_FOCUS; }
move { return TOK_MOVE; }
open { return TOK_OPEN; }
split { return TOK_SPLIT; }
horizontal { return TOK_HORIZONTAL; }
vertical { return TOK_VERTICAL; }
up { return TOK_UP; }
down { return TOK_DOWN; }
left { return TOK_LEFT; }
right { return TOK_RIGHT; }
parent { return TOK_PARENT; }
child { return TOK_CHILD; }
resize { return TOK_RESIZE; }
shrink { return TOK_SHRINK; }
grow { return TOK_GROW; }
px { return TOK_PX; }
or { return TOK_OR; }
ppt { return TOK_PPT; }
nop { WS_STRING; return TOK_NOP; }
append_layout { WS_STRING; return TOK_APPEND_LAYOUT; }
mark { WS_STRING; return TOK_MARK; }
enable { return TOK_ENABLE; }
true { return TOK_ENABLE; }
yes { return TOK_ENABLE; }
disable { return TOK_DISABLE; }
false { return TOK_DISABLE; }
no { return TOK_DISABLE; }
class { BEGIN(WANT_QSTRING); return TOK_CLASS; }
id { BEGIN(WANT_QSTRING); return TOK_ID; }
con_id { BEGIN(WANT_QSTRING); return TOK_CON_ID; }
con_mark { BEGIN(WANT_QSTRING); return TOK_MARK; }
title { BEGIN(WANT_QSTRING); return TOK_TITLE; }
[0-9]+ { cmdyylval.number = atoi(yytext); return NUMBER; }
. { return (int)yytext[0]; }
<<EOF>> {
while (yy_start_stack_ptr > 0)
yy_pop_state();
yyterminate();
}
%%

859
src/cmdparse.y Normal file
View File

@ -0,0 +1,859 @@
%{
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
* cmdparse.y: the parser for commands you send to i3 (or bind on keys)
*
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include "all.h"
/** When the command did not include match criteria (!), we use the currently
* focused command. Do not confuse this case with a command which included
* criteria but which did not match any windows. This macro has to be called in
* every command.
*/
#define HANDLE_EMPTY_MATCH do { \
if (match_is_empty(&current_match)) { \
owindow *ow = smalloc(sizeof(owindow)); \
ow->con = focused; \
TAILQ_INIT(&owindows); \
TAILQ_INSERT_TAIL(&owindows, ow, owindows); \
} \
} while (0)
typedef struct yy_buffer_state *YY_BUFFER_STATE;
extern int cmdyylex(struct context *context);
extern int cmdyyparse(void);
extern int cmdyylex_destroy(void);
extern FILE *cmdyyin;
YY_BUFFER_STATE cmdyy_scan_string(const char *);
static struct context *context;
static Match current_match;
/*
* Helper data structure for an operation window (window on which the operation
* will be performed). Used to build the TAILQ owindows.
*
*/
typedef struct owindow {
Con *con;
TAILQ_ENTRY(owindow) owindows;
} owindow;
static TAILQ_HEAD(owindows_head, owindow) owindows;
/* Holds the JSON which will be returned via IPC or NULL for the default return
* message */
static char *json_output;
/* 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 cmdyydebug = 1;
void cmdyyerror(const char *error_message) {
ELOG("\n");
ELOG("CMD: %s\n", error_message);
ELOG("CMD: in command:\n");
ELOG("CMD: %s\n", context->line_copy);
ELOG("CMD: ");
for (int c = 1; c <= context->last_column; c++)
if (c >= context->first_column)
printf("^");
else printf(" ");
printf("\n");
ELOG("\n");
context->compact_error = sstrdup(error_message);
}
int cmdyywrap() {
return 1;
}
char *parse_cmd(const char *new) {
LOG("COMMAND: *%s*\n", new);
cmdyy_scan_string(new);
match_init(&current_match);
context = scalloc(sizeof(struct context));
context->filename = "cmd";
FREE(json_output);
if (cmdyyparse() != 0) {
fprintf(stderr, "Could not parse command\n");
asprintf(&json_output, "{\"success\":false, \"error\":\"%s at position %d\"}",
context->compact_error, context->first_column);
FREE(context->line_copy);
FREE(context->compact_error);
free(context);
return json_output;
}
printf("done, json output = %s\n", json_output);
cmdyylex_destroy();
FREE(context->line_copy);
FREE(context->compact_error);
free(context);
return json_output;
}
%}
%error-verbose
%lex-param { struct context *context }
%union {
char *string;
char chr;
int number;
}
%token TOK_EXEC "exec"
%token TOK_EXIT "exit"
%token TOK_RELOAD "reload"
%token TOK_RESTART "restart"
%token TOK_KILL "kill"
%token TOK_WINDOW "window"
%token TOK_CLIENT "client"
%token TOK_FULLSCREEN "fullscreen"
%token TOK_GLOBAL "global"
%token TOK_LAYOUT "layout"
%token TOK_DEFAULT "default"
%token TOK_STACKED "stacked"
%token TOK_TABBED "tabbed"
%token TOK_BORDER "border"
%token TOK_NORMAL "normal"
%token TOK_NONE "none"
%token TOK_1PIXEL "1pixel"
%token TOK_MODE "mode"
%token TOK_TILING "tiling"
%token TOK_FLOATING "floating"
%token TOK_MODE_TOGGLE "mode_toggle"
%token TOK_ENABLE "enable"
%token TOK_DISABLE "disable"
%token TOK_WORKSPACE "workspace"
%token TOK_TOGGLE "toggle"
%token TOK_FOCUS "focus"
%token TOK_MOVE "move"
%token TOK_OPEN "open"
%token TOK_NEXT "next"
%token TOK_PREV "prev"
%token TOK_SPLIT "split"
%token TOK_HORIZONTAL "horizontal"
%token TOK_VERTICAL "vertical"
%token TOK_UP "up"
%token TOK_DOWN "down"
%token TOK_LEFT "left"
%token TOK_RIGHT "right"
%token TOK_PARENT "parent"
%token TOK_CHILD "child"
%token TOK_APPEND_LAYOUT "append_layout"
%token TOK_MARK "mark"
%token TOK_RESIZE "resize"
%token TOK_GROW "grow"
%token TOK_SHRINK "shrink"
%token TOK_PX "px"
%token TOK_OR "or"
%token TOK_PPT "ppt"
%token TOK_NOP "nop"
%token TOK_CLASS "class"
%token TOK_ID "id"
%token TOK_CON_ID "con_id"
%token TOK_TITLE "title"
%token <string> STR "<string>"
%token <number> NUMBER "<number>"
%type <number> direction
%type <number> split_direction
%type <number> fullscreen_mode
%type <number> level
%type <number> window_mode
%type <number> boolean
%type <number> border_style
%type <number> layout_mode
%type <number> resize_px
%type <number> resize_way
%type <number> resize_tiling
%type <number> optional_kill_mode
%%
commands:
commands ';' command
| command
{
owindow *current;
printf("single command completely parsed, dropping state...\n");
while (!TAILQ_EMPTY(&owindows)) {
current = TAILQ_FIRST(&owindows);
TAILQ_REMOVE(&owindows, current, owindows);
free(current);
}
match_init(&current_match);
}
;
command:
match operations
;
match:
| matchstart criteria matchend
{
printf("match parsed\n");
}
;
matchstart:
'['
{
printf("start\n");
match_init(&current_match);
TAILQ_INIT(&owindows);
/* copy all_cons */
Con *con;
TAILQ_FOREACH(con, &all_cons, all_cons) {
owindow *ow = smalloc(sizeof(owindow));
ow->con = con;
TAILQ_INSERT_TAIL(&owindows, ow, owindows);
}
}
;
matchend:
']'
{
owindow *next, *current;
printf("match specification finished, matching...\n");
/* copy the old list head to iterate through it and start with a fresh
* list which will contain only matching windows */
struct owindows_head old = owindows;
TAILQ_INIT(&owindows);
for (next = TAILQ_FIRST(&old); next != TAILQ_END(&old);) {
/* make a copy of the next pointer and advance the pointer to the
* next element as we are going to invalidate the elements
* next/prev pointers by calling TAILQ_INSERT_TAIL later */
current = next;
next = TAILQ_NEXT(next, owindows);
printf("checking if con %p / %s matches\n", current->con, current->con->name);
if (current_match.con_id != NULL) {
if (current_match.con_id == current->con) {
printf("matches container!\n");
TAILQ_INSERT_TAIL(&owindows, current, owindows);
}
} else if (current_match.mark != NULL && current->con->mark != NULL &&
strcasecmp(current_match.mark, current->con->mark) == 0) {
printf("match by mark\n");
TAILQ_INSERT_TAIL(&owindows, current, owindows);
} else {
if (current->con->window == NULL)
continue;
if (match_matches_window(&current_match, current->con->window)) {
printf("matches window!\n");
TAILQ_INSERT_TAIL(&owindows, current, owindows);
} else {
printf("doesnt match\n");
free(current);
}
}
}
TAILQ_FOREACH(current, &owindows, owindows) {
printf("matching: %p / %s\n", current->con, current->con->name);
}
}
;
criteria:
criteria criterion
| criterion
;
criterion:
TOK_CLASS '=' STR
{
printf("criteria: class = %s\n", $3);
current_match.class = $3;
}
| TOK_CON_ID '=' STR
{
printf("criteria: id = %s\n", $3);
char *end;
long parsed = strtol($3, &end, 10);
if (parsed == LONG_MIN ||
parsed == LONG_MAX ||
parsed < 0 ||
(end && *end != '\0')) {
ELOG("Could not parse con id \"%s\"\n", $3);
} else {
current_match.con_id = (Con*)parsed;
printf("id as int = %p\n", current_match.con_id);
}
}
| TOK_ID '=' STR
{
printf("criteria: window id = %s\n", $3);
char *end;
long parsed = strtol($3, &end, 10);
if (parsed == LONG_MIN ||
parsed == LONG_MAX ||
parsed < 0 ||
(end && *end != '\0')) {
ELOG("Could not parse window id \"%s\"\n", $3);
} else {
current_match.id = parsed;
printf("window id as int = %d\n", current_match.id);
}
}
| TOK_MARK '=' STR
{
printf("criteria: mark = %s\n", $3);
current_match.mark = $3;
}
| TOK_TITLE '=' STR
{
printf("criteria: title = %s\n", $3);
current_match.title = $3;
}
;
operations:
operation
| operations ',' operation
;
operation:
exec
| exit
| restart
| reload
| border
| layout
| append_layout
| move
| workspace
| focus
| kill
| open
| fullscreen
| split
| floating
| mark
| resize
| nop
| mode
;
exec:
TOK_EXEC STR
{
printf("should execute %s\n", $2);
start_application($2);
free($2);
}
;
exit:
TOK_EXIT
{
printf("exit, bye bye\n");
exit(0);
}
;
reload:
TOK_RELOAD
{
printf("reloading\n");
kill_configerror_nagbar(false);
load_configuration(conn, NULL, true);
x_set_i3_atoms();
/* Send an IPC event just in case the ws names have changed */
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}");
}
;
restart:
TOK_RESTART
{
printf("restarting i3\n");
i3_restart(false);
}
;
focus:
TOK_FOCUS
{
owindow *current;
if (match_is_empty(&current_match)) {
ELOG("You have to specify which window/container should be focused.\n");
ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
asprintf(&json_output, "{\"success\":false, \"error\":\"You have to "
"specify which window/container should be focused\"}");
break;
}
int count = 0;
TAILQ_FOREACH(current, &owindows, owindows) {
Con *ws = con_get_workspace(current->con);
workspace_show(ws->name);
LOG("focusing %p / %s\n", current->con, current->con->name);
con_focus(current->con);
count++;
}
if (count > 1)
LOG("WARNING: Your criteria for the focus command matches %d containers, "
"while only exactly one container can be focused at a time.\n", count);
tree_render();
}
| TOK_FOCUS direction
{
int direction = $2;
switch (direction) {
case TOK_LEFT:
LOG("Focusing left\n");
tree_next('p', HORIZ);
break;
case TOK_RIGHT:
LOG("Focusing right\n");
tree_next('n', HORIZ);
break;
case TOK_UP:
LOG("Focusing up\n");
tree_next('p', VERT);
break;
case TOK_DOWN:
LOG("Focusing down\n");
tree_next('n', VERT);
break;
default:
ELOG("Invalid focus direction (%d)\n", direction);
break;
}
tree_render();
}
| TOK_FOCUS window_mode
{
printf("should focus: ");
if ($2 == TOK_TILING)
printf("tiling\n");
else if ($2 == TOK_FLOATING)
printf("floating\n");
else printf("mode toggle\n");
Con *ws = con_get_workspace(focused);
Con *current;
if (ws != NULL) {
int to_focus = $2;
if ($2 == TOK_MODE_TOGGLE) {
current = TAILQ_FIRST(&(ws->focus_head));
if (current->type == CT_FLOATING_CON)
to_focus = TOK_TILING;
else to_focus = TOK_FLOATING;
}
TAILQ_FOREACH(current, &(ws->focus_head), focused) {
if ((to_focus == TOK_FLOATING && current->type != CT_FLOATING_CON) ||
(to_focus == TOK_TILING && current->type == CT_FLOATING_CON))
continue;
con_focus(con_descend_focused(current));
break;
}
}
tree_render();
}
| TOK_FOCUS level
{
if ($2 == TOK_PARENT)
level_up();
else level_down();
tree_render();
}
;
window_mode:
TOK_TILING { $$ = TOK_TILING; }
| TOK_FLOATING { $$ = TOK_FLOATING; }
| TOK_MODE_TOGGLE { $$ = TOK_MODE_TOGGLE; }
;
level:
TOK_PARENT { $$ = TOK_PARENT; }
| TOK_CHILD { $$ = TOK_CHILD; }
;
kill:
TOK_KILL optional_kill_mode
{
owindow *current;
printf("killing!\n");
/* check if the match is empty, not if the result is empty */
if (match_is_empty(&current_match))
tree_close_con($2);
else {
TAILQ_FOREACH(current, &owindows, owindows) {
printf("matching: %p / %s\n", current->con, current->con->name);
tree_close(current->con, $2, false);
}
}
tree_render();
}
;
optional_kill_mode:
/* empty */ { $$ = KILL_WINDOW; }
| TOK_WINDOW { $$ = KILL_WINDOW; }
| TOK_CLIENT { $$ = KILL_CLIENT; }
;
workspace:
TOK_WORKSPACE TOK_NEXT
{
workspace_next();
tree_render();
}
| TOK_WORKSPACE TOK_PREV
{
workspace_prev();
tree_render();
}
| TOK_WORKSPACE STR
{
printf("should switch to workspace %s\n", $2);
workspace_show($2);
free($2);
tree_render();
}
;
open:
TOK_OPEN
{
printf("opening new container\n");
Con *con = tree_open_con(NULL, NULL);
con_focus(con);
asprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con);
tree_render();
}
;
fullscreen:
TOK_FULLSCREEN fullscreen_mode
{
printf("toggling fullscreen, mode = %s\n", ($2 == CF_OUTPUT ? "normal" : "global"));
owindow *current;
HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) {
printf("matching: %p / %s\n", current->con, current->con->name);
con_toggle_fullscreen(current->con, $2);
}
tree_render();
}
;
fullscreen_mode:
/* empty */ { $$ = CF_OUTPUT; }
| TOK_GLOBAL { $$ = CF_GLOBAL; }
;
split:
TOK_SPLIT split_direction
{
/* TODO: use matches */
printf("splitting in direction %c\n", $2);
tree_split(focused, ($2 == 'v' ? VERT : HORIZ));
tree_render();
}
;
split_direction:
TOK_HORIZONTAL { $$ = 'h'; }
| 'h' { $$ = 'h'; }
| TOK_VERTICAL { $$ = 'v'; }
| 'v' { $$ = 'v'; }
;
floating:
TOK_FLOATING boolean
{
HANDLE_EMPTY_MATCH;
owindow *current;
TAILQ_FOREACH(current, &owindows, owindows) {
printf("matching: %p / %s\n", current->con, current->con->name);
if ($2 == TOK_TOGGLE) {
printf("should toggle mode\n");
toggle_floating_mode(current->con, false);
} else {
printf("should switch mode to %s\n", ($2 == TOK_FLOATING ? "floating" : "tiling"));
if ($2 == TOK_ENABLE) {
floating_enable(current->con, false);
} else {
floating_disable(current->con, false);
}
}
}
tree_render();
}
;
boolean:
TOK_ENABLE { $$ = TOK_ENABLE; }
| TOK_DISABLE { $$ = TOK_DISABLE; }
| TOK_TOGGLE { $$ = TOK_TOGGLE; }
;
border:
TOK_BORDER border_style
{
printf("border style should be changed to %d\n", $2);
owindow *current;
HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) {
printf("matching: %p / %s\n", current->con, current->con->name);
if ($2 == TOK_TOGGLE) {
current->con->border_style++;
current->con->border_style %= 3;
} else current->con->border_style = $2;
}
tree_render();
}
;
border_style:
TOK_NORMAL { $$ = BS_NORMAL; }
| TOK_NONE { $$ = BS_NONE; }
| TOK_1PIXEL { $$ = BS_1PIXEL; }
| TOK_TOGGLE { $$ = TOK_TOGGLE; }
;
move:
TOK_MOVE direction
{
printf("moving in direction %d\n", $2);
tree_move($2);
tree_render();
}
| TOK_MOVE TOK_WORKSPACE STR
{
owindow *current;
printf("should move window to workspace %s\n", $3);
/* get the workspace */
Con *ws = workspace_get($3, NULL);
free($3);
HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) {
printf("matching: %p / %s\n", current->con, current->con->name);
con_move_to_workspace(current->con, ws);
}
tree_render();
}
;
append_layout:
TOK_APPEND_LAYOUT STR
{
printf("restoring \"%s\"\n", $2);
tree_append_json($2);
free($2);
tree_render();
}
;
layout:
TOK_LAYOUT layout_mode
{
printf("changing layout to %d\n", $2);
owindow *current;
/* check if the match is empty, not if the result is empty */
if (match_is_empty(&current_match))
con_set_layout(focused->parent, $2);
else {
TAILQ_FOREACH(current, &owindows, owindows) {
printf("matching: %p / %s\n", current->con, current->con->name);
con_set_layout(current->con, $2);
}
}
tree_render();
}
;
layout_mode:
TOK_DEFAULT { $$ = L_DEFAULT; }
| TOK_STACKED { $$ = L_STACKED; }
| TOK_TABBED { $$ = L_TABBED; }
;
mark:
TOK_MARK STR
{
printf("marking window with str %s\n", $2);
owindow *current;
HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) {
printf("matching: %p / %s\n", current->con, current->con->name);
current->con->mark = sstrdup($2);
}
free($<string>2);
tree_render();
}
;
nop:
TOK_NOP STR
{
printf("-------------------------------------------------\n");
printf(" NOP: %s\n", $2);
printf("-------------------------------------------------\n");
free($2);
}
;
resize:
TOK_RESIZE resize_way direction resize_px resize_tiling
{
/* resize <grow|shrink> <direction> [<px> px] [or <ppt> ppt] */
printf("resizing in way %d, direction %d, px %d or ppt %d\n", $2, $3, $4, $5);
int direction = $3;
int px = $4;
int ppt = $5;
if ($2 == TOK_SHRINK) {
px *= -1;
ppt *= -1;
}
if (con_is_floating(focused)) {
printf("floating resize\n");
if (direction == TOK_UP) {
focused->parent->rect.y -= px;
focused->parent->rect.height += px;
} else if (direction == TOK_DOWN) {
focused->rect.height += px;
} else if (direction == TOK_LEFT) {
focused->rect.x -= px;
focused->rect.width += px;
} else {
focused->rect.width += px;
}
} else {
LOG("tiling resize\n");
/* get the default percentage */
int children = con_num_children(focused->parent);
Con *other;
LOG("ins. %d children\n", children);
double percentage = 1.0 / children;
LOG("default percentage = %f\n", percentage);
if (direction == TOK_UP || direction == TOK_LEFT) {
other = TAILQ_PREV(focused, nodes_head, nodes);
} else {
other = TAILQ_NEXT(focused, nodes);
}
if (other == TAILQ_END(workspaces)) {
LOG("No other container in this direction found, cannot resize.\n");
return 0;
}
LOG("other->percent = %f\n", other->percent);
LOG("focused->percent before = %f\n", focused->percent);
if (focused->percent == 0.0)
focused->percent = percentage;
if (other->percent == 0.0)
other->percent = percentage;
focused->percent += ((double)ppt / 100.0);
other->percent -= ((double)ppt / 100.0);
LOG("focused->percent after = %f\n", focused->percent);
LOG("other->percent after = %f\n", other->percent);
}
tree_render();
}
;
resize_px:
/* empty */
{
$$ = 10;
}
| NUMBER TOK_PX
{
$$ = $1;
}
;
resize_tiling:
/* empty */
{
$$ = 10;
}
| TOK_OR NUMBER TOK_PPT
{
$$ = $2;
}
;
resize_way:
TOK_GROW { $$ = TOK_GROW; }
| TOK_SHRINK { $$ = TOK_SHRINK; }
;
direction:
TOK_UP { $$ = TOK_UP; }
| TOK_DOWN { $$ = TOK_DOWN; }
| TOK_LEFT { $$ = TOK_LEFT; }
| TOK_RIGHT { $$ = TOK_RIGHT; }
;
mode:
TOK_MODE STR
{
switch_mode($2);
}
;

Some files were not shown because too many files have changed in this diff Show More