Merge branch 'tree' into next
This commit is contained in:
commit
2728c02467
32
.gitignore
vendored
32
.gitignore
vendored
@ -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
63
DEPENDS
@ -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 distribution’s 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.
|
||||
|
71
Makefile
71
Makefile
@ -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
|
||||
|
@ -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 user’s 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
|
||||
|
@ -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: Don’t 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: Don’t 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
|
@ -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 window’s 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
|
||||
* Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t allow fullscreen floating windows to be moved
|
||||
* Bugfix: Don’t 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
135
RELEASE-NOTES-4.0
Normal 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
|
||||
don’t 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 don’t 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
45
RELEASE-NOTES-tree-pr1
Normal 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 i3’s 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 it’s 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
43
RELEASE-NOTES-tree-pr2
Normal 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
|
||||
i3’s 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 it’s 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
43
RELEASE-NOTES-tree-pr3
Normal 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
|
||||
i3’s 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 it’s 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
37
RELEASE-NOTES-tree-pr4
Normal 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
|
||||
i3’s 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
|
94
common.mk
94
common.mk
@ -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 don’t 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 system’s 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
|
||||
|
||||
# Don’t print command lines which are run
|
||||
|
6
debian/changelog
vendored
6
debian/changelog
vendored
@ -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
12
debian/control
vendored
@ -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
1
debian/rules
vendored
@ -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.
|
||||
|
@ -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 '{}' \;
|
||||
|
@ -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 you’re 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 you’re 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)
|
||||
|
@ -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 i3’s 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 table’s 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 don’t 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 it’s 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
209
docs/ipc
@ -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
|
||||
container’s 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
192
docs/tree-migrating
Normal 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:
|
||||
Let’s 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
|
||||
|
||||
Let’s 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 User’s Guide] or it is not yet
|
||||
implemented.
|
660
docs/userguide
660
docs/userguide
File diff suppressed because it is too large
Load Diff
45
dump-asy.pl
Executable file
45
dump-asy.pl
Executable 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
219
gtk-tree-watch.pl
Executable 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
42
i3-config-wizard/Makefile
Normal 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
|
6
i3-config-wizard/atoms.xmacro
Normal file
6
i3-config-wizard/atoms.xmacro
Normal 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
105
i3-config-wizard/cfgparse.l
Normal 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
161
i3-config-wizard/cfgparse.y
Normal 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 don’t 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
68
i3-config-wizard/ipc.c
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
*/
|
||||
#include <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
9
i3-config-wizard/ipc.h
Normal 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
546
i3-config-wizard/main.c
Normal 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 it’s 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 doesn’t work.
|
||||
* As i3-msg is a short-running tool, we don’t 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 it’s 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 wm’s 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
221
i3-config-wizard/xcb.c
Normal 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
24
i3-config-wizard/xcb.h
Normal 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
|
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -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 doesn’t work.
|
||||
* As i3-msg is a short-running tool, we don’t 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;
|
||||
}
|
||||
|
@ -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
358
i3-migrate-config-to-v4.pl
Executable 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) isn’t
|
||||
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 it’s 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";
|
||||
}
|
@ -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"
|
||||
|
300
i3-msg/main.c
300
i3-msg/main.c
@ -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 doesn’t work.
|
||||
* As i3-msg is a short-running tool, we don’t 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 don’t 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
30
i3-nagbar/Makefile
Normal 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
6
i3-nagbar/atoms.xmacro
Normal 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
26
i3-nagbar/i3-nagbar.h
Normal 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
395
i3-nagbar/main.c
Normal 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 application’s parent exits (immediately),
|
||||
* the application is reparented to init (process-id 1), which correctly handles
|
||||
* childs, so we don’t have to do it :-).
|
||||
*
|
||||
* The shell is determined by looking for the SHELL environment variable. If it
|
||||
* does not exist, /bin/sh is used.
|
||||
*
|
||||
*/
|
||||
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
132
i3-nagbar/xcb.c
Normal 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
239
i3.config
@ -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 don’t 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
147
i3.config.keycodes
Normal 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
67
include/all.h
Normal 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
21
include/assignments.h
Normal 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
33
include/atoms.xmacro
Normal 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)
|
@ -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
|
||||
|
155
include/client.h
155
include/client.h
@ -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 doesn’t matter) below the
|
||||
* first floating client, so that floating windows are always on top.
|
||||
*
|
||||
*/
|
||||
void client_set_below_floating(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Returns true if the client is floating. Makes the code more beatiful, as
|
||||
* floating is not simply a boolean, but also saves whether the user selected
|
||||
* the current state or whether it was automatically set.
|
||||
*
|
||||
*/
|
||||
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 client’s 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 client’s 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
6
include/cmdparse.h
Normal file
@ -0,0 +1,6 @@
|
||||
#ifndef _CMDPARSE_H
|
||||
#define _CMDPARSE_H
|
||||
|
||||
char *parse_cmd(const char *new);
|
||||
|
||||
#endif
|
@ -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
215
include/con.h
Normal 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 container’s 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
|
@ -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);
|
||||
|
||||
|
@ -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
|
757
include/data.h
757
include/data.h
@ -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 @@
|
||||
*
|
||||
* Let’s 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;
|
||||
|
||||
/* We’re going to paint on it, so a graphics context will be needed */
|
||||
xcb_gcontext_t gc;
|
||||
|
||||
/* The rect with which the pixmap was created */
|
||||
Rect rect;
|
||||
|
||||
/* The rect of the object to which this pixmap belongs. Necessary to
|
||||
* find out when we need to re-create the pixmap. */
|
||||
Rect *referred_rect;
|
||||
|
||||
xcb_drawable_t referred_drawable;
|
||||
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 workspace’s name (in glyphs) */
|
||||
int name_len;
|
||||
|
||||
/** Width of the workspace’s name (in pixels) rendered in config.font */
|
||||
int text_width;
|
||||
|
||||
/** x, y, width, height */
|
||||
Rect rect;
|
||||
|
||||
/** table dimensions */
|
||||
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. I’ve always wanted to be a three-star
|
||||
* programmer :) */
|
||||
Container ***table;
|
||||
|
||||
/** width_factor and height_factor contain the amount of space
|
||||
* (percentage) a column/row has of all the space which is available
|
||||
* for resized windows. This ensures that non-resized windows (newly
|
||||
* opened, for example) have the same size as always */
|
||||
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, /* don’t float, but put on a workspace */
|
||||
ASSIGN_FLOATING_ONLY, /* float, but don’t assign on a workspace */
|
||||
ASSIGN_FLOATING /* float and put on a workspace */
|
||||
} floating;
|
||||
|
||||
/** The number of the workspace to assign to. */
|
||||
int workspace;
|
||||
TAILQ_ENTRY(Assignment) assignments;
|
||||
/** 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 don’t 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 client’s 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 user’s choice overwrites automatic mode, of course. The
|
||||
* order of the values is important because we check with >=
|
||||
* FLOATING_AUTO_ON if a client is floating. */
|
||||
enum { FLOATING_AUTO_OFF = 0, FLOATING_USER_OFF = 1, FLOATING_AUTO_ON = 2, FLOATING_USER_ON = 3 } floating;
|
||||
|
||||
/** Ensure TITLEBAR_TOP maps to 0 because we use calloc for
|
||||
* initialization later */
|
||||
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 they’re unmapped, we need to ignore that
|
||||
* one. Therefore, this flag is set when reparenting. */
|
||||
bool awaiting_useless_unmap;
|
||||
|
||||
/* XCB contexts */
|
||||
xcb_window_t frame; /**< Our window: The frame around the
|
||||
* client */
|
||||
xcb_gcontext_t titlegc; /**< The titlebar’s graphic context
|
||||
* inside the frame */
|
||||
xcb_window_t child; /**< The client’s window */
|
||||
|
||||
/** 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 user’s choice overwrites automatic mode, of course. The
|
||||
* order of the values is important because we check with >=
|
||||
* FLOATING_AUTO_ON if a client is floating. */
|
||||
enum {
|
||||
FLOATING_AUTO_OFF = 0,
|
||||
FLOATING_USER_OFF = 1,
|
||||
FLOATING_AUTO_ON = 2,
|
||||
FLOATING_USER_ON = 3
|
||||
} floating;
|
||||
|
||||
/** 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* vim:ts=8:expandtab
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
|
@ -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 con’s 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);
|
||||
|
||||
|
@ -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 let’s 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
|
||||
|
20
include/i3.h
20
include/i3.h
@ -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
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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 it’s in a stacking
|
||||
* container and re-rendering the stack window or just calling decorate_window
|
||||
* if it’s not in a stacking container.
|
||||
*
|
||||
*/
|
||||
void redecorate_window(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Pushes the client’s x and y coordinates to X11
|
||||
*
|
||||
*/
|
||||
void reposition_client(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Pushes the client’s width/height to X11 and resizes the child window. This
|
||||
* function also updates the client’s position, so if you work on tiling clients
|
||||
* only, you can use this function instead of separate calls to reposition_client
|
||||
* and resize_client to reduce flickering.
|
||||
*
|
||||
*/
|
||||
void resize_client(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Renders the given container. Is called by render_layout() or individually
|
||||
* (for example when focus changes in a stacking container)
|
||||
*
|
||||
*/
|
||||
void render_container(xcb_connection_t *conn, Container *container);
|
||||
|
||||
/**
|
||||
* Modifies the event mask of all clients on the given workspace to either
|
||||
* ignore or to handle enter notifies. It is handy to ignore notifies because
|
||||
* they will be sent when a window is mapped under the cursor, thus when the
|
||||
* user didn’t enter the window actively at all.
|
||||
*
|
||||
*/
|
||||
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 don’t need to render *everything*, you should call render_container
|
||||
* on the container you want to refresh.
|
||||
*
|
||||
*/
|
||||
void render_layout(xcb_connection_t *conn);
|
||||
|
||||
#endif
|
6
include/load_layout.h
Normal file
6
include/load_layout.h
Normal file
@ -0,0 +1,6 @@
|
||||
#ifndef _LOAD_LAYOUT_H
|
||||
#define _LOAD_LAYOUT_H
|
||||
|
||||
void tree_append_json(const char *filename);
|
||||
|
||||
#endif
|
@ -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.
|
||||
|
@ -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
31
include/match.h
Normal 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
15
include/move.h
Normal 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
14
include/output.h
Normal 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
|
@ -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.
|
||||
*/
|
||||
|
@ -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
18
include/render.h
Normal 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
|
@ -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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* vim:ts=8:expandtab
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
|
@ -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 table’s 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 table’s contents in human-readable form for debugging
|
||||
*
|
||||
*/
|
||||
void dump_table(xcb_connection_t *conn, Workspace *workspace);
|
||||
|
||||
#endif
|
97
include/tree.h
Normal file
97
include/tree.h
Normal 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
|
126
include/util.h
126
include/util.h
@ -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 application’s 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 FreeBSD’s 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
45
include/window.h
Normal 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
|
@ -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 workspace’s
|
||||
* 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
107
include/x.h
Normal 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
|
@ -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 doesn’t 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 client’s 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
25
include/xcb_compat.h
Normal 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
33
include/xcursor.h
Normal 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
|
@ -18,6 +18,6 @@
|
||||
* Xinerama information to setup workspaces for each screen.
|
||||
*
|
||||
*/
|
||||
void initialize_xinerama(xcb_connection_t *conn);
|
||||
void xinerama_init();
|
||||
|
||||
#endif
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
|
@ -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
34
man/i3-nagbar.man
Normal 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
|
92
man/i3.man
92
man/i3.man
@ -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
21
render-tree/Con.pm
Normal 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
125
render-tree/render.pl
Executable 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
68
src/assignments.c
Normal 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;
|
||||
}
|
180
src/cfgparse.l
180
src/cfgparse.l
@ -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();
|
||||
}
|
||||
|
||||
%%
|
||||
|
1472
src/cfgparse.y
1472
src/cfgparse.y
File diff suppressed because it is too large
Load Diff
588
src/click.c
588
src/click.c
@ -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 don’t 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 client’s 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 client’s 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);
|
||||
|
||||
/* Let’s see if this was on the borders (= resize). If not, we’re 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;
|
||||
}
|
||||
|
||||
/* Don’t 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);
|
||||
|
||||
/* don’t 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 doesn’t 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);
|
||||
}
|
||||
|
484
src/client.c
484
src/client.c
@ -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 client’s 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, we’re 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, let’s 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);
|
||||
|
||||
/* Child’s 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 it’s 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 haven’t changed, it would not be
|
||||
re-configured if we don’t 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 doesn’t matter) below the first floating client, so that
|
||||
* floating windows are always on top.
|
||||
*
|
||||
*/
|
||||
void client_set_below_floating(xcb_connection_t *conn, Client *client) {
|
||||
/* 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 child’s 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 don’t want to drag & drop if we don’t.
|
||||
* Also, xprop(1) needs that to work. */
|
||||
long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
|
||||
|
||||
xcb_map_window(conn, client->frame);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 client’s 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
171
src/cmdparse.l
Normal 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
859
src/cmdparse.y
Normal 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(¤t_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 don’t 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(¤t_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(¤t_match);
|
||||
}
|
||||
;
|
||||
|
||||
command:
|
||||
match operations
|
||||
;
|
||||
|
||||
match:
|
||||
| matchstart criteria matchend
|
||||
{
|
||||
printf("match parsed\n");
|
||||
}
|
||||
;
|
||||
|
||||
matchstart:
|
||||
'['
|
||||
{
|
||||
printf("start\n");
|
||||
match_init(¤t_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 element’s
|
||||
* 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(¤t_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(¤t_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(¤t_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(¤t_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
Loading…
Reference in New Issue
Block a user