│ 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/ │
+│ libev │ 4.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/ │
│ 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/ │
+│ PCRE │ 8.12 │ 8.12 │ http://www.pcre.org/ │
└─────────────┴────────┴────────┴────────────────────────────────────────┘
i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any new
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions
$(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/
$(INSTALL) -m 0755 i3-migrate-config-to-v4 $(DESTDIR)$(PREFIX)/bin/
+ $(INSTALL) -m 0755 i3-sensible-editor $(DESTDIR)$(PREFIX)/bin/
+ $(INSTALL) -m 0755 i3-sensible-pager $(DESTDIR)$(PREFIX)/bin/
+ $(INSTALL) -m 0755 i3-sensible-terminal $(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
[ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION}
[ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2
mkdir i3-${VERSION}
- cp i3-migrate-config-to-v4 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 i3-migrate-config-to-v4 i3-sensible-* 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 i3bar yajl-fallback include man i3-${VERSION}
# Only copy toplevel documentation (important stuff)
mkdir i3-${VERSION}/docs
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).
+Please check the i3-sensible-{editor,pager,terminal} scripts and modify them if
+necessary. The former two provide fallbacks in case $PAGER or $EDITOR is not
+set (which might be more common than you think, because they have to be set in
+~/.xsession, not in the shell configuration!) while the latter tries to launch
+a terminal emulator. The scripts are most prominently used in i3-nagbar, which
+alerts the user when the configuration is broken for some reason. Also,
+i3-sensible-terminal is used in the default configuration.
-On debian, this looks like this:
+If your distribution has a mechanism to get the preferred terminal, such as the
+x-terminal-emulator symlink in Debian, please use it in i3-sensible-terminal.
+
+On debian, compilation and installing the manpages looks like this:
# Compilation
- $(MAKE) TERM_EMU=x-terminal-emulator
+ $(MAKE)
$(MAKE) -C man
# Installation
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 $(TOPDIR)/.git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' $(TOPDIR)/.git/HEAD || echo 'unknown'))"
VERSION:=$(shell git describe --tags --abbrev=0)
CFLAGS += $(call cflags_for_lib, x11)
CFLAGS += $(call cflags_for_lib, yajl)
CFLAGS += $(call cflags_for_lib, libev)
+CFLAGS += $(call cflags_for_lib, libpcre)
CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\"
CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\"
CPPFLAGS += -DTERM_EMU=\"$(TERM_EMU)\"
+ifeq ($(shell pkg-config --atleast-version=8.10 libpcre && echo 1),1)
+CPPFLAGS += -DPCRE_HAS_UCP=1
+endif
+
LIBS += -lm
LIBS += $(call ldflags_for_lib, xcb-event, xcb-event)
LIBS += $(call ldflags_for_lib, xcb-keysyms, xcb-keysyms)
LIBS += $(call ldflags_for_lib, x11, X11)
LIBS += $(call ldflags_for_lib, yajl, yajl)
LIBS += $(call ldflags_for_lib, libev, ev)
+LIBS += $(call ldflags_for_lib, libpcre, pcre)
# Please test if -Wl,--as-needed works on your platform and send me a patch.
# it is known not to work on Darwin (Mac OS X)
-i3-wm (4.0.3-0) unstable; urgency=low
+i3-wm (4.1-0) unstable; urgency=low
* NOT YET RELEASED!
+ * Implement system tray support in i3bar (for NetworkManager, Skype, …)
+ * Implement support for PCRE regular expressions in criteria
+ * Implement a new assign syntax which uses criteria
+ * Sort named workspaces whose name starts with a number accordingly
+ * Warn on duplicate bindings for the same key
+ * Restrict 'resize' command to left/right for horizontal containers, up/down
+ for vertical containers
+ * Implement the GET_MARKS IPC request to get all marks
+ * Implement the new_float config option (border style for floating windows)
+ * Implement passing IPC sockets to i3 (systemd-style socket activation)
+ * Implement the 'move output' command to move containers to a specific output
+ * Bugfix: Preserve marks when restarting
-- Michael Stapelberg <michael@stapelberg.de> Sun, 28 Aug 2011 20:17:31 +0200
Priority: extra
Maintainer: Michael Stapelberg <michael@stapelberg.de>
DM-Upload-Allowed: yes
-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
+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, libpcre3-dev
Standards-Version: 3.9.2
Homepage: http://i3wm.org/
-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 refcard.pdf testsuite.html
hacking-howto.html: hacking-howto
asciidoc -a toc -n $<
userguide.html: userguide
asciidoc -a toc -n $<
+testsuite.html: testsuite
+ asciidoc -a toc -n $<
+
ipc.html: ipc
asciidoc -a toc -n $<
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).
+GET_MARKS (5)::
+ Gets a list of marks (identifiers for containers to easily jump to them
+ later). The reply will be a JSON-encoded list of window marks (see
+ reply section).
So, a typical message could look like this:
--------------------------------------------------
Reply to the GET_OUTPUTS message.
GET_TREE (4)::
Reply to the GET_TREE message.
+GET_MARKS (5)::
+ Reply to the GET_MARKS message.
=== COMMAND reply
}
]
}
+
+
+=== GET_MARKS reply
+
+The reply consists of a single array of strings for each container that has a
+mark. The order of that array is undefined. If more than one container has the
+same mark, it will be represented multiple times in the reply (the array
+contents are not unique).
+
+If no window has a mark the response will be the empty array [].
------------------------
The multi-monitor situation
===========================
Michael Stapelberg <michael+i3@stapelberg.de>
-March 2010
+September 2011
…or: oh no, I have an nVidia graphics card!
exec i3 --force-xinerama -V >>~/.i3/i3log 2>&1
----------------------------------------------
+…or use +force_xinerama yes+ in your configuration file.
+
== The explanation
Starting with version 3.ε, i3 uses the RandR (Rotate and Resize) API instead
For this very reason, we decided to implement the following workaround: As
long as the nVidia driver does not support RandR, an option called
-+--force-xinerama+ is available in i3. This option gets the list of screens
-*once* when starting, and never updates it. As the nVidia driver cannot do
-dynamic configuration anyways, this is not a big deal.
++--force-xinerama+ is available in i3 (alternatively, you can use the
++force_xinerama+ configuration file directive). This option gets the list of
+screens *once* when starting, and never updates it. As the nVidia driver cannot
+do dynamic configuration anyways, this is not a big deal.
+
+Also note that your output names are not descriptive (like +HDMI1+) when using
+Xinerama, instead they are counted up, starting at 0: +xinerama-0+, +xinerama-1+, …
== See also
--- /dev/null
+i3 testsuite
+============
+Michael Stapelberg <michael+i3@stapelberg.de>
+September 2011
+
+This document explains how the i3 testsuite works, how to use it and extend it.
+It is targeted at developers who not necessarily have been doing testing before
+or have not been testing in Perl before. In general, the testsuite is not of
+interest for end users.
+
+
+== Introduction
+
+The i3 testsuite is a collection of files which contain testcases for various
+i3 features. Some of them test if a certain workflow works correctly (moving
+windows, focus behaviour, …). Others are regression tests and contain code
+which previously made i3 crash or lead to unexpected behaviour. They then check
+if i3 still runs (meaning it did not crash) and if it handled everything
+correctly.
+
+The goal of having these tests is to automatically find problems and to
+automatically get a feel for whether a change in the source code breaks any
+existing feature. After every modification of the i3 sourcecode, the developer
+should run the full testsuite. If one of the tests fails, the corresponding
+problem should be fixed (or, in some cases, the testcase has to be modified).
+For every bugreport, a testcase should be written to test the correct
+behaviour. Initially, it will fail, but after fixing the bug, it will pass.
+This ensures (or increases the chance) that bugs which have been fixed once
+will never be found again.
+
+Also, when implementing a new feature, a testcase might be a good way to be
+able to easily test if the feature is working correctly. Many developers will
+test manually if everything works. Having a testcase not only helps you with
+that, but it will also be useful for every future change.
+
+== Implementation
+
+For several reasons, the i3 testsuite has been implemented in Perl:
+
+1. Perl has a long tradition of testing. Every popular/bigger Perl module which
+ you can find on CPAN will not only come with documentation, but also with
+ tests. Therefore, the available infrastructure for tests is comprehensive.
+ See for example the excellent http://search.cpan.org/perldoc?Test::More
+ and the referenced http://search.cpan.org/perldoc?Test::Tutorial.
+
+2. Perl is widely available and has a well-working package infrastructure.
+3. The author is familiar with Perl :).
+
+Please do not start programming language flamewars at this point.
+
+=== Mechanisms
+
+==== Script: complete-run
+
+The testcases are run by a script called +complete-run.pl+. It runs all
+testcases by default, but you can be more specific and let it only run one or
+more testcases. Also, it takes care of starting up a separate instance of i3
+with an appropriate configuration file and creates a folder for each run
+containing the appropriate i3 logfile for each testcase. The latest folder can
+always be found under the symlink +latest/+. It is recommended that you run the
+tests on one or more separate X server instances (you can only start one window
+manager per X session), for example using the provided Xdummy script.
++complete-run.pl+ takes one or more X11 display specifications and parallelizes
+the testcases appropriately:
+
+.Example invocation of complete-run.pl+
+---------------------------------------
+$ cd ~/i3/testcases
+
+# start two dummy X11 instances in the background
+$ ./Xdummy :1 &
+$ ./Xdummy :2 &
+
+$ ./complete-run.pl -d :1,:2
+# output omitted because it is very long
+All tests successful.
+Files=78, Tests=734, 27 wallclock secs ( 0.38 usr 0.48 sys + 17.65 cusr 3.21 csys = 21.72 CPU)
+Result: PASS
+
+$ ./complete-run.pl -d :1 t/04-floating.t
+[:3] i3 startup: took 0.07s, status = 1
+[:3] Running t/04-floating.t with logfile testsuite-2011-09-24-16-06-04-4.0.2-226-g1eb011a/i3-log-for-04-floating.t
+[:3] t/04-floating.t finished
+[:3] killing i3
+output for t/04-floating.t:
+ok 1 - use X11::XCB::Window;
+ok 2 - The object isa X11::XCB::Window
+ok 3 - Window is mapped
+ok 4 - i3 raised the width to 75
+ok 5 - i3 raised the height to 50
+ok 6 - i3 did not map it to (0x0)
+ok 7 - The object isa X11::XCB::Window
+ok 8 - i3 let the width at 80
+ok 9 - i3 let the height at 90
+ok 10 - i3 mapped it to x=1
+ok 11 - i3 mapped it to y=18
+ok 12 - The object isa X11::XCB::Window
+ok 13 - i3 let the width at 80
+ok 14 - i3 let the height at 90
+1..14
+
+All tests successful.
+Files=1, Tests=14, 0 wallclock secs ( 0.01 usr 0.00 sys + 0.19 cusr 0.03 csys = 0.23 CPU)
+Result: PASS
+
+$ less latest/i3-log-for-04-floating.t
+----------------------------------------
+
+==== IPC interface
+
+The testsuite makes extensive use of the IPC (Inter-Process Communication)
+interface which i3 provides. It is used for the startup process of i3, for
+terminating it cleanly and (most importantly) for modifying and getting the
+current state (layout tree).
+
+See [http://i3wm.org/docs/ipc.html] for documentation on the IPC interface.
+
+==== X11::XCB
+
+In order to open new windows, change attributes, get events, etc., the
+testsuite uses X11::XCB, a new (and quite specific to i3 at the moment) Perl
+module which uses the XCB protocol description to generate Perl bindings to
+X11. They work in a very similar way to libxcb (which i3 uses) and provide
+relatively high-level interfaces (objects such as +X11::XCB::Window+) aswell as
+access to the low-level interface, which is very useful when testing a window
+manager.
+
+=== Filesystem structure
+
+In the git root of i3, the testcases live in the folder +testcases+. This
+folder contains the +complete-run.pl+ and +Xdummy+ scripts and a base
+configuration file which will be used for the tests. The different testcases
+(their file extension is .t, not .pl) themselves can be found in the
+conventionally named subfolder +t+:
+
+.Filesystem structure
+--------------------------------------------
+├── testcases
+│ ├── complete-run.pl
+│ ├── i3-test.config
+│ ├── t
+│ │ ├── 00-load.t
+│ │ ├── 01-tile.t
+│ │ ├── 02-fullscreen.t
+│ │ ├── ...
+│ │ ├── omitted for brevity
+│ │ ├── ...
+│ │ ├── 74-regress-focus-toggle.t
+│ │ └── lib
+│ │ └── i3test.pm
+│ └── Xdummy
+--------------------------------------------
+
+== Anatomy of a testcase
+
+Learning by example is definitely a good strategy when you are wondering how to
+write a testcase. Let's take +t/11-goto.t+ as an easy example and go through it
+step by step:
+
+.t/11-goto.t: Boilerplate
+----------------------
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use File::Temp;
+
+my $x = X11::XCB::Connection->new;
+-----------------------
+
+This is what we call boilerplate. It exists at the top of every test file (to
+some extent). The first line is the shebang, which specifies that this file is
+a Perl script. The second line contains VIM specific settings on how to
+edit/format this file (use spaces instead of tabs, indent using 4 spaces).
+Afterwards, the +i3test+ module is used. This module contains i3 testsuite
+specific functions which you are strongly encouraged to use. They make writing
+testcases a lot easier and will make it easier for other people to read your
+tests.
+
+The next line uses the +File::Temp+ module. This is specific to this testcase,
+because it needs to generate a temporary name during the test. Many testcases
+use only the +i3test+ module.
+
+The last line opens a connection to X11. You might or might not need this in
+your testcase, depending on whether you are going to open windows (etc.) or
+only use i3 commands.
+
+.t/11-goto.t: Setup
+----------------------
+my $tmp = fresh_workspace;
+
+cmd 'split h';
+----------------------
+
+The first line calls i3test's +fresh_workspace+ function which looks for a
+currently unused workspace, switches to it, and returns its name. The variable
++$tmp+ will end up having a value such as +"/tmp/87kBVcHbA9"+. Note that this
+is not (necessarily) a valid path, it's just a random workspace name.
+
+So, now that we are on a new workspace, we ensure that the workspace uses
+horizontal orientation by issuing the +split h+ command (see the i3 User's
+Guide for a list of commands). This is not strictly necessary, but good style.
+In general, the +cmd+ function executes the specified i3 command by using the
+IPC interface and returns once i3 acknowledged the command.
+
+.t/11-goto.t: Setup
+----------------------
+#####################################################################
+# Create two windows and make sure focus switching works
+#####################################################################
+
+my $top = open_window($x);
+my $mid = open_window($x);
+my $bottom = open_window($x);
+----------------------
+
+In every major section of a testcase, you should put a comment like the one
+above. This makes it immediately clear how the file is structured.
+
+The +open_window+ function opens a standard window, which will then be put into
+tiling mode by i3. If you want a floating window, use the
++open_floating_window+ function. These functions accept the same parameters as
++X11::XCB::Window->new+, see the i3test documentation at TODO.
+
+.t/11-goto.t: Helper function
+----------------------
+#
+# Returns the input focus after sending the given command to i3 via IPC
+# and syncing with i3
+#
+sub focus_after {
+ my $msg = shift;
+
+ cmd $msg;
+ sync_with_i3 $x;
+ return $x->input_focus;
+}
+----------------------
+
+This section defines a helper function which will be used over and over in this
+testcase. If you have code which gets executed more than once or twice
+(depending on the length of your test, use your best judgement), please put it
+in a function. Tests should be short, concise and clear.
+
+The +focus_after+ function executes a command and returns the X11 focus after
+the command was executed. The +sync_with_i3+ command makes sure that i3 could
+push its state to X11. See <<i3_sync>> to learn how this works exactly.
+
+.t/11-goto.t: Test assumptions
+----------------------
+$focus = $x->input_focus;
+is($focus, $bottom->id, "Latest window focused");
+
+$focus = focus_after('focus left');
+is($focus, $mid->id, "Middle window focused");
+----------------------
+
+Now, we run the first two real tests. They use +Test::More+'s +is+ function,
+which compares two values and prints the differences if they are not the same.
+After the arguments, we supply a short comment to indicate what we are testing
+here. This makes it vastly more easy for the developer to spot which testcase
+is the problem in case one fails.
+
+The first test checks that the most recently opened window is focused.
+Afterwards, the command +focus left+ is issued and it is verified that the
+middle window now has focus.
+
+Note that this is not a comprehensive test of the +focus+ command -- we would
+have to test wrapping, focus when using a more complex layout, focusing the
+parent/child containers, etc. But that is not the point of this testcase.
+Instead, we just want to know if +$x->input_focus+ corresponds with what we are
+expecting. If not, something is completely wrong with the test environment and
+this trivial test will fail.
+
+.t/11-goto.t: Test that the feature does not work (yet)
+----------------------
+#####################################################################
+# Now goto a mark which does not exist
+#####################################################################
+
+my $random_mark = mktemp('mark.XXXXXX');
+
+$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
+is($focus, $mid->id, "focus unchanged");
+----------------------
+
+Syntax hint: The qq keyword is the interpolating quote operator. It lets you
+chose a quote character (in this case the +|+ character, a pipe). This makes
+having double quotes in our string easy.
+
+In this new major section, a random mark (mark is an identifier for a window,
+see "VIM-like marks" in the i3 User’s Guide) will be generated. Afterwards, we
+test that trying to focus that mark will not do anything. This is important: Do
+not only test that using a feature has the expected outcome, but also test that
+using it without properly initializing it does no harm. This command could for
+example have changed focus anyways (a bug) or crash i3 (obviously a bug).
+
+.t/11-goto.t: Test that the feature does work
+----------------------
+cmd "mark $random_mark";
+
+$focus = focus_after('focus left');
+is($focus, $top->id, "Top window focused");
+
+$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
+is($focus, $mid->id, "goto worked");
+----------------------
+
+Remember: Focus was on the middle window (we verified that earlier in "Test
+assumptions"). We now mark the middle window with our randomly generated mark.
+Afterwards, we switch focus away from the middle window to be able to tell if
+focusing it via its mark will work. If the test works, the goto command seems
+to be working.
+
+.t/11-goto.t: Test corner case
+----------------------
+# check that we can specify multiple criteria
+
+$focus = focus_after('focus left');
+is($focus, $top->id, "Top window focused");
+
+$focus = focus_after(qq|[con_mark="$random_mark" con_mark="$random_mark"] focus|);
+is($focus, $mid->id, "goto worked");
+----------------------
+
+Now we test the same feature, but specifying the mark twice in the command.
+This should have no effect, but let’s be sure: test it and see if things go
+wrong.
+
+.t/11-goto.t: Test second code path
+----------------------
+#####################################################################
+# Check whether the focus command will switch to a different
+# workspace if necessary
+#####################################################################
+
+my $tmp2 = fresh_workspace;
+
+is(focused_ws(), $tmp2, 'tmp2 now focused');
+
+cmd qq|[con_mark="$random_mark"] focus|;
+
+is(focused_ws(), $tmp, 'tmp now focused');
+----------------------
+
+This part of the test checks that focusing windows by mark works across
+workspaces. It uses i3test's +focused_ws+ function to get the current
+workspace.
+
+.t/11-goto.t: Test second code path
+----------------------
+done_testing;
+----------------------
+
+The end of every testcase has to contain the +done_testing+ line. This tells
++complete-run.pl+ that the test was finished successfully. If it does not
+occur, the test might have crashed during execution -- some of the reasons why
+that could happen are bugs in the used modules, bugs in the testcase itself or
+an i3 crash resulting in the testcase being unable to communicate with i3 via
+IPC anymore.
+
+[[i3_sync]]
+== Appendix A: The i3 sync protocol
+
+Consider the following situation: You open two windows in your testcase, then
+you use +focus left+ and want to verify that the X11 focus has been updated
+properly. Sounds simple, right? Let’s assume you use this straight-forward
+implementation:
+
+.Racey focus testcase
+-----------
+my $left = open_window($x);
+my $right = open_window($x);
+cmd 'focus left';
+is($x->input_focus, $left->id, 'left window focused');
+----------
+
+However, the test fails. Sometimes. Apparantly, there is a race condition in
+your test. If you think about it, this is because you are using two different
+pieces of software: You tell i3 to update focus, i3 confirms that, and then you
+ask X11 to give you the current focus. There is a certain time i3 needs to
+update the X11 state. If the testcase gets CPU time before X11 processed i3's
+requests, the test will fail.
+
+image::i3-sync.png["Diagram of the race condition", title="Diagram of the race condition"]
+
+One way to "solve" this would be to add +sleep 0.5;+ after the +cmd+ call.
+After 0.5 seconds it should be safe to assume that focus has been updated,
+right?
+
+In practice, this usually works. However, it has several problems:
+
+1. This is obviously not a clean solution, but a workaround. Ugly.
+2. On very slow machines, this might not work. Unlikely, but in different
+ situations (a delay to wait for i3 to startup) the necessary time is much
+ harder to guess, even for fast machines.
+3. This *wastes a lot of time*. Usually, your computer is much faster than 0.5s
+ to update the status. However, sometimes, it might take 0.4s, so we can’t
+ make it +sleep 0.1+.
+
+To illustrate how grave the problem with wasting time actually is: Before
+removing all sleeps from the testsuite, a typical run using 4 separate X
+servers took around 50 seconds on my machine. After removing all the sleeps,
+we achieved times of about 25 seconds. This is very significant and influences
+the way you think about tests -- the faster they are, the more likely you are
+to check whether everything still works quite often (which you should).
+
+What I am trying to say is: Delays adds up quickly and make the test suite
+less robust.
+
+The real solution for this problem is a mechanism which I call "the i3 sync
+protocol". The idea is to send a request (which does not modify state) via X11
+to i3 which will then be answered. Due to the request's position in the event
+queue (*after* all previous events), you can be sure that by the time you
+receive the reply, all other events have been dealt with by i3 (and, more
+importantly, X11).
+
+image::i3-sync-working.png["Diagram of the i3 sync solution", title="Diagram of the i3 sync solution"]
+
+=== Implementation details
+
+The client which wants to sync with i3 initiates the protocol by sending a
+ClientMessage to the X11 root window:
+
+.Send ClientMessage
+-------------------
+# Generate a ClientMessage, see xcb_client_message_t
+my $msg = pack "CCSLLLLLLL",
+ CLIENT_MESSAGE, # response_type
+ 32, # format
+ 0, # sequence
+ $root, # destination window
+ $x->atom(name => 'I3_SYNC')->id,
+
+ $_sync_window->id, # data[0]: our own window id
+ $myrnd, # data[1]: a random value to identify the request
+ 0,
+ 0,
+ 0;
+
+# Send it to the root window -- since i3 uses the SubstructureRedirect
+# event mask, it will get the ClientMessage.
+$x->send_event(0, $root, EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
+-------------------
+
+i3 will then reply with the same ClientMessage, sent to the window specified in
++data[0]+. In the reply, +data[0]+ and +data[1]+ are exactly the same as in the
+request. You should use a random value in +data[1]+ and check that you received
+the same one when getting the reply.
+
+== Appendix B: Socket activation
i3 User’s Guide
===============
Michael Stapelberg <michael+i3@stapelberg.de>
-August 2011
+September 2011
This document contains all the information you need to configure and use the i3
window manager. If it does not, please contact us on IRC (preferred) or post your
*Syntax*:
-----------------------------
-for_window [criteria] command
+for_window <criteria> command
-----------------------------
*Examples*:
[[assign_workspace]]
-Specific windows can be matched by window class and/or window title. It is
-recommended that you match on window classes instead of window titles whenever
-possible because some applications first create their window, and then worry
-about setting the correct title. Firefox with Vimperator comes to mind. The
-window starts up being named Firefox, and only when Vimperator is loaded does
-the title change. As i3 will get the title as soon as the application maps the
+To automatically make a specific window show up on a specific workspace, you
+can use an *assignment*. You can match windows by using any criteria,
+see <<command_criteria>>. It is recommended that you match on window classes
+(and instances, when appropriate) instead of window titles whenever possible
+because some applications first create their window, and then worry about
+setting the correct title. Firefox with Vimperator comes to mind. The window
+starts up being named Firefox, and only when Vimperator is loaded does the
+title change. As i3 will get the title as soon as the application maps the
window (mapping means actually displaying it on the screen), you’d need to have
to match on 'Firefox' in this case.
-You can prefix or suffix workspaces with a `~` to specify that matching clients
-should be put into floating mode. If you specify only a `~`, the client will
-not be put onto any workspace, but will be set floating on the current one.
-
*Syntax*:
------------------------------------------------------------
-assign ["]window class[/window title]["] [→] [workspace]
+assign <criteria> [→] workspace
------------------------------------------------------------
*Examples*:
----------------------
-assign urxvt 2
-assign urxvt → 2
-assign urxvt → work
-assign "urxvt" → 2
-assign "urxvt/VIM" → 3
-assign "gecko" → 4
+# Assign URxvt terminals to workspace 2
+assign [class="URxvt"] 2
+
+# Same thing, but more precise (exact match instead of substring)
+assign [class="^URxvt$"] 2
+
+# Same thing, but with a beautiful arrow :)
+assign [class="^URxvt$"] → 2
+
+# Assignment to a named workspace
+assign [class="^URxvt$"] → work
+
+# Start urxvt -name irssi
+assign [class="^URxvt$" instance="^irssi$"] → 3
----------------------
Note that the arrow is not required, it just looks good :-). If you decide to
use it, it has to be a UTF-8 encoded arrow, not `->` or something like that.
+To get the class and instance, you can use +xprop+. After clicking on the
+window, you will see the following output:
+
+*xwininfo*:
+-----------------------------------
+WM_CLASS(STRING) = "irssi", "URxvt"
+-----------------------------------
+
+The first part of the WM_CLASS is the instance ("irssi" in this example), the
+second part is the class ("URxvt" in this example).
+
+Should you have any problems with assignments, make sure to check the i3
+logfile first (see http://i3wm.org/docs/debugging.html). It includes more
+details about the matching process and the window’s actual class, instance and
+title when starting up.
+
=== Automatically starting applications on i3 startup
By using the +exec+ keyword outside a keybinding, you can configure
force_focus_wrapping yes
------------------------
+=== Forcing Xinerama
+
+As explained in-depth in <http://i3wm.org/docs/multi-monitor.html>, some X11
+video drivers (especially the nVidia binary driver) only provide support for
+Xinerama instead of RandR. In such a situation, i3 must be told to use the
+inferior Xinerama API explicitly and therefore don’t provide support for
+reconfiguring your screens on the fly (they are read only once on startup and
+that’s it).
+
+For people who do cannot modify their +~/.xsession+ to add the
++--force-xinerama+ commandline parameter, a configuration option is provided:
+
+*Syntax*:
+-----------------------
+force_xinerama <yes|no>
+-----------------------
+
+*Example*:
+------------------
+force_xinerama yes
+------------------
+
+Also note that your output names are not descriptive (like +HDMI1+) when using
+Xinerama, instead they are counted up, starting at 0: +xinerama-0+, +xinerama-1+, …
+
== List of commands
Commands are what you bind to specific keypresses. You can also issue commands
*Example*:
------------------------------------
bindsym mod+x [class="Firefox"] kill
+
+# same thing, but case-insensitive
+bindsym mod+x [class="(?i)firefox"] kill
------------------------------------
The criteria which are currently implemented are:
Compares the window class (the second part of WM_CLASS)
instance::
Compares the window instance (the first part of WM_CLASS)
+window_role::
+ Compares the window role (WM_WINDOW_ROLE).
id::
Compares the X11 window ID, which you can get via +xwininfo+ for example.
title::
Compares the i3-internal container ID, which you can get via the IPC
interface. Handy for scripting.
-Note that currently all criteria are compared case-insensitive and do not
-support regular expressions. This is planned to change in the future.
+The criteria +class+, +instance+, +role+, +title+ and +mark+ are actually
+regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for
+information on how to use them.
=== Splitting containers
workspace 1, 3, 4 and 9 and you want to cycle through them with a single key
combination.
+To move a container to another xrandr output such as +LVDS1+ or +VGA1+, you can
+use the +move output+ command followed by the name of the target output. You
+may also use +left+, +right+, +up+, +down+ instead of the xrandr output name to
+move to the the next output in the specified direction.
+
*Examples*:
-------------------------
bindsym mod+1 workspace 1
/*
- * 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.
*
#include "i3-input.h"
+/* IPC format string. %s will be replaced with what the user entered, then
+ * the command will be sent to i3 */
+static char *format;
+
static char *socket_path;
static int sockfd;
static xcb_key_symbols_t *symbols;
static char *glyphs_utf8[512];
static int input_position;
static int font_height;
-static char *command_prefix;
static char *prompt;
static int prompt_len;
static int limit;
*
*/
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;
+ 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;
}
/*
*
*/
static uint8_t *concat_strings(char **glyphs, int max) {
- uint8_t *output = calloc(max+1, 4);
- uint8_t *walk = output;
- for (int c = 0; c < max; c++) {
- printf("at %c\n", glyphs[c][0]);
- /* if the first byte is 0, this has to be UCS2 */
- if (glyphs[c][0] == '\0') {
- memcpy(walk, glyphs[c], 2);
- walk += 2;
- } else {
- strcpy((char*)walk, glyphs[c]);
- walk += strlen(glyphs[c]);
- }
+ uint8_t *output = calloc(max+1, 4);
+ uint8_t *walk = output;
+ for (int c = 0; c < max; c++) {
+ printf("at %c\n", glyphs[c][0]);
+ /* if the first byte is 0, this has to be UCS2 */
+ if (glyphs[c][0] == '\0') {
+ memcpy(walk, glyphs[c], 2);
+ walk += 2;
+ } else {
+ strcpy((char*)walk, glyphs[c]);
+ walk += strlen(glyphs[c]);
}
- printf("output = %s\n", output);
- return output;
+ }
+ printf("output = %s\n", output);
+ return output;
}
/*
*
*/
static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) {
- printf("expose!\n");
-
- /* re-draw the background */
- xcb_rectangle_t border = {0, 0, 500, font_height + 8}, inner = {2, 2, 496, font_height + 8 - 4};
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
- xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
- xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
-
- /* restore font color */
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
- uint8_t *con = concat_strings(glyphs_ucs, input_position);
- char *full_text = (char*)con;
- if (prompt != NULL) {
- full_text = malloc((prompt_len + input_position) * 2 + 1);
- if (full_text == NULL)
- err(EXIT_FAILURE, "malloc() failed\n");
- memcpy(full_text, prompt, prompt_len * 2);
- memcpy(full_text + (prompt_len * 2), con, input_position * 2);
- }
- xcb_image_text_16(conn, input_position + prompt_len, pixmap, pixmap_gc, 4 /* X */,
- font_height + 2 /* Y = baseline of font */, (xcb_char2b_t*)full_text);
-
- /* Copy the contents of the pixmap to the real window */
- xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font_height + 8);
- xcb_flush(conn);
- free(con);
- if (prompt != NULL)
- free(full_text);
-
- return 1;
+ printf("expose!\n");
+
+ /* re-draw the background */
+ xcb_rectangle_t border = {0, 0, 500, font_height + 8}, inner = {2, 2, 496, font_height + 8 - 4};
+ xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
+ xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
+ xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
+ xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
+
+ /* restore font color */
+ xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
+ uint8_t *con = concat_strings(glyphs_ucs, input_position);
+ char *full_text = (char*)con;
+ if (prompt != NULL) {
+ full_text = malloc((prompt_len + input_position) * 2 + 1);
+ if (full_text == NULL)
+ err(EXIT_FAILURE, "malloc() failed\n");
+ memcpy(full_text, prompt, prompt_len * 2);
+ memcpy(full_text + (prompt_len * 2), con, input_position * 2);
+ }
+ xcb_image_text_16(conn, input_position + prompt_len, pixmap, pixmap_gc, 4 /* X */,
+ font_height + 2 /* Y = baseline of font */, (xcb_char2b_t*)full_text);
+
+ /* Copy the contents of the pixmap to the real window */
+ xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font_height + 8);
+ xcb_flush(conn);
+ free(con);
+ if (prompt != NULL)
+ free(full_text);
+
+ return 1;
}
/*
*
*/
static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) {
- printf("releasing %d, state raw = %d\n", event->detail, event->state);
+ printf("releasing %d, state raw = %d\n", event->detail, event->state);
- /* fix state */
- event->state &= ~numlockmask;
+ /* fix state */
+ event->state &= ~numlockmask;
- xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
- if (sym == XK_Mode_switch) {
- printf("Mode switch disabled\n");
- modeswitch_active = false;
- }
+ xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
+ if (sym == XK_Mode_switch) {
+ printf("Mode switch disabled\n");
+ modeswitch_active = false;
+ }
- return 1;
+ return 1;
}
static void finish_input() {
- uint8_t *command = concat_strings(glyphs_utf8, input_position);
- char *full_command = (char*)command;
- /* prefix the command if a prefix was specified on commandline */
- if (command_prefix != NULL) {
- if (asprintf(&full_command, "%s%s", command_prefix, command) == -1)
- err(EXIT_FAILURE, "asprintf() failed\n");
+ char *command = (char*)concat_strings(glyphs_utf8, input_position);
+
+ /* count the occurences of %s in the string */
+ int c;
+ int len = strlen(format);
+ int cnt = 0;
+ for (c = 0; c < (len-1); c++)
+ if (format[c] == '%' && format[c+1] == 's')
+ cnt++;
+ printf("occurences = %d\n", cnt);
+
+ /* allocate space for the output */
+ int inputlen = strlen(command);
+ char *full = calloc(1,
+ strlen(format) - (2 * cnt) /* format without all %s */
+ + (inputlen * cnt) /* replaced %s */
+ + 1); /* trailing NUL */
+ char *dest = full;
+ for (c = 0; c < len; c++) {
+ /* if this is not % or it is % but without a following 's',
+ * just copy the character */
+ if (format[c] != '%' || (c == (len-1)) || format[c+1] != 's')
+ *(dest++) = format[c];
+ else {
+ strncat(dest, command, inputlen);
+ dest += inputlen;
+ /* skip the following 's' of '%s' */
+ c++;
}
- printf("command = %s\n", full_command);
+ }
+
+ /* prefix the command if a prefix was specified on commandline */
+ printf("command = %s\n", full);
- ipc_send_message(sockfd, strlen(full_command), 0, (uint8_t*)full_command);
+ ipc_send_message(sockfd, strlen(full), 0, (uint8_t*)full);
#if 0
- free(command);
- return 1;
+ free(command);
+ return 1;
#endif
- exit(0);
+ exit(0);
}
/*
*
*/
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);
-
- /* fix state */
- if (modeswitch_active)
- event->state |= modeswitchmask;
-
- /* Apparantly, after activating numlock once, the numlock modifier
- * stays turned on (use xev(1) to verify). So, to resolve useful
- * keysyms, we remove the numlock flag from the event state */
- event->state &= ~numlockmask;
-
- xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
- if (sym == XK_Mode_switch) {
- printf("Mode switch enabled\n");
- modeswitch_active = true;
- return 1;
- }
-
- if (sym == XK_Return)
- finish_input();
+ printf("Keypress %d, state raw = %d\n", event->detail, event->state);
- if (sym == XK_BackSpace) {
- if (input_position == 0)
- return 1;
+ /* fix state */
+ if (modeswitch_active)
+ event->state |= modeswitchmask;
- input_position--;
- free(glyphs_ucs[input_position]);
- free(glyphs_utf8[input_position]);
+ /* Apparantly, after activating numlock once, the numlock modifier
+ * stays turned on (use xev(1) to verify). So, to resolve useful
+ * keysyms, we remove the numlock flag from the event state */
+ event->state &= ~numlockmask;
- handle_expose(NULL, conn, NULL);
- return 1;
- }
- if (sym == XK_Escape) {
- exit(0);
- }
+ xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
+ if (sym == XK_Mode_switch) {
+ printf("Mode switch enabled\n");
+ modeswitch_active = true;
+ return 1;
+ }
- /* TODO: handle all of these? */
- printf("is_keypad_key = %d\n", xcb_is_keypad_key(sym));
- printf("is_private_keypad_key = %d\n", xcb_is_private_keypad_key(sym));
- printf("xcb_is_cursor_key = %d\n", xcb_is_cursor_key(sym));
- printf("xcb_is_pf_key = %d\n", xcb_is_pf_key(sym));
- printf("xcb_is_function_key = %d\n", xcb_is_function_key(sym));
- printf("xcb_is_misc_function_key = %d\n", xcb_is_misc_function_key(sym));
- printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym));
-
- if (xcb_is_modifier_key(sym) || xcb_is_cursor_key(sym))
- return 1;
-
- printf("sym = %c (%d)\n", sym, sym);
-
- /* convert the keysym to UCS */
- uint16_t ucs = keysym2ucs(sym);
- if ((int16_t)ucs == -1) {
- fprintf(stderr, "Keysym could not be converted to UCS, skipping\n");
- return 1;
- }
+ if (sym == XK_Return)
+ finish_input();
- /* store the UCS into a string */
- uint8_t inp[3] = {(ucs & 0xFF00) >> 8, (ucs & 0xFF), 0};
+ if (sym == XK_BackSpace) {
+ if (input_position == 0)
+ return 1;
- printf("inp[0] = %02x, inp[1] = %02x, inp[2] = %02x\n", inp[0], inp[1], inp[2]);
- /* convert it to UTF-8 */
- char *out = convert_ucs_to_utf8((char*)inp);
- printf("converted to %s\n", out);
+ input_position--;
+ free(glyphs_ucs[input_position]);
+ free(glyphs_utf8[input_position]);
- glyphs_ucs[input_position] = malloc(3 * sizeof(uint8_t));
- if (glyphs_ucs[input_position] == NULL)
- err(EXIT_FAILURE, "malloc() failed\n");
- memcpy(glyphs_ucs[input_position], inp, 3);
- glyphs_utf8[input_position] = strdup(out);
- input_position++;
+ handle_expose(NULL, conn, NULL);
+ return 1;
+ }
+ if (sym == XK_Escape) {
+ exit(0);
+ }
+
+ /* TODO: handle all of these? */
+ printf("is_keypad_key = %d\n", xcb_is_keypad_key(sym));
+ printf("is_private_keypad_key = %d\n", xcb_is_private_keypad_key(sym));
+ printf("xcb_is_cursor_key = %d\n", xcb_is_cursor_key(sym));
+ printf("xcb_is_pf_key = %d\n", xcb_is_pf_key(sym));
+ printf("xcb_is_function_key = %d\n", xcb_is_function_key(sym));
+ printf("xcb_is_misc_function_key = %d\n", xcb_is_misc_function_key(sym));
+ printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym));
+
+ if (xcb_is_modifier_key(sym) || xcb_is_cursor_key(sym))
+ return 1;
- if (input_position == limit)
- finish_input();
+ printf("sym = %c (%d)\n", sym, sym);
- handle_expose(NULL, conn, NULL);
+ /* convert the keysym to UCS */
+ uint16_t ucs = keysym2ucs(sym);
+ if ((int16_t)ucs == -1) {
+ fprintf(stderr, "Keysym could not be converted to UCS, skipping\n");
return 1;
+ }
+
+ /* store the UCS into a string */
+ uint8_t inp[3] = {(ucs & 0xFF00) >> 8, (ucs & 0xFF), 0};
+
+ printf("inp[0] = %02x, inp[1] = %02x, inp[2] = %02x\n", inp[0], inp[1], inp[2]);
+ /* convert it to UTF-8 */
+ char *out = convert_ucs_to_utf8((char*)inp);
+ printf("converted to %s\n", out);
+
+ glyphs_ucs[input_position] = malloc(3 * sizeof(uint8_t));
+ if (glyphs_ucs[input_position] == NULL)
+ err(EXIT_FAILURE, "malloc() failed\n");
+ memcpy(glyphs_ucs[input_position], inp, 3);
+ glyphs_utf8[input_position] = strdup(out);
+ input_position++;
+
+ if (input_position == limit)
+ finish_input();
+
+ handle_expose(NULL, conn, NULL);
+ return 1;
}
int main(int argc, char *argv[]) {
- socket_path = getenv("I3SOCK");
- 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[] = {
- {"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:p:P:f:l: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-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':
- printf("i3-input " I3_VERSION);
- printf("i3-input [-s <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-f <font>] [-v]\n");
- return 0;
- }
+ format = strdup("%s");
+ socket_path = getenv("I3SOCK");
+ 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[] = {
+ {"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'},
+ {"format", required_argument, 0, 'F'},
+ {"font", required_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0}
+ };
+
+ char *options_string = "s:p:P:f:l:F: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-input " I3_VERSION);
+ return 0;
+ case 'p':
+ /* This option is deprecated, but will still work in i3 v4.1, 4.2 and 4.3 */
+ fprintf(stderr, "i3-input: WARNING: the -p option is DEPRECATED in favor of the -F (format) option\n");
+ FREE(format);
+ asprintf(&format, "%s%%s", 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 'F':
+ FREE(format);
+ format = strdup(optarg);
+ break;
+ case 'h':
+ printf("i3-input " I3_VERSION "\n");
+ printf("i3-input [-s <socket>] [-F <format>] [-l <limit>] [-P <prompt>] [-f <font>] [-v]\n");
+ printf("\n");
+ printf("Example:\n");
+ printf(" i3-input -F 'workspace \"%%s\"' -P 'Switch to workspace: '\n");
+ return 0;
}
+ }
- if (socket_path == NULL)
- socket_path = socket_path_from_x11();
+ printf("using format \"%s\"\n", format);
- if (socket_path == NULL)
- socket_path = "/tmp/i3-ipc.sock";
+ if (socket_path == NULL)
+ socket_path = socket_path_from_x11();
- sockfd = connect_ipc(socket_path);
+ if (socket_path == NULL)
+ socket_path = "/tmp/i3-ipc.sock";
- if (prompt != NULL)
- prompt = convert_utf8_to_ucs2(prompt, &prompt_len);
+ sockfd = connect_ipc(socket_path);
- int screens;
- xcb_connection_t *conn = xcb_connect(NULL, &screens);
- if (xcb_connection_has_error(conn))
- die("Cannot open display\n");
+ if (prompt != NULL)
+ prompt = convert_utf8_to_ucs2(prompt, &prompt_len);
- xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
- root = root_screen->root;
+ int screens;
+ xcb_connection_t *conn = xcb_connect(NULL, &screens);
+ if (xcb_connection_has_error(conn))
+ die("Cannot open display\n");
- modeswitchmask = get_mod_mask(conn, XK_Mode_switch);
- numlockmask = get_mod_mask(conn, XK_Num_Lock);
- symbols = xcb_key_symbols_alloc(conn);
+ 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);
+ modeswitchmask = get_mod_mask(conn, XK_Mode_switch);
+ numlockmask = get_mod_mask(conn, XK_Num_Lock);
+ symbols = xcb_key_symbols_alloc(conn);
- /* Open an input window */
- win = open_input_window(conn, 500, font_height + 8);
+ uint32_t font_id = get_font_id(conn, pattern, &font_height);
- /* 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);
+ /* Open an input window */
+ win = open_input_window(conn, 500, font_height + 8);
- /* Set input focus (we have override_redirect=1, so the wm will not do
- * this for us) */
- xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
+ /* Create 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);
+ /* Set input focus (we have override_redirect=1, so the wm will not do
+ * this for us) */
+ xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
- /* Grab the keyboard to get all input */
- xcb_flush(conn);
+ /* Create graphics context */
+ xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
- /* 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;
+ /* Grab the keyboard to get all input */
+ xcb_flush(conn);
- 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);
- }
+ /* 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;
- if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
- fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
- exit(-1);
- }
+ 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);
+ }
- xcb_flush(conn);
+ if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
+ fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
+ exit(-1);
+ }
- 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;
- }
+ xcb_flush(conn);
- /* Strip off the highest bit (set if the event is generated) */
- int type = (event->response_type & 0x7F);
+ 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;
+ }
- switch (type) {
- case XCB_KEY_PRESS:
- handle_key_press(NULL, conn, (xcb_key_press_event_t*)event);
- break;
+ /* Strip off the highest bit (set if the event is generated) */
+ int type = (event->response_type & 0x7F);
- case XCB_KEY_RELEASE:
- handle_key_release(NULL, conn, (xcb_key_release_event_t*)event);
- break;
+ switch (type) {
+ case XCB_KEY_PRESS:
+ handle_key_press(NULL, conn, (xcb_key_press_event_t*)event);
+ break;
- case XCB_EXPOSE:
- handle_expose(NULL, conn, (xcb_expose_event_t*)event);
- break;
- }
+ case XCB_KEY_RELEASE:
+ handle_key_release(NULL, conn, (xcb_key_release_event_t*)event);
+ break;
- free(event);
+ case XCB_EXPOSE:
+ handle_expose(NULL, conn, (xcb_expose_event_t*)event);
+ break;
}
- return 0;
+ free(event);
+ }
+
+ return 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 if (strcasecmp(optarg, "get_marks") == 0)
+ message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS;
else {
printf("Unknown message type\n");
- printf("Known types: command, get_workspaces, get_outputs, get_tree\n");
+ printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks\n");
exit(EXIT_FAILURE);
}
} else if (o == 'q') {
uint32_t reply_length;
uint8_t *reply;
ipc_recv_message(sockfd, message_type, &reply_length, &reply);
- printf("%.*s", reply_length, reply);
+ printf("%.*s\n", reply_length, reply);
free(reply);
close(sockfd);
--- /dev/null
+#!/bin/sh
+# This script tries to exec an editor by trying some known editors if $EDITOR is
+# not set.
+#
+# Distributions/packagers can enhance this script with a
+# distribution-specific mechanism to find the preferred pager.
+which $VISUAL >/dev/null && exec $VISUAL "$@"
+which $EDITOR >/dev/null && exec $EDITOR "$@"
+
+# Hopefully one of these is installed (no flamewars about preference please!):
+which nano >/dev/null && exec nano "$@"
+which vim >/dev/null && exec vim "$@"
+which vi >/dev/null && exec vi "$@"
+which emacs >/dev/null && exec emacs "$@"
--- /dev/null
+#!/bin/sh
+# This script tries to exec a pager by trying some known pagers if $PAGER is
+# not set.
+#
+# Distributions/packagers can enhance this script with a
+# distribution-specific mechanism to find the preferred pager.
+which $PAGER >/dev/null && exec $PAGER "$@"
+
+# Hopefully one of these is installed:
+which most >/dev/null && exec most "$@"
+which less >/dev/null && exec less "$@"
+# we don't use 'more' because it will exit if the file is 'too short'
+
+# If no pager is installed, try an editor
+exec i3-sensible-editor "$@"
--- /dev/null
+#!/bin/sh
+# This script tries to exec a terminal emulator by trying some known terminal
+# emulators.
+#
+# Distributions/packagers should enhance this script with a
+# distribution-specific mechanism to find the preferred terminal emulator. On
+# Debian, there is the x-terminal-emulator symlink for example.
+# Please don't touch the first line, though:
+which $TERMINAL >/dev/null && exec $TERMINAL "$@"
+
+# Hopefully one of these is installed:
+which xterm >/dev/null && exec xterm "$@"
+which urxvt >/dev/null && exec urxvt "$@"
+which rxvt >/dev/null && exec rxvt "$@"
+which roxterm >/dev/null && exec roxterm "$@"
floating_modifier Mod1
# start a terminal
-bindsym Mod1+Return exec urxvt
+bindsym Mod1+Return exec i3-sensible-terminal
# kill focused window
bindsym Mod1+Shift+q kill
floating_modifier $mod
# start a terminal
-bindcode $mod+36 exec urxvt
+bindcode $mod+36 exec i3-sensible-terminal
# kill focused window
bindcode $mod+Shift+24 kill
#ifndef COMMON_H_
#define COMMON_H_
+#include <stdbool.h>
+
typedef struct rect_t rect;
-typedef int bool;
struct ev_loop* main_loop;
char *statusline;
char *statusline_buffer;
struct rect_t {
- int x;
- int y;
- int w;
- int h;
+ int x;
+ int y;
+ int w;
+ int h;
};
#include "queue.h"
#include "outputs.h"
#include "util.h"
#include "workspaces.h"
+#include "trayclients.h"
#include "xcb.h"
#include "ucs2_to_utf8.h"
#include "config.h"
* Start parsing the received json-string
*
*/
-void parse_outputs_json(char* json);
+void parse_outputs_json(char* json);
/*
* Initiate the output-list
*
*/
-void init_outputs();
+void init_outputs();
/*
* Returns the output with the given name
*
*/
-i3_output* get_output_by_name(char* name);
+i3_output* get_output_by_name(char* name);
struct i3_output {
- char* name; /* Name of the output */
- bool active; /* If the output is active */
- int ws; /* The number of the currently visible ws */
- rect rect; /* The rect (relative to the root-win) */
+ char* name; /* Name of the output */
+ bool active; /* If the output is active */
+ int ws; /* The number of the currently visible ws */
+ rect rect; /* The rect (relative to the root-win) */
- xcb_window_t bar; /* The id of the bar of the output */
- xcb_pixmap_t buffer; /* An extra pixmap for double-buffering */
- xcb_gcontext_t bargc; /* The graphical context of the bar */
+ xcb_window_t bar; /* The id of the bar of the output */
+ xcb_pixmap_t buffer; /* An extra pixmap for double-buffering */
+ xcb_gcontext_t bargc; /* The graphical context of the bar */
- struct ws_head *workspaces; /* The workspaces on this output */
+ struct ws_head *workspaces; /* The workspaces on this output */
+ struct tc_head *trayclients; /* The tray clients on this output */
- SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */
+ SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */
};
#endif
--- /dev/null
+/*
+ * i3bar - an xcb-based status- and ws-bar for i3
+ *
+ * © 2010-2011 Axel Wagner and contributors
+ *
+ * See file LICNSE for license information
+ *
+ */
+#ifndef TRAYCLIENT_H_
+#define TRAYCLIENT_H_
+
+#include "common.h"
+
+typedef struct trayclient trayclient;
+
+TAILQ_HEAD(tc_head, trayclient);
+
+struct trayclient {
+ xcb_window_t win; /* The window ID of the tray client */
+ bool mapped; /* Whether this window is mapped */
+ int xe_version; /* The XEMBED version supported by the client */
+
+ TAILQ_ENTRY(trayclient) tailq; /* Pointer for the TAILQ-Macro */
+};
+
+#endif
#include <stdint.h>
//#include "outputs.h"
+#ifdef XCB_COMPAT
+#define XCB_ATOM_CARDINAL CARDINAL
+#endif
+
+#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0
+#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1
+#define SYSTEM_TRAY_REQUEST_DOCK 0
+#define SYSTEM_TRAY_BEGIN_MESSAGE 1
+#define SYSTEM_TRAY_CANCEL_MESSAGE 2
+#define XEMBED_MAPPED (1 << 0)
+#define XEMBED_EMBEDDED_NOTIFY 0
+
struct xcb_color_strings_t {
char *bar_fg;
char *bar_bg;
ATOM_DO(_NET_WM_WINDOW_TYPE_DOCK)
ATOM_DO(_NET_WM_STRUT_PARTIAL)
ATOM_DO(I3_SOCKET_PATH)
+ATOM_DO(MANAGER)
+ATOM_DO(_NET_SYSTEM_TRAY_ORIENTATION)
+ATOM_DO(_NET_SYSTEM_TRAY_VISUAL)
+ATOM_DO(_NET_SYSTEM_TRAY_OPCODE)
+ATOM_DO(_XEMBED_INFO)
+ATOM_DO(_XEMBED)
#undef ATOM_DO
if (rec == buffer_len) {
buffer_len += STDIN_CHUNK_SIZE;
buffer = realloc(buffer, buffer_len);
- }
+ }
}
if (*buffer == '\0') {
FREE(buffer);
* Parse a boolean value (active)
*
*/
-static int outputs_boolean_cb(void *params_, bool val) {
+static int outputs_boolean_cb(void *params_, int val) {
struct outputs_json_params *params = (struct outputs_json_params*) params_;
if (strcmp(params->cur_key, "active")) {
new_output->workspaces = malloc(sizeof(struct ws_head));
TAILQ_INIT(new_output->workspaces);
+ new_output->trayclients = malloc(sizeof(struct tc_head));
+ TAILQ_INIT(new_output->trayclients);
+
params->outputs_walk = new_output;
return 1;
*
*/
char *convert_ucs_to_utf8(char *input) {
- size_t input_size = 2;
- /* UTF-8 may consume up to 4 byte */
- int buffer_size = 8;
+ size_t input_size = 2;
+ /* UTF-8 may consume up to 4 byte */
+ int buffer_size = 8;
- char *buffer = calloc(buffer_size, 1);
+ char *buffer = calloc(buffer_size, 1);
if (buffer == NULL)
err(EXIT_FAILURE, "malloc() failed\n");
- size_t output_size = buffer_size;
- /* We need to use an additional pointer, because iconv() modifies it */
- char *output = buffer;
+ size_t output_size = buffer_size;
+ /* We need to use an additional pointer, because iconv() modifies it */
+ char *output = buffer;
- /* We convert the input into UCS-2 big endian */
+ /* We convert the input into UCS-2 big endian */
if (conversion_descriptor == 0) {
conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
if (conversion_descriptor == 0) {
}
}
- /* Get the conversion descriptor back to original state */
- iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
+ /* Get the conversion descriptor back to original state */
+ iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
- /* Convert our text */
- int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
+ /* Convert our text */
+ int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
if (rc == (size_t)-1) {
perror("Converting to UCS-2 failed");
return NULL;
- }
+ }
- return buffer;
+ return buffer;
}
/*
*
*/
char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
- size_t input_size = strlen(input) + 1;
- /* UCS-2 consumes exactly two bytes for each glyph */
- int buffer_size = input_size * 2;
+ size_t input_size = strlen(input) + 1;
+ /* UCS-2 consumes exactly two bytes for each glyph */
+ int buffer_size = input_size * 2;
- char *buffer = malloc(buffer_size);
+ char *buffer = malloc(buffer_size);
if (buffer == NULL)
err(EXIT_FAILURE, "malloc() failed\n");
- size_t output_size = buffer_size;
- /* We need to use an additional pointer, because iconv() modifies it */
- char *output = buffer;
+ size_t output_size = buffer_size;
+ /* We need to use an additional pointer, because iconv() modifies it */
+ char *output = buffer;
- /* We convert the input into UCS-2 big endian */
+ /* We convert the input into UCS-2 big endian */
if (conversion_descriptor2 == 0) {
conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8");
if (conversion_descriptor2 == 0) {
}
}
- /* Get the conversion descriptor back to original state */
- iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
+ /* Get the conversion descriptor back to original state */
+ iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
- /* Convert our text */
- int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
+ /* Convert our text */
+ int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
if (rc == (size_t)-1) {
perror("Converting to UCS-2 failed");
if (real_strlen != NULL)
- *real_strlen = 0;
+ *real_strlen = 0;
return NULL;
- }
+ }
if (real_strlen != NULL)
- *real_strlen = ((buffer_size - output_size) / 2) - 1;
+ *real_strlen = ((buffer_size - output_size) / 2) - 1;
- return buffer;
+ return buffer;
}
* Parse a boolean value (visible, focused, urgent)
*
*/
-static int workspaces_boolean_cb(void *params_, bool val) {
+static int workspaces_boolean_cb(void *params_, int val) {
struct workspaces_json_params *params = (struct workspaces_json_params*) params_;
if (!strcmp(params->cur_key, "visible")) {
/* Variables, that are the same for all functions at all times */
xcb_connection_t *xcb_connection;
+int screen;
xcb_screen_t *xcb_screen;
xcb_window_t xcb_root;
xcb_font_t xcb_font;
i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, buffer);
}
+/*
+ * Configures the x coordinate of all trayclients. To be called after adding a
+ * new tray client or removing an old one.
+ *
+ */
+static void configure_trayclients() {
+ trayclient *trayclient;
+ i3_output *output;
+ SLIST_FOREACH(output, outputs, slist) {
+ if (!output->active)
+ continue;
+
+ int clients = 0;
+ TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) {
+ clients++;
+
+ DLOG("Configuring tray window %08x to x=%d\n",
+ trayclient->win, output->rect.w - (clients * (font_height + 2)));
+ uint32_t x = output->rect.w - (clients * (font_height + 2));
+ xcb_configure_window(xcb_connection,
+ trayclient->win,
+ XCB_CONFIG_WINDOW_X,
+ &x);
+ }
+ }
+}
+
+/*
+ * Handles ClientMessages (messages sent from another client directly to us).
+ *
+ * At the moment, only the tray window will receive client messages. All
+ * supported client messages currently are _NET_SYSTEM_TRAY_OPCODE.
+ *
+ */
+static void handle_client_message(xcb_client_message_event_t* event) {
+ if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
+ event->format == 32) {
+ DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
+ /* event->data.data32[0] is the timestamp */
+ uint32_t op = event->data.data32[1];
+ uint32_t mask;
+ uint32_t values[2];
+ if (op == SYSTEM_TRAY_REQUEST_DOCK) {
+ xcb_window_t client = event->data.data32[2];
+
+ /* Listen for PropertyNotify events to get the most recent value of
+ * the XEMBED_MAPPED atom, also listen for UnmapNotify events */
+ mask = XCB_CW_EVENT_MASK;
+ values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
+ XCB_EVENT_MASK_STRUCTURE_NOTIFY;
+ xcb_change_window_attributes(xcb_connection,
+ client,
+ mask,
+ values);
+
+ /* Request the _XEMBED_INFO property. The XEMBED specification
+ * (which is referred by the tray specification) says this *has* to
+ * be set, but VLC does not set it… */
+ bool map_it = true;
+ int xe_version = 1;
+ xcb_get_property_cookie_t xembedc;
+ xembedc = xcb_get_property_unchecked(xcb_connection,
+ 0,
+ client,
+ atoms[_XEMBED_INFO],
+ XCB_GET_PROPERTY_TYPE_ANY,
+ 0,
+ 2 * 32);
+
+ xcb_get_property_reply_t *xembedr = xcb_get_property_reply(xcb_connection,
+ xembedc,
+ NULL);
+ if (xembedr != NULL && xembedr->length != 0) {
+ DLOG("xembed format = %d, len = %d\n", xembedr->format, xembedr->length);
+ uint32_t *xembed = xcb_get_property_value(xembedr);
+ DLOG("xembed version = %d\n", xembed[0]);
+ DLOG("xembed flags = %d\n", xembed[1]);
+ map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
+ xe_version = xembed[0];
+ if (xe_version > 1)
+ xe_version = 1;
+ free(xembedr);
+ } else {
+ ELOG("Window %08x violates the XEMBED protocol, _XEMBED_INFO not set\n", client);
+ }
+
+ DLOG("X window %08x requested docking\n", client);
+ i3_output *walk, *output = NULL;
+ SLIST_FOREACH(walk, outputs, slist) {
+ if (!walk->active)
+ continue;
+ DLOG("using output %s\n", walk->name);
+ output = walk;
+ }
+ if (output == NULL) {
+ ELOG("No output found\n");
+ return;
+ }
+ xcb_reparent_window(xcb_connection,
+ client,
+ output->bar,
+ output->rect.w - font_height - 2,
+ 2);
+ /* We reconfigure the window to use a reasonable size. The systray
+ * specification explicitly says:
+ * Tray icons may be assigned any size by the system tray, and
+ * should do their best to cope with any size effectively
+ */
+ mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
+ values[0] = font_height;
+ values[1] = font_height;
+ xcb_configure_window(xcb_connection,
+ client,
+ mask,
+ values);
+
+ /* send the XEMBED_EMBEDDED_NOTIFY message */
+ void *event = calloc(32, 1);
+ xcb_client_message_event_t *ev = event;
+ ev->response_type = XCB_CLIENT_MESSAGE;
+ ev->window = client;
+ ev->type = atoms[_XEMBED];
+ ev->format = 32;
+ ev->data.data32[0] = XCB_CURRENT_TIME;
+ ev->data.data32[1] = atoms[XEMBED_EMBEDDED_NOTIFY];
+ ev->data.data32[2] = output->bar;
+ ev->data.data32[3] = xe_version;
+ xcb_send_event(xcb_connection,
+ 0,
+ client,
+ XCB_EVENT_MASK_NO_EVENT,
+ (char*)ev);
+ free(event);
+
+ if (map_it) {
+ DLOG("Mapping dock client\n");
+ xcb_map_window(xcb_connection, client);
+ } else {
+ DLOG("Not mapping dock client yet\n");
+ }
+ trayclient *tc = malloc(sizeof(trayclient));
+ tc->win = client;
+ tc->mapped = map_it;
+ tc->xe_version = xe_version;
+ TAILQ_INSERT_TAIL(output->trayclients, tc, tailq);
+
+ /* Trigger an update to copy the statusline text to the appropriate
+ * position */
+ configure_trayclients();
+ draw_bars();
+ }
+ }
+}
+
+/*
+ * Handles UnmapNotify events. These events happen when a tray window unmaps
+ * itself. We then update our data structure
+ *
+ */
+static void handle_unmap_notify(xcb_unmap_notify_event_t* event) {
+ DLOG("UnmapNotify for window = %08x, event = %08x\n", event->window, event->event);
+
+ i3_output *walk;
+ SLIST_FOREACH(walk, outputs, slist) {
+ if (!walk->active)
+ continue;
+ DLOG("checking output %s\n", walk->name);
+ trayclient *trayclient;
+ TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
+ if (trayclient->win != event->window)
+ continue;
+
+ DLOG("Removing tray client with window ID %08x\n", event->window);
+ TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
+
+ /* Trigger an update, we now have more space for the statusline */
+ configure_trayclients();
+ draw_bars();
+ return;
+ }
+ }
+}
+
+/*
+ * Handle PropertyNotify messages. Currently only the _XEMBED_INFO property is
+ * handled, which tells us whether a dock client should be mapped or unmapped.
+ *
+ */
+static void handle_property_notify(xcb_property_notify_event_t *event) {
+ DLOG("PropertyNotify\n");
+ if (event->atom == atoms[_XEMBED_INFO] &&
+ event->state == XCB_PROPERTY_NEW_VALUE) {
+ DLOG("xembed_info updated\n");
+ trayclient *trayclient = NULL, *walk;
+ i3_output *o_walk;
+ SLIST_FOREACH(o_walk, outputs, slist) {
+ if (!o_walk->active)
+ continue;
+
+ TAILQ_FOREACH(walk, o_walk->trayclients, tailq) {
+ if (walk->win != event->window)
+ continue;
+ trayclient = walk;
+ break;
+ }
+
+ if (trayclient)
+ break;
+ }
+ if (!trayclient) {
+ ELOG("PropertyNotify received for unknown window %08x\n",
+ event->window);
+ return;
+ }
+ xcb_get_property_cookie_t xembedc;
+ xembedc = xcb_get_property_unchecked(xcb_connection,
+ 0,
+ trayclient->win,
+ atoms[_XEMBED_INFO],
+ XCB_GET_PROPERTY_TYPE_ANY,
+ 0,
+ 2 * 32);
+
+ xcb_get_property_reply_t *xembedr = xcb_get_property_reply(xcb_connection,
+ xembedc,
+ NULL);
+ if (xembedr == NULL || xembedr->length == 0)
+ return;
+
+ DLOG("xembed format = %d, len = %d\n", xembedr->format, xembedr->length);
+ uint32_t *xembed = xcb_get_property_value(xembedr);
+ DLOG("xembed version = %d\n", xembed[0]);
+ DLOG("xembed flags = %d\n", xembed[1]);
+ bool map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
+ DLOG("map-state now %d\n", map_it);
+ if (trayclient->mapped && !map_it) {
+ /* need to unmap the window */
+ xcb_unmap_window(xcb_connection, trayclient->win);
+ trayclient->mapped = map_it;
+ draw_bars();
+ } else if (!trayclient->mapped && map_it) {
+ /* need to map the window */
+ xcb_map_window(xcb_connection, trayclient->win);
+ trayclient->mapped = map_it;
+ draw_bars();
+ }
+ free(xembedr);
+ }
+}
+
/*
* This function is called immediately before the main loop locks. We flush xcb
* then (and only then)
/* Button-press-events are mouse-buttons clicked on one of our bars */
handle_button((xcb_button_press_event_t*) event);
break;
+ case XCB_CLIENT_MESSAGE:
+ /* Client messages are used for client-to-client communication, for
+ * example system tray widgets talk to us directly via client messages. */
+ handle_client_message((xcb_client_message_event_t*) event);
+ break;
+ case XCB_UNMAP_NOTIFY:
+ /* UnmapNotifies are received when a tray window unmaps itself */
+ handle_unmap_notify((xcb_unmap_notify_event_t*) event);
+ break;
+ case XCB_PROPERTY_NOTIFY:
+ /* PropertyNotify */
+ handle_property_notify((xcb_property_notify_event_t*) event);
+ break;
}
FREE(event);
}
*/
char *init_xcb(char *fontname) {
/* FIXME: xcb_connect leaks Memory */
- xcb_connection = xcb_connect(NULL, NULL);
+ xcb_connection = xcb_connect(NULL, &screen);
if (xcb_connection_has_error(xcb_connection)) {
ELOG("Cannot open display\n");
exit(EXIT_FAILURE);
return path;
}
+/*
+ * Initializes tray support by requesting the appropriate _NET_SYSTEM_TRAY atom
+ * for the X11 display we are running on, then acquiring the selection for this
+ * atom. Afterwards, tray clients will send ClientMessages to our window.
+ *
+ */
+void init_tray() {
+ /* request the tray manager atom for the X11 display we are running on */
+ char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11];
+ snprintf(atomname, strlen("_NET_SYSTEM_TRAY_S") + 11, "_NET_SYSTEM_TRAY_S%d", screen);
+ xcb_intern_atom_cookie_t tray_cookie;
+ xcb_intern_atom_reply_t *tray_reply;
+ tray_cookie = xcb_intern_atom(xcb_connection, 0, strlen(atomname), atomname);
+
+ /* tray support: we need a window to own the selection */
+ xcb_window_t selwin = xcb_generate_id(xcb_connection);
+ uint32_t selmask = XCB_CW_OVERRIDE_REDIRECT;
+ uint32_t selval[] = { 1 };
+ xcb_create_window(xcb_connection,
+ xcb_screen->root_depth,
+ selwin,
+ xcb_root,
+ -1, -1,
+ 1, 1,
+ 1,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ xcb_screen->root_visual,
+ selmask,
+ selval);
+
+ uint32_t orientation = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
+ /* set the atoms */
+ xcb_change_property(xcb_connection,
+ XCB_PROP_MODE_REPLACE,
+ selwin,
+ atoms[_NET_SYSTEM_TRAY_ORIENTATION],
+ XCB_ATOM_CARDINAL,
+ 32,
+ 1,
+ &orientation);
+
+ if (!(tray_reply = xcb_intern_atom_reply(xcb_connection, tray_cookie, NULL))) {
+ ELOG("Could not get atom %s\n", atomname);
+ exit(EXIT_FAILURE);
+ }
+
+ xcb_set_selection_owner(xcb_connection,
+ selwin,
+ tray_reply->atom,
+ XCB_CURRENT_TIME);
+
+ /* Verify that we have the selection */
+ xcb_get_selection_owner_cookie_t selcookie;
+ xcb_get_selection_owner_reply_t *selreply;
+
+ selcookie = xcb_get_selection_owner(xcb_connection, tray_reply->atom);
+ if (!(selreply = xcb_get_selection_owner_reply(xcb_connection, selcookie, NULL))) {
+ ELOG("Could not get selection owner for %s\n", atomname);
+ exit(EXIT_FAILURE);
+ }
+
+ if (selreply->owner != selwin) {
+ ELOG("Could not set the %s selection. " \
+ "Maybe another tray is already running?\n", atomname);
+ /* NOTE that this error is not fatal. We just can’t provide tray
+ * functionality */
+ free(selreply);
+ return;
+ }
+
+ /* Inform clients waiting for a new _NET_SYSTEM_TRAY that we are here */
+ void *event = calloc(32, 1);
+ xcb_client_message_event_t *ev = event;
+ ev->response_type = XCB_CLIENT_MESSAGE;
+ ev->window = xcb_root;
+ ev->type = atoms[MANAGER];
+ ev->format = 32;
+ ev->data.data32[0] = XCB_CURRENT_TIME;
+ ev->data.data32[1] = tray_reply->atom;
+ ev->data.data32[2] = selwin;
+ xcb_send_event(xcb_connection,
+ 0,
+ xcb_root,
+ XCB_EVENT_MASK_STRUCTURE_NOTIFY,
+ (char*)ev);
+ free(event);
+ free(tray_reply);
+}
+
/*
* Cleanup the xcb-stuff.
* Called once, before the program terminates.
*/
void clean_xcb() {
i3_output *o_walk;
+ trayclient *trayclient;
free_workspaces();
SLIST_FOREACH(o_walk, outputs, slist) {
+ TAILQ_FOREACH(trayclient, o_walk->trayclients, tailq) {
+ /* Unmap, then reparent (to root) the tray client windows */
+ xcb_unmap_window(xcb_connection, trayclient->win);
+ xcb_reparent_window(xcb_connection,
+ trayclient->win,
+ xcb_root,
+ 0,
+ 0);
+ }
destroy_window(o_walk);
+ FREE(o_walk->trayclients);
FREE(o_walk->workspaces);
FREE(o_walk->name);
}
FREE_SLIST(outputs, i3_output);
FREE(outputs);
+ xcb_flush(xcb_connection);
xcb_disconnect(xcb_connection);
ev_check_stop(main_loop, xcb_chk);
if (walk->bar == XCB_NONE) {
DLOG("Creating Window for output %s\n", walk->name);
+ /* TODO: only call init_tray() if the tray is configured for this output */
+ init_tray();
+
walk->bar = xcb_generate_id(xcb_connection);
walk->buffer = xcb_generate_id(xcb_connection);
mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
/* Luckily we already prepared a seperate pixmap containing the rendered
* statusline, we just have to copy the relevant parts to the relevant
* position */
+ trayclient *trayclient;
+ int traypx = 0;
+ TAILQ_FOREACH(trayclient, outputs_walk->trayclients, tailq) {
+ if (!trayclient->mapped)
+ continue;
+ /* We assume the tray icons are quadratic (we use the font
+ * *height* as *width* of the icons) because we configured them
+ * like this. */
+ traypx += font_height;
+ }
+ /* Add 2px of padding if there are any tray icons */
+ if (traypx > 0)
+ traypx += 2;
xcb_copy_area(xcb_connection,
statusline_pm,
outputs_walk->buffer,
outputs_walk->bargc,
MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0,
- MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - 4)), 3,
- MIN(outputs_walk->rect.w - 4, statusline_width), font_height);
+ MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3,
+ MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font_height);
}
if (config.disable_ws) {
#include "output.h"
#include "ewmh.h"
#include "assignments.h"
+#include "regex.h"
#endif
xmacro(WM_STATE)
xmacro(WM_CLIENT_LEADER)
xmacro(WM_TAKE_FOCUS)
+xmacro(WM_WINDOW_ROLE)
xmacro(I3_SOCKET_PATH)
xmacro(I3_CONFIG_PATH)
+xmacro(I3_SYNC)
* more often. */
bool force_focus_wrapping;
+ /** By default, use the RandR API for multi-monitor setups.
+ * Unfortunately, the nVidia binary graphics driver doesn't support
+ * this API. Instead, it only support the less powerful Xinerama API,
+ * which can be enabled by this option.
+ *
+ * Note: this option takes only effect on the initial startup (eg.
+ * reconfiguration is not possible). On startup, the list of screens
+ * is fetched once and never updated. */
+ bool force_xinerama;
+
/** The default border style for new windows. */
border_style_t default_border;
+ /** The default border style for new floating windows. */
+ border_style_t default_floating_border;
+
/** The modifier which needs to be pressed in combination with your mouse
* buttons to do things with floating windows (move, resize) */
uint32_t floating_modifier;
#include <xcb/randr.h>
#include <xcb/xcb_atom.h>
#include <stdbool.h>
+#include <pcre.h>
#ifndef _DATA_H
#define _DATA_H
SLIST_ENTRY(Ignore_Event) ignore_events;
};
+/**
+ * Regular expression wrapper. It contains the pattern itself as a string (like
+ * ^foo[0-9]$) as well as a pointer to the compiled PCRE expression and the
+ * pcre_extra data returned by pcre_study().
+ *
+ * This makes it easier to have a useful logfile, including the matching or
+ * non-matching pattern.
+ *
+ */
+struct regex {
+ char *pattern;
+ pcre *regex;
+ pcre_extra *extra;
+};
+
/******************************************************************************
* Major types
*****************************************************************************/
* application supports _NET_WM_NAME, in COMPOUND_TEXT otherwise). */
char *name_x;
+ /** The WM_WINDOW_ROLE of this window (for example, the pidgin buddy window
+ * sets "buddy list"). Useful to match specific windows in assignments or
+ * for_window. */
+ char *role;
+
/** Flag to force re-rendering the decoration upon changes */
bool name_x_changed;
};
struct Match {
- char *title;
- int title_len;
- char *application;
- char *class;
- char *instance;
- char *mark;
+ struct regex *title;
+ struct regex *application;
+ struct regex *class;
+ struct regex *instance;
+ struct regex *mark;
+ struct regex *role;
enum {
M_DONTCHECK = -1,
M_NODOCK = 0,
/** Requests the tree layout from i3 */
#define I3_IPC_MESSAGE_TYPE_GET_TREE 4
+/** Request the current defined marks from i3 */
+#define I3_IPC_MESSAGE_TYPE_GET_MARKS 5
/*
* Messages from i3 to clients
/** Tree reply type */
#define I3_IPC_REPLY_TYPE_TREE 4
+/** Marks reply type*/
+#define I3_IPC_REPLY_TYPE_MARKS 5
/*
* Events from i3 to clients. Events have the first bit set high.
*/
bool match_matches_window(Match *match, i3Window *window);
+/**
+ * Frees the given match. It must not be used afterwards!
+ *
+ */
+void match_free(Match *match);
+
#endif
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ */
+#ifndef _REGEX_H
+#define _REGEX_H
+
+/**
+ * Creates a new 'regex' struct containing the given pattern and a PCRE
+ * compiled regular expression. Also, calls pcre_study because this regex will
+ * most likely be used often (like for every new window and on every relevant
+ * property change of existing windows).
+ *
+ * Returns NULL if the pattern could not be compiled into a regular expression
+ * (and ELOGs an appropriate error message).
+ *
+ */
+struct regex *regex_new(const char *pattern);
+
+/**
+ * Frees the given regular expression. It must not be used afterwards!
+ *
+ */
+void regex_free(struct regex *regex);
+
+/**
+ * Checks if the given regular expression matches the given input and returns
+ * true if it does. In either case, it logs the outcome using LOG(), so it will
+ * be visible without any debug loglevel.
+ *
+ */
+bool regex_matches(struct regex *regex, const char *input);
+
+#endif
--- /dev/null
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosddaemonhfoo
+#define foosddaemonhfoo
+
+/***
+ Copyright 2010 Lennart Poettering
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+***/
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ Reference implementation of a few systemd related interfaces for
+ writing daemons. These interfaces are trivial to implement. To
+ simplify porting we provide this reference implementation.
+ Applications are welcome to reimplement the algorithms described
+ here if they do not want to include these two source files.
+
+ The following functionality is provided:
+
+ - Support for logging with log levels on stderr
+ - File descriptor passing for socket-based activation
+ - Daemon startup and status notification
+ - Detection of systemd boots
+
+ You may compile this with -DDISABLE_SYSTEMD to disable systemd
+ support. This makes all those calls NOPs that are directly related to
+ systemd (i.e. only sd_is_xxx() will stay useful).
+
+ Since this is drop-in code we don't want any of our symbols to be
+ exported in any case. Hence we declare hidden visibility for all of
+ them.
+
+ You may find an up-to-date version of these source files online:
+
+ http://cgit.freedesktop.org/systemd/plain/src/sd-daemon.h
+ http://cgit.freedesktop.org/systemd/plain/src/sd-daemon.c
+
+ This should compile on non-Linux systems, too, but with the
+ exception of the sd_is_xxx() calls all functions will become NOPs.
+
+ See sd-daemon(7) for more information.
+*/
+
+#ifndef _sd_printf_attr_
+#if __GNUC__ >= 4
+#define _sd_printf_attr_(a,b) __attribute__ ((format (printf, a, b)))
+#else
+#define _sd_printf_attr_(a,b)
+#endif
+#endif
+
+#ifndef _sd_hidden_
+#if (__GNUC__ >= 4) && !defined(SD_EXPORT_SYMBOLS)
+#define _sd_hidden_ __attribute__ ((visibility("hidden")))
+#else
+#define _sd_hidden_
+#endif
+#endif
+
+/*
+ Log levels for usage on stderr:
+
+ fprintf(stderr, SD_NOTICE "Hello World!\n");
+
+ This is similar to printk() usage in the kernel.
+*/
+#define SD_EMERG "<0>" /* system is unusable */
+#define SD_ALERT "<1>" /* action must be taken immediately */
+#define SD_CRIT "<2>" /* critical conditions */
+#define SD_ERR "<3>" /* error conditions */
+#define SD_WARNING "<4>" /* warning conditions */
+#define SD_NOTICE "<5>" /* normal but significant condition */
+#define SD_INFO "<6>" /* informational */
+#define SD_DEBUG "<7>" /* debug-level messages */
+
+/* The first passed file descriptor is fd 3 */
+#define SD_LISTEN_FDS_START 3
+
+/*
+ Returns how many file descriptors have been passed, or a negative
+ errno code on failure. Optionally, removes the $LISTEN_FDS and
+ $LISTEN_PID file descriptors from the environment (recommended, but
+ problematic in threaded environments). If r is the return value of
+ this function you'll find the file descriptors passed as fds
+ SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative
+ errno style error code on failure. This function call ensures that
+ the FD_CLOEXEC flag is set for the passed file descriptors, to make
+ sure they are not passed on to child processes. If FD_CLOEXEC shall
+ not be set, the caller needs to unset it after this call for all file
+ descriptors that are used.
+
+ See sd_listen_fds(3) for more information.
+*/
+int sd_listen_fds(int unset_environment) _sd_hidden_;
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is a FIFO in the file system stored under the
+ specified path, 0 otherwise. If path is NULL a path name check will
+ not be done and the call only verifies if the file descriptor
+ refers to a FIFO. Returns a negative errno style error code on
+ failure.
+
+ See sd_is_fifo(3) for more information.
+*/
+int sd_is_fifo(int fd, const char *path) _sd_hidden_;
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is a socket of the specified family (AF_INET,
+ ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If
+ family is 0 a socket family check will not be done. If type is 0 a
+ socket type check will not be done and the call only verifies if
+ the file descriptor refers to a socket. If listening is > 0 it is
+ verified that the socket is in listening mode. (i.e. listen() has
+ been called) If listening is == 0 it is verified that the socket is
+ not in listening mode. If listening is < 0 no listening mode check
+ is done. Returns a negative errno style error code on failure.
+
+ See sd_is_socket(3) for more information.
+*/
+int sd_is_socket(int fd, int family, int type, int listening) _sd_hidden_;
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is an Internet socket, of the specified family
+ (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM,
+ SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version
+ check is not done. If type is 0 a socket type check will not be
+ done. If port is 0 a socket port check will not be done. The
+ listening flag is used the same way as in sd_is_socket(). Returns a
+ negative errno style error code on failure.
+
+ See sd_is_socket_inet(3) for more information.
+*/
+int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) _sd_hidden_;
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is an AF_UNIX socket of the specified type
+ (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0
+ a socket type check will not be done. If path is NULL a socket path
+ check will not be done. For normal AF_UNIX sockets set length to
+ 0. For abstract namespace sockets set length to the length of the
+ socket name (including the initial 0 byte), and pass the full
+ socket path in path (including the initial 0 byte). The listening
+ flag is used the same way as in sd_is_socket(). Returns a negative
+ errno style error code on failure.
+
+ See sd_is_socket_unix(3) for more information.
+*/
+int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) _sd_hidden_;
+
+/*
+ Informs systemd about changed daemon state. This takes a number of
+ newline separated environment-style variable assignments in a
+ string. The following variables are known:
+
+ READY=1 Tells systemd that daemon startup is finished (only
+ relevant for services of Type=notify). The passed
+ argument is a boolean "1" or "0". Since there is
+ little value in signaling non-readiness the only
+ value daemons should send is "READY=1".
+
+ STATUS=... Passes a single-line status string back to systemd
+ that describes the daemon state. This is free-from
+ and can be used for various purposes: general state
+ feedback, fsck-like programs could pass completion
+ percentages and failing programs could pass a human
+ readable error message. Example: "STATUS=Completed
+ 66% of file system check..."
+
+ ERRNO=... If a daemon fails, the errno-style error code,
+ formatted as string. Example: "ERRNO=2" for ENOENT.
+
+ BUSERROR=... If a daemon fails, the D-Bus error-style error
+ code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut"
+
+ MAINPID=... The main pid of a daemon, in case systemd did not
+ fork off the process itself. Example: "MAINPID=4711"
+
+ Daemons can choose to send additional variables. However, it is
+ recommended to prefix variable names not listed above with X_.
+
+ Returns a negative errno-style error code on failure. Returns > 0
+ if systemd could be notified, 0 if it couldn't possibly because
+ systemd is not running.
+
+ Example: When a daemon finished starting up, it could issue this
+ call to notify systemd about it:
+
+ sd_notify(0, "READY=1");
+
+ See sd_notifyf() for more complete examples.
+
+ See sd_notify(3) for more information.
+*/
+int sd_notify(int unset_environment, const char *state) _sd_hidden_;
+
+/*
+ Similar to sd_notify() but takes a format string.
+
+ Example 1: A daemon could send the following after initialization:
+
+ sd_notifyf(0, "READY=1\n"
+ "STATUS=Processing requests...\n"
+ "MAINPID=%lu",
+ (unsigned long) getpid());
+
+ Example 2: A daemon could send the following shortly before
+ exiting, on failure:
+
+ sd_notifyf(0, "STATUS=Failed to start up: %s\n"
+ "ERRNO=%i",
+ strerror(errno),
+ errno);
+
+ See sd_notifyf(3) for more information.
+*/
+int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_attr_(2,3) _sd_hidden_;
+
+/*
+ Returns > 0 if the system was booted with systemd. Returns < 0 on
+ error. Returns 0 if the system was not booted with systemd. Note
+ that all of the functions above handle non-systemd boots just
+ fine. You should NOT protect them with a call to this function. Also
+ note that this function checks whether the system, not the user
+ session is controlled by systemd. However the functions above work
+ for both user and system services.
+
+ See sd_booted(3) for more information.
+*/
+int sd_booted(void) _sd_hidden_;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
*/
void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop);
+/**
+ * Updates the WM_WINDOW_ROLE
+ *
+ */
+void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
+
#endif
i3-input(1)
=========
Michael Stapelberg <michael+i3@stapelberg.de>
-v3.delta, November 2009
+v4.1, September 2011
== NAME
== SYNOPSIS
-i3-input [-s <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-f <font>] [-v]
+i3-input [-s <socket>] [-F <format>] [-l <limit>] [-P <prompt>] [-f <font>] [-v]
== DESCRIPTION
the user, and send it/them to i3. This is useful, for example, for the
mark/goto command.
+The -F option takes a format string. In this string, every occurence of %s is
+replaced by the user input.
+
== EXAMPLE
------------------------------------------------
-i3-input -p 'mark ' -l 1 -P 'Mark: '
+i3-input -F 'mark %s' -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.
+i3-input handles the different sources of socket paths in the following order:
+
+* I3SOCK environment variable
+* I3SOCK gets overwritten by the -s parameter, if specified
+* if neither are available, i3-input reads the socket path from the X11
+ property, which is the recommended way
+* if everything fails, i3-input tries +/tmp/i3-ipc.sock+
+
+The socket path is necessary to connect to i3 and actually issue the command.
== SEE ALSO
<FOR_WINDOW_COND>"]" { yy_pop_state(); return ']'; }
+<ASSIGN_COND>"[" {
+ /* this is the case for the new assign syntax
+ * that uses criteria */
+ yy_pop_state();
+ yy_push_state(FOR_WINDOW_COND);
+ /* afterwards we will be in ASSIGN_TARGET_COND */
+ return '[';
+ }
<EAT_WHITESPACE>[ \t]* { yy_pop_state(); }
<WANT_QSTRING>\"[^\"]+\" {
yy_pop_state();
floating_modifier { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; }
workspace { BEGIN(INITIAL); return TOKWORKSPACE; }
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");
- yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE);
- return TOKOUTPUT;
- }
terminal { WS_STRING; return TOKTERMINAL; }
font { WS_STRING; return TOKFONT; }
assign { yy_push_state(ASSIGN_TARGET_COND); yy_push_state(ASSIGN_COND); return TOKASSIGN; }
auto { return TOK_AUTO; }
workspace_layout { return TOK_WORKSPACE_LAYOUT; }
new_window { return TOKNEWWINDOW; }
+new_float { return TOKNEWFLOAT; }
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; }
+force_xinerama { return TOK_FORCE_XINERAMA; }
workspace_bar { return TOKWORKSPACEBAR; }
popup_during_fullscreen { return TOK_POPUP_DURING_FULLSCREEN; }
ignore { return TOK_IGNORE; }
class { yy_push_state(WANT_QSTRING); return TOK_CLASS; }
instance { yy_push_state(WANT_QSTRING); return TOK_INSTANCE; }
+window_role { yy_push_state(WANT_QSTRING); return TOK_WINDOW_ROLE; }
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; }
yylval.string = copy;
return QUOTEDSTRING;
}
-<ASSIGN_COND>[^ \t\"]+ { BEGIN(ASSIGN_TARGET_COND); yylval.string = sstrdup(yytext); return STR_NG; }
+<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]; }
configerror_pid = -1;
}
+/* We need ev >= 4 for the following code. Since it is not *that* important (it
+ * only makes sure that there are no i3-nagbar instances left behind) we still
+ * support old systems with libev 3. */
+#if EV_VERSION_MAJOR >= 4
+/*
+ * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal
+ * SIGKILL (9) to make sure there are no left-over i3-nagbar processes.
+ *
+ */
+static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
+ if (configerror_pid != -1) {
+ LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", configerror_pid);
+ kill(configerror_pid, SIGKILL);
+ }
+}
+#endif
+
/*
* Starts an i3-nagbar process which alerts the user that his configuration
* file contains one or more errors. Also offers two buttons: One to launch an
if (configerror_pid == 0) {
char *editaction,
*pageraction;
- if (asprintf(&editaction, TERM_EMU " -e sh -c \"${EDITOR:-vi} \"%s\" && i3-msg reload\"", config_path) == -1)
+ if (asprintf(&editaction, "i3-sensible-terminal -e sh -c \"i3-sensible-editor \\\"%s\\\" && i3-msg reload\"", config_path) == -1)
exit(1);
- if (asprintf(&pageraction, TERM_EMU " -e sh -c \"${PAGER:-less} \"%s\"\"", errorfilename) == -1)
+ if (asprintf(&pageraction, "i3-sensible-terminal -e i3-sensible-pager \"%s\"", errorfilename) == -1)
exit(1);
char *argv[] = {
NULL, /* will be replaced by the executable path */
ev_child *child = smalloc(sizeof(ev_child));
ev_child_init(child, &nagbar_exited, configerror_pid, 0);
ev_child_start(main_loop, child);
+
+/* We need ev >= 4 for the following code. Since it is not *that* important (it
+ * only makes sure that there are no i3-nagbar instances left behind) we still
+ * support old systems with libev 3. */
+#if EV_VERSION_MAJOR >= 4
+ /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is
+ * still running) */
+ ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup));
+ ev_cleanup_init(cleanup, nagbar_cleanup);
+ ev_cleanup_start(main_loop, cleanup);
+#endif
}
/*
waitpid(configerror_pid, NULL, 0);
}
+/*
+ * Checks for duplicate key bindings (the same keycode or keysym is configured
+ * more than once). If a duplicate binding is found, a message is printed to
+ * stderr and the has_errors variable is set to true, which will start
+ * i3-nagbar.
+ *
+ */
+static void check_for_duplicate_bindings(struct context *context) {
+ Binding *bind, *current;
+ TAILQ_FOREACH(current, bindings, bindings) {
+ TAILQ_FOREACH(bind, bindings, bindings) {
+ /* Abort when we reach the current keybinding, only check the
+ * bindings before */
+ if (bind == current)
+ break;
+
+ /* Check if one is using keysym while the other is using bindsym.
+ * If so, skip. */
+ /* XXX: It should be checked at a later place (when translating the
+ * keysym to keycodes) if there are any duplicates */
+ if ((bind->symbol == NULL && current->symbol != NULL) ||
+ (bind->symbol != NULL && current->symbol == NULL))
+ continue;
+
+ /* If bind is NULL, current has to be NULL, too (see above).
+ * If the keycodes differ, it can't be a duplicate. */
+ if (bind->symbol != NULL &&
+ strcasecmp(bind->symbol, current->symbol) != 0)
+ continue;
+
+ /* Check if the keycodes or modifiers are different. If so, they
+ * can't be duplicate */
+ if (bind->keycode != current->keycode ||
+ bind->mods != current->mods)
+ continue;
+ context->has_errors = true;
+ if (current->keycode != 0) {
+ ELOG("Duplicate keybinding in config file:\n modmask %d with keycode %d, command \"%s\"\n",
+ current->mods, current->keycode, current->command);
+ } else {
+ ELOG("Duplicate keybinding in config file:\n modmask %d with keysym %s, command \"%s\"\n",
+ current->mods, current->symbol, current->command);
+ }
+ }
+ }
+}
+
void parse_file(const char *f) {
SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
int fd, ret, read_bytes = 0;
exit(1);
}
+ check_for_duplicate_bindings(context);
+
if (context->has_errors) {
start_configerror_nagbar(f);
}
%token TOK_AUTO "auto"
%token TOK_WORKSPACE_LAYOUT "workspace_layout"
%token TOKNEWWINDOW "new_window"
+%token TOKNEWFLOAT "new_float"
%token TOK_NORMAL "normal"
%token TOK_NONE "none"
%token TOK_1PIXEL "1pixel"
%token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse"
%token TOK_FORCE_FOCUS_WRAPPING "force_focus_wrapping"
+%token TOK_FORCE_XINERAMA "force_xinerama"
%token TOKWORKSPACEBAR "workspace_bar"
%token TOK_DEFAULT "default"
%token TOK_STACKING "stacking"
%token TOK_MARK "mark"
%token TOK_CLASS "class"
%token TOK_INSTANCE "instance"
+%token TOK_WINDOW_ROLE "window_role"
%token TOK_ID "id"
%token TOK_CON_ID "con_id"
%token TOK_TITLE "title"
%type <number> layout_mode
%type <number> border_style
%type <number> new_window
+%type <number> new_float
%type <number> colorpixel
%type <number> bool
%type <number> popup_setting
| orientation
| workspace_layout
| new_window
+ | new_float
| focus_follows_mouse
| force_focus_wrapping
+ | force_xinerama
| workspace_bar
| workspace
| assign
TOK_CLASS '=' STR
{
printf("criteria: class = %s\n", $3);
- current_match.class = $3;
+ current_match.class = regex_new($3);
+ free($3);
}
| TOK_INSTANCE '=' STR
{
printf("criteria: instance = %s\n", $3);
- current_match.instance = $3;
+ current_match.instance = regex_new($3);
+ free($3);
+ }
+ | TOK_WINDOW_ROLE '=' STR
+ {
+ printf("criteria: window_role = %s\n", $3);
+ current_match.role = regex_new($3);
+ free($3);
}
| TOK_CON_ID '=' STR
{
| TOK_MARK '=' STR
{
printf("criteria: mark = %s\n", $3);
- current_match.mark = $3;
+ current_match.mark = regex_new($3);
+ free($3);
}
| TOK_TITLE '=' STR
{
printf("criteria: title = %s\n", $3);
- current_match.title = $3;
+ current_match.title = regex_new($3);
+ free($3);
}
;
}
;
+new_float:
+ TOKNEWFLOAT border_style
+ {
+ DLOG("new floating windows should start with border style %d\n", $2);
+ config.default_floating_border = $2;
+ }
+ ;
+
border_style:
TOK_NORMAL { $$ = BS_NORMAL; }
| TOK_NONE { $$ = BS_NONE; }
}
;
+force_xinerama:
+ TOK_FORCE_XINERAMA bool
+ {
+ DLOG("force xinerama = %d\n", $2);
+ config.force_xinerama = $2;
+ }
+ ;
+
workspace_bar:
TOKWORKSPACEBAR bool
{
assign:
TOKASSIGN window_class STR
{
+ /* This is the old, deprecated form of assignments. It’s provided for
+ * compatibility in version (4.1, 4.2, 4.3) and will be removed
+ * afterwards. It triggers an i3-nagbar warning starting from 4.1. */
+ ELOG("You are using the old assign syntax (without criteria). "
+ "Please see the User's Guide for the new syntax and fix "
+ "your config file.\n");
+ context->has_errors = true;
printf("assignment of %s to *%s*\n", $2, $3);
char *workspace = $3;
char *criteria = $2;
char *separator = NULL;
if ((separator = strchr(criteria, '/')) != NULL) {
*(separator++) = '\0';
- match->title = sstrdup(separator);
+ char *pattern;
+ if (asprintf(&pattern, "(?i)%s", separator) == -1) {
+ ELOG("asprintf failed\n");
+ break;
+ }
+ match->title = regex_new(pattern);
+ free(pattern);
+ printf(" title = %s\n", separator);
+ }
+ if (*criteria != '\0') {
+ char *pattern;
+ if (asprintf(&pattern, "(?i)%s", criteria) == -1) {
+ ELOG("asprintf failed\n");
+ break;
+ }
+ match->class = regex_new(pattern);
+ free(pattern);
+ printf(" class = %s\n", criteria);
}
- if (*criteria != '\0')
- match->class = sstrdup(criteria);
free(criteria);
- printf(" class = %s\n", match->class);
- printf(" title = %s\n", match->title);
-
/* Compatibility with older versions: If the assignment target starts
* with ~, we create the equivalent of:
*
assignment->dest.workspace = workspace;
TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
}
+ | TOKASSIGN match STR
+ {
+ if (match_is_empty(¤t_match)) {
+ ELOG("Match is empty, ignoring this assignment\n");
+ break;
+ }
+ printf("new assignment, using above criteria, to workspace %s\n", $3);
+ Assignment *assignment = scalloc(sizeof(Assignment));
+ assignment->match = current_match;
+ assignment->type = A_TO_WORKSPACE;
+ assignment->dest.workspace = $3;
+ TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
+ }
;
window_class:
toggle { return TOK_TOGGLE; }
mode_toggle { return TOK_MODE_TOGGLE; }
workspace { WS_STRING; return TOK_WORKSPACE; }
+output { WS_STRING; return TOK_OUTPUT; }
focus { return TOK_FOCUS; }
move { return TOK_MOVE; }
open { return TOK_OPEN; }
class { BEGIN(WANT_QSTRING); return TOK_CLASS; }
instance { BEGIN(WANT_QSTRING); return TOK_INSTANCE; }
+window_role { BEGIN(WANT_QSTRING); return TOK_WINDOW_ROLE; }
id { BEGIN(WANT_QSTRING); return TOK_ID; }
con_id { BEGIN(WANT_QSTRING); return TOK_CON_ID; }
con_mark { BEGIN(WANT_QSTRING); return TOK_MARK; }
%token TOK_ENABLE "enable"
%token TOK_DISABLE "disable"
%token TOK_WORKSPACE "workspace"
+%token TOK_OUTPUT "output"
%token TOK_TOGGLE "toggle"
%token TOK_FOCUS "focus"
%token TOK_MOVE "move"
%token TOK_CLASS "class"
%token TOK_INSTANCE "instance"
+%token TOK_WINDOW_ROLE "window_role"
%token TOK_ID "id"
%token TOK_CON_ID "con_id"
%token TOK_TITLE "title"
}
} else if (current_match.mark != NULL && current->con->mark != NULL &&
- strcasecmp(current_match.mark, current->con->mark) == 0) {
+ regex_matches(current_match.mark, current->con->mark)) {
printf("match by mark\n");
- TAILQ_INSERT_TAIL(&owindows, current, owindows);
-
+ TAILQ_INSERT_TAIL(&owindows, current, owindows);
} else {
if (current->con->window == NULL)
continue;
TOK_CLASS '=' STR
{
printf("criteria: class = %s\n", $3);
- current_match.class = $3;
+ current_match.class = regex_new($3);
+ free($3);
}
| TOK_INSTANCE '=' STR
{
printf("criteria: instance = %s\n", $3);
- current_match.instance = $3;
+ current_match.instance = regex_new($3);
+ free($3);
+ }
+ | TOK_WINDOW_ROLE '=' STR
+ {
+ printf("criteria: window_role = %s\n", $3);
+ current_match.role = regex_new($3);
+ free($3);
}
| TOK_CON_ID '=' STR
{
| TOK_MARK '=' STR
{
printf("criteria: mark = %s\n", $3);
- current_match.mark = $3;
+ current_match.mark = regex_new($3);
+ free($3);
}
| TOK_TITLE '=' STR
{
printf("criteria: title = %s\n", $3);
- current_match.title = $3;
+ current_match.title = regex_new($3);
+ free($3);
}
;
tree_render();
}
+ | TOK_MOVE TOK_OUTPUT STR
+ {
+ owindow *current;
+
+ printf("should move window to output %s", $3);
+
+ HANDLE_EMPTY_MATCH;
+
+ /* get the output */
+ Output *current_output = NULL;
+ Output *output;
+
+ TAILQ_FOREACH(current, &owindows, owindows)
+ current_output = get_output_containing(current->con->rect.x, current->con->rect.y);
+
+ assert(current_output != NULL);
+
+ if (strcasecmp($3, "up") == 0)
+ output = get_output_next(D_UP, current_output);
+ else if (strcasecmp($3, "down") == 0)
+ output = get_output_next(D_DOWN, current_output);
+ else if (strcasecmp($3, "left") == 0)
+ output = get_output_next(D_LEFT, current_output);
+ else if (strcasecmp($3, "right") == 0)
+ output = get_output_next(D_RIGHT, current_output);
+ else
+ output = get_output_by_name($3);
+ free($3);
+
+ if (!output)
+ break;
+
+ /* get visible workspace on output */
+ Con *ws = NULL;
+ GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
+ if (!ws)
+ break;
+
+ TAILQ_FOREACH(current, &owindows, owindows) {
+ printf("matching: %p / %s\n", current->con, current->con->name);
+ con_move_to_workspace(current->con, ws, true, false);
+ }
+
+ tree_render();
+ }
;
append_layout:
double percentage = 1.0 / children;
LOG("default percentage = %f\n", percentage);
+ orientation_t orientation = current->parent->orientation;
+
+ if ((orientation == HORIZ &&
+ (direction == TOK_UP || direction == TOK_DOWN)) ||
+ (orientation == VERT &&
+ (direction == TOK_LEFT || direction == TOK_RIGHT))) {
+ LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
+ (orientation == HORIZ ? "horizontal" : "vertical"));
+ break;
+ }
+
if (direction == TOK_UP || direction == TOK_LEFT) {
other = TAILQ_PREV(current, nodes_head, nodes);
} else {
}
if (other == TAILQ_END(workspaces)) {
LOG("No other container in this direction found, cannot resize.\n");
- return 0;
+ break;
}
LOG("other->percent = %f\n", other->percent);
LOG("current->percent before = %f\n", current->percent);
*
*/
void load_configuration(xcb_connection_t *conn, const char *override_configpath, bool reload) {
- if (reload) {
- /* First ungrab the keys */
- ungrab_all_keys(conn);
+ if (reload) {
+ /* First ungrab the keys */
+ ungrab_all_keys(conn);
- struct Mode *mode;
- Binding *bind;
- while (!SLIST_EMPTY(&modes)) {
- mode = SLIST_FIRST(&modes);
- FREE(mode->name);
-
- /* Clear the old binding list */
- bindings = mode->bindings;
- while (!TAILQ_EMPTY(bindings)) {
- bind = TAILQ_FIRST(bindings);
- TAILQ_REMOVE(bindings, bind, bindings);
- FREE(bind->translated_to);
- FREE(bind->command);
- FREE(bind);
- }
- FREE(bindings);
- SLIST_REMOVE(&modes, mode, Mode, modes);
- }
+ struct Mode *mode;
+ Binding *bind;
+ while (!SLIST_EMPTY(&modes)) {
+ mode = SLIST_FIRST(&modes);
+ FREE(mode->name);
+
+ /* Clear the old binding list */
+ bindings = mode->bindings;
+ while (!TAILQ_EMPTY(bindings)) {
+ bind = TAILQ_FIRST(bindings);
+ TAILQ_REMOVE(bindings, bind, bindings);
+ FREE(bind->translated_to);
+ FREE(bind->command);
+ FREE(bind);
+ }
+ FREE(bindings);
+ SLIST_REMOVE(&modes, mode, Mode, modes);
+ }
-#if 0
- struct Assignment *assign;
- while (!TAILQ_EMPTY(&assignments)) {
- assign = TAILQ_FIRST(&assignments);
- FREE(assign->windowclass_title);
- TAILQ_REMOVE(&assignments, assign, assignments);
- FREE(assign);
- }
-#endif
+ struct Assignment *assign;
+ while (!TAILQ_EMPTY(&assignments)) {
+ assign = TAILQ_FIRST(&assignments);
+ if (assign->type == A_TO_WORKSPACE)
+ FREE(assign->dest.workspace);
+ else if (assign->type == A_TO_OUTPUT)
+ FREE(assign->dest.output);
+ else if (assign->type == A_COMMAND)
+ FREE(assign->dest.command);
+ match_free(&(assign->match));
+ TAILQ_REMOVE(&assignments, assign, assignments);
+ FREE(assign);
+ }
- /* Clear workspace names */
+ /* Clear workspace names */
#if 0
- Workspace *ws;
- TAILQ_FOREACH(ws, workspaces, workspaces)
- workspace_set_name(ws, NULL);
+ Workspace *ws;
+ TAILQ_FOREACH(ws, workspaces, workspaces)
+ workspace_set_name(ws, NULL);
#endif
- }
+ }
- SLIST_INIT(&modes);
+ SLIST_INIT(&modes);
- struct Mode *default_mode = scalloc(sizeof(struct Mode));
- default_mode->name = sstrdup("default");
- default_mode->bindings = scalloc(sizeof(struct bindings_head));
- TAILQ_INIT(default_mode->bindings);
- SLIST_INSERT_HEAD(&modes, default_mode, modes);
+ struct Mode *default_mode = scalloc(sizeof(struct Mode));
+ default_mode->name = sstrdup("default");
+ default_mode->bindings = scalloc(sizeof(struct bindings_head));
+ TAILQ_INIT(default_mode->bindings);
+ SLIST_INSERT_HEAD(&modes, default_mode, modes);
- bindings = default_mode->bindings;
+ bindings = default_mode->bindings;
#define REQUIRED_OPTION(name) \
- if (config.name == NULL) \
- die("You did not specify required configuration option " #name "\n");
+ if (config.name == NULL) \
+ die("You did not specify required configuration option " #name "\n");
- /* Clear the old config or initialize the data structure */
- memset(&config, 0, sizeof(config));
+ /* Clear the old config or initialize the data structure */
+ memset(&config, 0, sizeof(config));
- /* Initialize default colors */
+ /* Initialize default colors */
#define INIT_COLOR(x, cborder, cbackground, ctext) \
- do { \
- x.border = get_colorpixel(cborder); \
- x.background = get_colorpixel(cbackground); \
- x.text = get_colorpixel(ctext); \
- } while (0)
-
- config.client.background = get_colorpixel("#000000");
- INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff");
- INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff");
- INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888");
- INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff");
- INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff");
- INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888");
- INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff");
-
- config.default_border = BS_NORMAL;
- /* Set default_orientation to NO_ORIENTATION for auto orientation. */
- config.default_orientation = NO_ORIENTATION;
-
- parse_configuration(override_configpath);
-
- if (reload) {
- translate_keysyms();
- grab_all_keys(conn, false);
- }
+ do { \
+ x.border = get_colorpixel(cborder); \
+ x.background = get_colorpixel(cbackground); \
+ x.text = get_colorpixel(ctext); \
+ } while (0)
+
+ config.client.background = get_colorpixel("#000000");
+ INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff");
+ INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff");
+ INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888");
+ INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff");
+ INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff");
+ INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888");
+ INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff");
+
+ config.default_border = BS_NORMAL;
+ config.default_floating_border = BS_NORMAL;
+ /* Set default_orientation to NO_ORIENTATION for auto orientation. */
+ config.default_orientation = NO_ORIENTATION;
+
+ parse_configuration(override_configpath);
+
+ if (reload) {
+ translate_keysyms();
+ grab_all_keys(conn, false);
+ }
- if (config.font.id == 0) {
- ELOG("You did not specify required configuration option \"font\"\n");
- config.font = load_font("fixed", true);
- }
+ if (config.font.id == 0) {
+ ELOG("You did not specify required configuration option \"font\"\n");
+ config.font = load_font("fixed", true);
+ }
#if 0
- /* Set an empty name for every workspace which got no name */
- Workspace *ws;
- TAILQ_FOREACH(ws, workspaces, workspaces) {
- if (ws->name != NULL) {
- /* If the font was not specified when the workspace name
- * was loaded, we need to predict the text width now */
- if (ws->text_width == 0)
- ws->text_width = predict_text_width(global_conn,
- config.font, ws->name, ws->name_len);
- continue;
- }
-
- workspace_set_name(ws, NULL);
- }
+ /* Set an empty name for every workspace which got no name */
+ Workspace *ws;
+ TAILQ_FOREACH(ws, workspaces, workspaces) {
+ if (ws->name != NULL) {
+ /* If the font was not specified when the workspace name
+ * was loaded, we need to predict the text width now */
+ if (ws->text_width == 0)
+ ws->text_width = predict_text_width(global_conn,
+ config.font, ws->name, ws->name_len);
+ continue;
+ }
+
+ workspace_set_name(ws, NULL);
+ }
#endif
}
con->percent = 1.0;
con->floating = FLOATING_USER_ON;
+ /* 4: set the border style as specified with new_float */
+ if (automatic)
+ con->border_style = config.default_floating_border;
+
TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes);
TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused);
}
#if 0
-/*
- * Changes focus in the given direction for floating clients.
- *
- * Changing to the left/right means going to the previous/next floating client,
- * changing to top/bottom means cycling through the Z-index.
- *
- */
-void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
- DLOG("floating focus\n");
-
- if (direction == D_LEFT || direction == D_RIGHT) {
- /* Go to the next/previous floating client */
- Client *client;
-
- while ((client = (direction == D_LEFT ? TAILQ_PREV(currently_focused, floating_clients_head, floating_clients) :
- TAILQ_NEXT(currently_focused, floating_clients))) !=
- TAILQ_END(&(currently_focused->workspace->floating_clients))) {
- if (!client->floating)
- continue;
- set_focus(conn, client, true);
- return;
- }
- }
-}
-
/*
* Moves the client 10px to the specified direction.
*
return true;
}
+/*
+ * Called when a window changes its WM_WINDOW_ROLE.
+ *
+ */
+static bool handle_windowrole_change(void *data, xcb_connection_t *conn, uint8_t state,
+ xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
+ Con *con;
+ if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
+ return false;
+
+ window_update_role(con->window, prop, false);
+
+ return true;
+}
+
#if 0
/*
* Updates the client’s WM_CLASS property
tree_render();
x_push_changes(croot);
+ } else if (event->type == A_I3_SYNC) {
+ DLOG("i3 sync, yay\n");
+ xcb_window_t window = event->data.data32[0];
+ uint32_t rnd = event->data.data32[1];
+ DLOG("Sending random value %d back to X11 window 0x%08x\n", rnd, window);
+
+ void *reply = scalloc(32);
+ xcb_client_message_event_t *ev = reply;
+
+ ev->response_type = XCB_CLIENT_MESSAGE;
+ ev->window = window;
+ ev->type = A_I3_SYNC;
+ ev->format = 32;
+ ev->data.data32[0] = window;
+ ev->data.data32[1] = rnd;
+
+ xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char*)ev);
+ xcb_flush(conn);
+ free(reply);
} else {
ELOG("unhandled clientmessage\n");
return 0;
{ 0, 128, handle_windowname_change_legacy },
{ 0, UINT_MAX, handle_normal_hints },
{ 0, UINT_MAX, handle_clientleader_change },
- { 0, UINT_MAX, handle_transient_for }
+ { 0, UINT_MAX, handle_transient_for },
+ { 0, 128, handle_windowrole_change }
};
#define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
property_handlers[3].atom = XCB_ATOM_WM_NORMAL_HINTS;
property_handlers[4].atom = A_WM_CLIENT_LEADER;
property_handlers[5].atom = XCB_ATOM_WM_TRANSIENT_FOR;
+ property_handlers[6].atom = A_WM_WINDOW_ROLE;
}
static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
ystr("urgent");
y(bool, con->urgent);
+ if (con->mark != NULL) {
+ ystr("mark");
+ ystr(con->mark);
+ }
+
ystr("focused");
y(bool, (con == focused));
y(free);
}
+
/*
* Formats the reply message for a GET_WORKSPACES request and sends it to the
* client
y(free);
}
+/*
+ * Formats the reply message for a GET_MARKS request and sends it to the
+ * client
+ *
+ */
+IPC_HANDLER(get_marks) {
+#if YAJL_MAJOR >= 2
+ yajl_gen gen = yajl_gen_alloc(NULL);
+#else
+ yajl_gen gen = yajl_gen_alloc(NULL, NULL);
+#endif
+ y(array_open);
+
+ Con *con;
+ TAILQ_FOREACH(con, &all_cons, all_cons)
+ if (con->mark != NULL)
+ ystr(con->mark);
+
+ y(array_close);
+
+ const unsigned char *payload;
+#if YAJL_MAJOR >= 2
+ size_t length;
+#else
+ unsigned int length;
+#endif
+ y(get_buf, &payload, &length);
+
+ ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_MARKS, length);
+ y(free);
+}
+
/*
* Callback for the YAJL parser (will be called when a string is parsed).
*
/* The index of each callback function corresponds to the numeric
* value of the message type (see include/i3/ipc.h) */
-handler_t handlers[5] = {
+handler_t handlers[6] = {
handle_command,
handle_get_workspaces,
handle_subscribe,
handle_get_outputs,
- handle_tree
+ handle_tree,
+ handle_get_marks
};
/*
ev_io_init(package, ipc_receive_message, client, EV_READ);
ev_io_start(EV_A_ package);
- DLOG("IPC: new client connected\n");
+ DLOG("IPC: new client connected on fd %d\n", w->fd);
ipc_client *new = scalloc(sizeof(ipc_client));
new->fd = client;
json_node->layout = L_OUTPUT;
else LOG("Unhandled \"layout\": %s\n", buf);
free(buf);
+ } else if (strcasecmp(last_key, "mark") == 0) {
+ char *buf = NULL;
+ asprintf(&buf, "%.*s", (int)len, val);
+ json_node->mark = buf;
}
}
return 1;
#include <fcntl.h>
#include "all.h"
+#include "sd-daemon.h"
+
static int xkb_event_base;
int xkb_current_group;
DLOG("Done\n");
}
+/*
+ * Exit handler which destroys the main_loop. Will trigger cleanup handlers.
+ *
+ */
+static void i3_exit() {
+ ev_loop_destroy(main_loop);
+}
+
int main(int argc, char *argv[]) {
//parse_cmd("[ foo ] attach, attach ; focus");
int screens;
free(greply);
- if (force_xinerama) {
+ /* Force Xinerama (for drivers which don't support RandR yet, esp. the
+ * nVidia binary graphics driver), when specified either in the config
+ * file or on command-line */
+ if (force_xinerama || config.force_xinerama) {
xinerama_init();
} else {
DLOG("Checking for XRandR...\n");
ev_io_start(main_loop, ipc_io);
}
+ /* Also handle the UNIX domain sockets passed via socket activation */
+ int fds = sd_listen_fds(1);
+ if (fds < 0)
+ ELOG("socket activation: Error in sd_listen_fds\n");
+ else if (fds == 0)
+ DLOG("socket activation: no sockets passed\n");
+ else {
+ for (int fd = SD_LISTEN_FDS_START; fd < (SD_LISTEN_FDS_START + fds); fd++) {
+ DLOG("socket activation: also listening on fd %d\n", fd);
+ struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
+ ev_io_init(ipc_io, ipc_new_client, fd, EV_READ);
+ ev_io_start(main_loop, ipc_io);
+ }
+ }
+
/* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */
x_set_i3_atoms();
start_application(exec_always->command);
}
+ /* Make sure to destroy the event loop to invoke the cleeanup callbacks
+ * when calling exit() */
+ atexit(i3_exit);
+
ev_loop(main_loop, 0);
}
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
utf8_title_cookie, title_cookie,
- class_cookie, leader_cookie, transient_cookie;
+ class_cookie, leader_cookie, transient_cookie,
+ role_cookie;
geomc = xcb_get_geometry(conn, d);
transient_cookie = GET_PROPERTY(XCB_ATOM_WM_TRANSIENT_FOR, UINT32_MAX);
title_cookie = GET_PROPERTY(XCB_ATOM_WM_NAME, 128);
class_cookie = GET_PROPERTY(XCB_ATOM_WM_CLASS, 128);
+ role_cookie = GET_PROPERTY(A_WM_WINDOW_ROLE, 128);
/* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */
DLOG("reparenting!\n");
window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL));
window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL));
window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));
+ window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL), true);
/* check if the window needs WM_TAKE_FOCUS */
cwindow->needs_take_focus = window_supports_protocol(cwindow->id, A_WM_TAKE_FOCUS);
if (want_floating) {
DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height);
- floating_enable(nc, false);
+ floating_enable(nc, true);
}
/* to avoid getting an UnmapNotify event due to reparenting, we temporarily
match->application == NULL &&
match->class == NULL &&
match->instance == NULL &&
+ match->role == NULL &&
match->id == XCB_NONE &&
match->con_id == NULL &&
match->dock == -1 &&
void match_copy(Match *dest, Match *src) {
memcpy(dest, src, sizeof(Match));
-#define STRDUP(field) do { \
+/* The DUPLICATE_REGEX macro creates a new regular expression from the
+ * ->pattern of the old one. It therefore does use a little more memory then
+ * with a refcounting system, but it’s easier this way. */
+#define DUPLICATE_REGEX(field) do { \
if (src->field != NULL) \
- dest->field = sstrdup(src->field); \
+ dest->field = regex_new(src->field->pattern); \
} while (0)
- STRDUP(title);
- STRDUP(mark);
- STRDUP(application);
- STRDUP(class);
- STRDUP(instance);
+ DUPLICATE_REGEX(title);
+ DUPLICATE_REGEX(mark);
+ DUPLICATE_REGEX(application);
+ DUPLICATE_REGEX(class);
+ DUPLICATE_REGEX(instance);
+ DUPLICATE_REGEX(role);
}
/*
bool match_matches_window(Match *match, i3Window *window) {
LOG("checking window %d (%s)\n", window->id, window->class_class);
- /* TODO: pcre, full matching, … */
if (match->class != NULL) {
- if (window->class_class != NULL && strcasecmp(match->class, window->class_class) == 0) {
+ if (window->class_class != NULL &&
+ regex_matches(match->class, window->class_class)) {
LOG("window class matches (%s)\n", window->class_class);
} else {
LOG("window class does not match\n");
}
if (match->instance != NULL) {
- if (window->class_instance != NULL && strcasecmp(match->instance, window->class_instance) == 0) {
+ if (window->class_instance != NULL &&
+ regex_matches(match->instance, window->class_instance)) {
LOG("window instance matches (%s)\n", window->class_instance);
} else {
LOG("window instance does not match\n");
}
}
- /* TODO: pcre match */
if (match->title != NULL) {
- if (window->name_json != NULL && strcasecmp(match->title, window->name_json) == 0) {
+ if (window->name_json != NULL &&
+ regex_matches(match->title, window->name_json)) {
LOG("title matches (%s)\n", window->name_json);
} else {
LOG("title does not match\n");
}
}
+ if (match->role != NULL) {
+ if (window->role != NULL &&
+ regex_matches(match->role, window->role)) {
+ LOG("window_role matches (%s)\n", window->role);
+ } else {
+ LOG("window_role does not match\n");
+ return false;
+ }
+ }
+
if (match->dock != -1) {
LOG("match->dock = %d, window->dock = %d\n", match->dock, window->dock);
if ((window->dock == W_DOCK_TOP && match->dock == M_DOCK_TOP) ||
return true;
}
+
+/*
+ * Frees the given match. It must not be used afterwards!
+ *
+ */
+void match_free(Match *match) {
+ /* First step: free the regex fields / patterns */
+ regex_free(match->title);
+ regex_free(match->application);
+ regex_free(match->class);
+ regex_free(match->instance);
+ regex_free(match->mark);
+ regex_free(match->role);
+
+ /* Second step: free the regex helper struct itself */
+ FREE(match->title);
+ FREE(match->application);
+ FREE(match->class);
+ FREE(match->instance);
+ FREE(match->mark);
+ FREE(match->role);
+}
if (!exists) {
/* Set ->num to the number of the workspace, if the name actually
* is a number or starts with a number */
- long parsed_num = strtol(ws->name, NULL, 10);
+ char *endptr = NULL;
+ long parsed_num = strtol(ws->name, &endptr, 10);
if (parsed_num == LONG_MIN ||
parsed_num == LONG_MAX ||
- parsed_num <= 0)
+ parsed_num < 0 ||
+ endptr == ws->name)
ws->num = -1;
else ws->num = parsed_num;
LOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
--- /dev/null
+/*
+ * 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"
+
+/*
+ * Creates a new 'regex' struct containing the given pattern and a PCRE
+ * compiled regular expression. Also, calls pcre_study because this regex will
+ * most likely be used often (like for every new window and on every relevant
+ * property change of existing windows).
+ *
+ * Returns NULL if the pattern could not be compiled into a regular expression
+ * (and ELOGs an appropriate error message).
+ *
+ */
+struct regex *regex_new(const char *pattern) {
+ const char *error;
+ int errorcode, offset;
+
+ struct regex *re = scalloc(sizeof(struct regex));
+ re->pattern = sstrdup(pattern);
+ int options = PCRE_UTF8;
+#ifdef PCRE_HAS_UCP
+ /* We use PCRE_UCP so that \B, \b, \D, \d, \S, \s, \W, \w and some POSIX
+ * character classes play nicely with Unicode */
+ options |= PCRE_UCP;
+#endif
+ while (!(re->regex = pcre_compile2(pattern, options, &errorcode, &error, &offset, NULL))) {
+ /* If the error is that PCRE was not compiled with UTF-8 support we
+ * disable it and try again */
+ if (errorcode == 32) {
+ options &= ~PCRE_UTF8;
+ continue;
+ }
+ ELOG("PCRE regular expression compilation failed at %d: %s\n",
+ offset, error);
+ return NULL;
+ }
+ re->extra = pcre_study(re->regex, 0, &error);
+ /* If an error happened, we print the error message, but continue.
+ * Studying the regular expression leads to faster matching, but it’s not
+ * absolutely necessary. */
+ if (error) {
+ ELOG("PCRE regular expression studying failed: %s\n", error);
+ }
+ return re;
+}
+
+/*
+ * Frees the given regular expression. It must not be used afterwards!
+ *
+ */
+void regex_free(struct regex *regex) {
+ if (!regex)
+ return;
+ FREE(regex->pattern);
+ FREE(regex->regex);
+ FREE(regex->extra);
+}
+
+/*
+ * Checks if the given regular expression matches the given input and returns
+ * true if it does. In either case, it logs the outcome using LOG(), so it will
+ * be visible without any debug loglevel.
+ *
+ */
+bool regex_matches(struct regex *regex, const char *input) {
+ int rc;
+
+ /* We use strlen() because pcre_exec() expects the length of the input
+ * string in bytes */
+ if ((rc = pcre_exec(regex->regex, regex->extra, input, strlen(input), 0, 0, NULL, 0)) == 0) {
+ LOG("Regular expression \"%s\" matches \"%s\"\n",
+ regex->pattern, input);
+ return true;
+ }
+
+ if (rc == PCRE_ERROR_NOMATCH) {
+ LOG("Regular expression \"%s\" does not match \"%s\"\n",
+ regex->pattern, input);
+ return false;
+ }
+
+ ELOG("PCRE error %d while trying to use regular expression \"%s\" on input \"%s\", see pcreapi(3)\n",
+ rc, regex->pattern, input);
+ return false;
+}
--- /dev/null
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ Copyright 2010 Lennart Poettering
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+***/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/fcntl.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stddef.h>
+
+#include "sd-daemon.h"
+
+int sd_listen_fds(int unset_environment) {
+
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+ return 0;
+#else
+ int r, fd;
+ const char *e;
+ char *p = NULL;
+ unsigned long l;
+
+ if (!(e = getenv("LISTEN_PID"))) {
+ r = 0;
+ goto finish;
+ }
+
+ errno = 0;
+ l = strtoul(e, &p, 10);
+
+ if (errno != 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (!p || *p || l <= 0) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ /* Is this for us? */
+ if (getpid() != (pid_t) l) {
+ r = 0;
+ goto finish;
+ }
+
+ if (!(e = getenv("LISTEN_FDS"))) {
+ r = 0;
+ goto finish;
+ }
+
+ errno = 0;
+ l = strtoul(e, &p, 10);
+
+ if (errno != 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (!p || *p) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int) l; fd ++) {
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFD)) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (flags & FD_CLOEXEC)
+ continue;
+
+ if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) {
+ r = -errno;
+ goto finish;
+ }
+ }
+
+ r = (int) l;
+
+finish:
+ if (unset_environment) {
+ unsetenv("LISTEN_PID");
+ unsetenv("LISTEN_FDS");
+ }
+
+ return r;
+#endif
+}
+
+int sd_is_fifo(int fd, const char *path) {
+ struct stat st_fd;
+
+ if (fd < 0)
+ return -EINVAL;
+
+ memset(&st_fd, 0, sizeof(st_fd));
+ if (fstat(fd, &st_fd) < 0)
+ return -errno;
+
+ if (!S_ISFIFO(st_fd.st_mode))
+ return 0;
+
+ if (path) {
+ struct stat st_path;
+
+ memset(&st_path, 0, sizeof(st_path));
+ if (stat(path, &st_path) < 0) {
+
+ if (errno == ENOENT || errno == ENOTDIR)
+ return 0;
+
+ return -errno;
+ }
+
+ return
+ st_path.st_dev == st_fd.st_dev &&
+ st_path.st_ino == st_fd.st_ino;
+ }
+
+ return 1;
+}
+
+static int sd_is_socket_internal(int fd, int type, int listening) {
+ struct stat st_fd;
+
+ if (fd < 0 || type < 0)
+ return -EINVAL;
+
+ if (fstat(fd, &st_fd) < 0)
+ return -errno;
+
+ if (!S_ISSOCK(st_fd.st_mode))
+ return 0;
+
+ if (type != 0) {
+ int other_type = 0;
+ socklen_t l = sizeof(other_type);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0)
+ return -errno;
+
+ if (l != sizeof(other_type))
+ return -EINVAL;
+
+ if (other_type != type)
+ return 0;
+ }
+
+ if (listening >= 0) {
+ int accepting = 0;
+ socklen_t l = sizeof(accepting);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0)
+ return -errno;
+
+ if (l != sizeof(accepting))
+ return -EINVAL;
+
+ if (!accepting != !listening)
+ return 0;
+ }
+
+ return 1;
+}
+
+union sockaddr_union {
+ struct sockaddr sa;
+ struct sockaddr_in in4;
+ struct sockaddr_in6 in6;
+ struct sockaddr_un un;
+ struct sockaddr_storage storage;
+};
+
+int sd_is_socket(int fd, int family, int type, int listening) {
+ int r;
+
+ if (family < 0)
+ return -EINVAL;
+
+ if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+ return r;
+
+ if (family > 0) {
+ union sockaddr_union sockaddr;
+ socklen_t l;
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ l = sizeof(sockaddr);
+
+ if (getsockname(fd, &sockaddr.sa, &l) < 0)
+ return -errno;
+
+ if (l < sizeof(sa_family_t))
+ return -EINVAL;
+
+ return sockaddr.sa.sa_family == family;
+ }
+
+ return 1;
+}
+
+int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) {
+ union sockaddr_union sockaddr;
+ socklen_t l;
+ int r;
+
+ if (family != 0 && family != AF_INET && family != AF_INET6)
+ return -EINVAL;
+
+ if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+ return r;
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ l = sizeof(sockaddr);
+
+ if (getsockname(fd, &sockaddr.sa, &l) < 0)
+ return -errno;
+
+ if (l < sizeof(sa_family_t))
+ return -EINVAL;
+
+ if (sockaddr.sa.sa_family != AF_INET &&
+ sockaddr.sa.sa_family != AF_INET6)
+ return 0;
+
+ if (family > 0)
+ if (sockaddr.sa.sa_family != family)
+ return 0;
+
+ if (port > 0) {
+ if (sockaddr.sa.sa_family == AF_INET) {
+ if (l < sizeof(struct sockaddr_in))
+ return -EINVAL;
+
+ return htons(port) == sockaddr.in4.sin_port;
+ } else {
+ if (l < sizeof(struct sockaddr_in6))
+ return -EINVAL;
+
+ return htons(port) == sockaddr.in6.sin6_port;
+ }
+ }
+
+ return 1;
+}
+
+int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) {
+ union sockaddr_union sockaddr;
+ socklen_t l;
+ int r;
+
+ if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+ return r;
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ l = sizeof(sockaddr);
+
+ if (getsockname(fd, &sockaddr.sa, &l) < 0)
+ return -errno;
+
+ if (l < sizeof(sa_family_t))
+ return -EINVAL;
+
+ if (sockaddr.sa.sa_family != AF_UNIX)
+ return 0;
+
+ if (path) {
+ if (length <= 0)
+ length = strlen(path);
+
+ if (length <= 0)
+ /* Unnamed socket */
+ return l == offsetof(struct sockaddr_un, sun_path);
+
+ if (path[0])
+ /* Normal path socket */
+ return
+ (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) &&
+ memcmp(path, sockaddr.un.sun_path, length+1) == 0;
+ else
+ /* Abstract namespace socket */
+ return
+ (l == offsetof(struct sockaddr_un, sun_path) + length) &&
+ memcmp(path, sockaddr.un.sun_path, length) == 0;
+ }
+
+ return 1;
+}
+
+int sd_notify(int unset_environment, const char *state) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__) || !defined(SOCK_CLOEXEC)
+ return 0;
+#else
+ int fd = -1, r;
+ struct msghdr msghdr;
+ struct iovec iovec;
+ union sockaddr_union sockaddr;
+ const char *e;
+
+ if (!state) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ if (!(e = getenv("NOTIFY_SOCKET")))
+ return 0;
+
+ /* Must be an abstract socket, or an absolute path */
+ if ((e[0] != '@' && e[0] != '/') || e[1] == 0) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ sockaddr.sa.sa_family = AF_UNIX;
+ strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path));
+
+ if (sockaddr.un.sun_path[0] == '@')
+ sockaddr.un.sun_path[0] = 0;
+
+ memset(&iovec, 0, sizeof(iovec));
+ iovec.iov_base = (char*) state;
+ iovec.iov_len = strlen(state);
+
+ memset(&msghdr, 0, sizeof(msghdr));
+ msghdr.msg_name = &sockaddr;
+ msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(e);
+
+ if (msghdr.msg_namelen > sizeof(struct sockaddr_un))
+ msghdr.msg_namelen = sizeof(struct sockaddr_un);
+
+ msghdr.msg_iov = &iovec;
+ msghdr.msg_iovlen = 1;
+
+ if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ r = 1;
+
+finish:
+ if (unset_environment)
+ unsetenv("NOTIFY_SOCKET");
+
+ if (fd >= 0)
+ close(fd);
+
+ return r;
+#endif
+}
+
+int sd_notifyf(int unset_environment, const char *format, ...) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+ return 0;
+#else
+ va_list ap;
+ char *p = NULL;
+ int r;
+
+ va_start(ap, format);
+ r = vasprintf(&p, format, ap);
+ va_end(ap);
+
+ if (r < 0 || !p)
+ return -ENOMEM;
+
+ r = sd_notify(unset_environment, p);
+ free(p);
+
+ return r;
+#endif
+}
+
+int sd_booted(void) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+ return 0;
+#else
+
+ struct stat a, b;
+
+ /* We simply test whether the systemd cgroup hierarchy is
+ * mounted */
+
+ if (lstat("/sys/fs/cgroup", &a) < 0)
+ return 0;
+
+ if (lstat("/sys/fs/cgroup/systemd", &b) < 0)
+ return 0;
+
+ return a.st_dev != b.st_dev;
+#endif
+}
return true;
}
+ Con *parent = con->parent;
+
if (con->type == CT_FLOATING_CON) {
- /* TODO: implement focus for floating windows */
- return false;
- }
+ /* left/right focuses the previous/next floating container */
+ if (orientation == HORIZ) {
+ Con *next;
+ if (way == 'n')
+ next = TAILQ_NEXT(con, floating_windows);
+ else next = TAILQ_PREV(con, floating_head, floating_windows);
+
+ /* If there is no next/previous container, wrap */
+ if (!next) {
+ if (way == 'n')
+ next = TAILQ_FIRST(&(parent->floating_head));
+ else next = TAILQ_LAST(&(parent->floating_head), floating_head);
+ }
- Con *parent = con->parent;
+ /* Still no next/previous container? bail out */
+ if (!next)
+ return false;
+
+ con_focus(con_descend_focused(next));
+ return true;
+ } else {
+ /* up/down cycles through the Z-index */
+ /* TODO: implement cycling through the z-index */
+ return false;
+ }
+ }
/* If the orientation does not match or there is no other con to focus, we
* need to go higher in the hierarchy */
free(prop);
}
+
+/*
+ * Updates the WM_WINDOW_ROLE
+ *
+ */
+void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
+ if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
+ DLOG("prop == NULL\n");
+ FREE(prop);
+ return;
+ }
+
+ char *new_role;
+ if (asprintf(&new_role, "%.*s", xcb_get_property_value_length(prop),
+ (char*)xcb_get_property_value(prop)) == -1) {
+ perror("asprintf()");
+ DLOG("Could not get WM_WINDOW_ROLE\n");
+ free(prop);
+ return;
+ }
+ FREE(win->role);
+ win->role = new_role;
+ LOG("WM_WINDOW_ROLE changed to \"%s\"\n", win->role);
+
+ if (before_mgmt) {
+ free(prop);
+ return;
+ }
+
+ run_assignments(win);
+
+ free(prop);
+}
workspace->name = sstrdup(num);
/* We set ->num to the number if this workspace’s name consists only of
* a positive number. Otherwise it’s a named ws and num will be -1. */
- char *end;
- long parsed_num = strtol(num, &end, 10);
+ char *endptr = NULL;
+ long parsed_num = strtol(num, &endptr, 10);
if (parsed_num == LONG_MIN ||
parsed_num == LONG_MAX ||
parsed_num < 0 ||
- (end && *end != '\0'))
+ endptr == num)
workspace->num = -1;
else workspace->num = parsed_num;
LOG("num = %d\n", workspace->num);
use AnyEvent::I3 qw(:all);
use Try::Tiny;
use Getopt::Long;
-use Time::HiRes qw(sleep);
-use X11::XCB::Connection;
+use Time::HiRes qw(sleep gettimeofday tv_interval);
+use X11::XCB;
+use IO::Socket::UNIX; # core
+use POSIX; # core
+use AnyEvent::Handle;
+
+# open a file so that we get file descriptor 3. we will later close it in the
+# child and dup() the listening socket file descriptor to 3 to pass it to i3
+open(my $reserved, '<', '/dev/null');
+if (fileno($reserved) != 3) {
+ warn "Socket file descriptor is not 3.";
+ warn "Please don't start this script within a subshell of vim or something.";
+ exit 1;
+}
# install a dummy CHLD handler to overwrite the CHLD handler of AnyEvent / EV
# XXX: we could maybe also use a different loop than the default loop in EV?
my @conns;
my @wdisplays;
for my $display (@displays) {
- try {
- my $x = X11::XCB::Connection->new(display => $display);
+ my $screen;
+ my $x = X11::XCB->new($display, $screen);
+ if ($x->has_error) {
+ say STDERR "WARNING: Not using X11 display $display, could not connect";
+ } else {
push @conns, $x;
push @wdisplays, $display;
- } catch {
- say STDERR "WARNING: Not using X11 display $display, could not connect";
- };
+ }
}
-my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
my $config = slurp('i3-test.config');
# 1: get a list of all testcases
#
sub take_job {
my ($display) = @_;
- my ($fh, $tmpfile) = tempfile();
- say $fh $config;
- say $fh "ipc-socket /tmp/nested-$display";
- close($fh);
my $test = shift @testfiles;
return unless $test;
- my $logpath = "$outdir/i3-log-for-" . basename($test);
- my $cmd = "export DISPLAY=$display; exec $i3cmd -c $tmpfile >$logpath 2>&1";
my $dont_start = (slurp($test) =~ /# !NO_I3_INSTANCE!/);
+ my $logpath = "$outdir/i3-log-for-" . basename($test);
+
+ my ($fh, $tmpfile) = tempfile('i3-run-cfg.XXXXXX', UNLINK => 1);
+ say $fh $config;
+ say $fh "ipc-socket /tmp/nested-$display";
+ close($fh);
+
+ my $activate_cv = AnyEvent->condvar;
+ my $time_before_start = [gettimeofday];
+ my $start_i3 = sub {
+ # remove the old unix socket
+ unlink("/tmp/nested-$display-activation");
+
+ # pass all file descriptors up to three to the children.
+ # we need to set this flag before opening the socket.
+ open(my $fdtest, '<', '/dev/null');
+ $^F = fileno($fdtest);
+ close($fdtest);
+ my $socket = IO::Socket::UNIX->new(
+ Listen => 1,
+ Local => "/tmp/nested-$display-activation",
+ );
+
+ my $pid = fork;
+ if (!defined($pid)) {
+ die "could not fork()";
+ }
+ if ($pid == 0) {
+ $ENV{LISTEN_PID} = $$;
+ $ENV{LISTEN_FDS} = 1;
+ $ENV{DISPLAY} = $display;
+ $^F = 3;
+
+ close($reserved);
+ POSIX::dup2(fileno($socket), 3);
+
+ # now execute i3
+ my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
+ my $cmd = "exec $i3cmd -c $tmpfile >$logpath 2>&1";
+ exec "/bin/sh", '-c', $cmd;
+
+ # if we are still here, i3 could not be found or exec failed. bail out.
+ exit 1;
+ }
- my $process = Proc::Background->new($cmd) unless $dont_start;
- say "[$display] Running $test with logfile $logpath";
+ my $child_watcher;
+ $child_watcher = AnyEvent->child(pid => $pid, cb => sub {
+ say "child died. pid = $pid";
+ undef $child_watcher;
+ });
+
+ # close the socket, the child process should be the only one which keeps a file
+ # descriptor on the listening socket.
+ $socket->close;
+
+ # We now connect (will succeed immediately) and send a request afterwards.
+ # As soon as the reply is there, i3 is considered ready.
+ my $cl = IO::Socket::UNIX->new(Peer => "/tmp/nested-$display-activation");
+ my $hdl;
+ $hdl = AnyEvent::Handle->new(fh => $cl, on_error => sub { $activate_cv->send(0) });
+
+ # send a get_tree message without payload
+ $hdl->push_write('i3-ipc' . pack("LL", 0, 4));
+
+ # wait for the reply
+ $hdl->push_read(chunk => 1, => sub {
+ my ($h, $line) = @_;
+ $activate_cv->send(1);
+ undef $hdl;
+ });
+
+ return $pid;
+ };
+
+ my $pid;
+ $pid = $start_i3->() unless $dont_start;
- sleep 0.5;
my $kill_i3 = sub {
# Don’t bother killing i3 when we haven’t started it
return if $dont_start;
}
say "[$display] killing i3";
- kill(9, $process->pid) or die "could not kill i3";
+ kill(9, $pid) or die "could not kill i3";
};
- my $output;
- my $parser = TAP::Parser->new({
- exec => [ 'sh', '-c', "DISPLAY=$display /usr/bin/perl -It/lib $test" ],
- spool => IO::Scalar->new(\$output),
- merge => 1,
- });
+ # This will be called as soon as i3 is running and answered to our
+ # IPC request
+ $activate_cv->cb(sub {
+ my $time_activating = [gettimeofday];
+ my $start_duration = tv_interval($time_before_start, $time_activating);
+ my ($status) = $activate_cv->recv;
+ if ($dont_start) {
+ say "[$display] Not starting i3, testcase does that";
+ } else {
+ say "[$display] i3 startup: took " . sprintf("%.2f", $start_duration) . "s, status = $status";
+ }
- my @watchers;
- my ($stdout, $stderr) = $parser->get_select_handles;
- for my $handle ($parser->get_select_handles) {
- my $w;
- $w = AnyEvent->io(
- fh => $handle,
- poll => 'r',
- cb => sub {
- # Ignore activity on stderr (unnecessary with merge => 1,
- # but let’s keep it in here if we want to use merge => 0
- # for some reason in the future).
- return if defined($stderr) and $handle == $stderr;
-
- my $result = $parser->next;
- if (defined($result)) {
- # TODO: check if we should bail out
- return;
+ say "[$display] Running $test with logfile $logpath";
+
+ my $output;
+ my $parser = TAP::Parser->new({
+ exec => [ 'sh', '-c', qq|DISPLAY=$display LOGPATH="$logpath" /usr/bin/perl -It/lib $test| ],
+ spool => IO::Scalar->new(\$output),
+ merge => 1,
+ });
+
+ my @watchers;
+ my ($stdout, $stderr) = $parser->get_select_handles;
+ for my $handle ($parser->get_select_handles) {
+ my $w;
+ $w = AnyEvent->io(
+ fh => $handle,
+ poll => 'r',
+ cb => sub {
+ # Ignore activity on stderr (unnecessary with merge => 1,
+ # but let’s keep it in here if we want to use merge => 0
+ # for some reason in the future).
+ return if defined($stderr) and $handle == $stderr;
+
+ my $result = $parser->next;
+ if (defined($result)) {
+ # TODO: check if we should bail out
+ return;
+ }
+
+ # $result is not defined, we are done parsing
+ say "[$display] $test finished";
+ close($parser->delete_spool);
+ $aggregator->add($test, $parser);
+ push @done, [ $test, $output ];
+
+ $kill_i3->();
+
+ undef $_ for @watchers;
+ if (@done == $num) {
+ $cv->send;
+ } else {
+ take_job($display);
+ }
}
+ );
+ push @watchers, $w;
+ }
+ });
- # $result is not defined, we are done parsing
- say "[$display] $test finished";
- close($parser->delete_spool);
- $aggregator->add($test, $parser);
- push @done, [ $test, $output ];
-
- $kill_i3->();
-
- undef $_ for @watchers;
- if (@done == $num) {
- $cv->send;
- } else {
- take_job($display);
- }
- }
- );
- push @watchers, $w;
- }
+ $activate_cv->send(1) if $dont_start;
}
$cv->recv;
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => $original_rect,
background_color => '#C0C0C0',
+ event_mask => [ 'structure_notify' ],
);
isa_ok($window, 'X11::XCB::Window');
$window->map;
-sleep 0.25;
+wait_for_map $x;
# open another container to make the window get only half of the screen
cmd 'open';
ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
$original_rect = $new_rect;
-sleep 0.25;
-
$window->fullscreen(1);
-sleep 0.25;
+sync_with_i3($x);
$new_rect = $window->rect;
ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => $original_rect,
background_color => 61440,
+ event_mask => [ 'structure_notify' ],
);
is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
$window->fullscreen(1);
$window->map;
-sleep(0.25);
+wait_for_map $x;
$new_rect = $window->rect;
ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => $original_rect,
background_color => '#C0C0C0',
+ event_mask => [ 'structure_notify' ],
);
$swindow->map;
-sleep 0.25;
+
+sync_with_i3($x);
ok(!$swindow->mapped, 'window not mapped while fullscreen window active');
ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
$swindow->fullscreen(1);
-sleep 0.25;
+sync_with_i3($x);
is(fullscreen_windows(), 1, 'amount of fullscreen windows');
$window->fullscreen(0);
-sleep 0.25;
+sync_with_i3($x);
is(fullscreen_windows(), 0, 'amount of fullscreen windows');
ok($swindow->mapped, 'window mapped after other fullscreen ended');
###########################################################################
$swindow->fullscreen(0);
-sleep 0.25;
+sync_with_i3($x);
is(fullscreen_windows(), 0, 'amount of fullscreen windows after disabling');
background_color => '#C0C0C0',
# replace the type with 'utility' as soon as the coercion works again in X11::XCB
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+ event_mask => [ 'structure_notify' ],
);
isa_ok($window, 'X11::XCB::Window');
$window->map;
-sleep 0.25;
+wait_for_map $x;
my ($absolute, $top) = $window->rect;
rect => [ 1, 1, 80, 90],
background_color => '#C0C0C0',
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+ event_mask => [ 'structure_notify' ],
);
isa_ok($window, 'X11::XCB::Window');
$window->map;
-sleep 0.25;
+wait_for_map $x;
($absolute, $top) = $window->rect;
rect => [ 1, 1, 80, 90],
background_color => '#C0C0C0',
#window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+ event_mask => [ 'structure_notify' ],
);
isa_ok($window, 'X11::XCB::Window');
$window->map;
-sleep 0.25;
+wait_for_map $x;
cmd 'floating enable';
# vim:ts=4:sw=4:expandtab
use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
my $x = X11::XCB::Connection->new;
#####################################################################
# Create a window so we can get a focus different from NULL
-my $window = open_standard_window($x);
-diag("window->id = " . $window->id);
-
-sleep 0.25;
+my $window = open_window($x);
my $focus = $x->input_focus;
-diag("old focus = $focus");
# Switch to another workspace
fresh_workspace;
+sync_with_i3($x);
my $new_focus = $x->input_focus;
isnt($focus, $new_focus, "Focus changed");
# vim:ts=4:sw=4:expandtab
use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace;
#####################################################################
cmd 'layout default';
cmd 'split v';
-my $top = open_standard_window($x);
-my $mid = open_standard_window($x);
-my $bottom = open_standard_window($x);
-sleep 0.25;
-
-diag("top id = " . $top->id);
-diag("mid id = " . $mid->id);
-diag("bottom id = " . $bottom->id);
+my $top = open_window($x);
+my $mid = open_window($x);
+my $bottom = open_window($x);
#
# Returns the input focus after sending the given command to i3 via IPC
sub focus_after {
my $msg = shift;
- $i3->command($msg)->recv;
+ cmd $msg;
+ sync_with_i3 $x;
return $x->input_focus;
}
# over an unfocused tiling client and destroying the floating one again.
use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window') or BAIL_OUT('Could not load X11::XCB::Window');
-}
my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
fresh_workspace;
cmd 'split h';
-my $tiled_left = open_standard_window($x);
-my $tiled_right = open_standard_window($x);
-
-sleep 0.25;
+my $tiled_left = open_window($x);
+my $tiled_right = open_window($x);
# Get input focus before creating the floating window
my $focus = $x->input_focus;
# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 1, 1, 30, 30],
- background_color => '#C0C0C0',
- type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
+my $window = open_floating_window($x);
-sleep 1;
-sleep 0.25;
is($x->input_focus, $window->id, 'floating window focused');
$window->unmap;
-sleep 0.25;
+wait_for_unmap($x);
is($x->input_focus, $focus, 'Focus correctly restored');
#####################################################################
my $top = i3test::open_standard_window($x);
-sleep(0.25);
my $mid = i3test::open_standard_window($x);
-sleep(0.25);
my $bottom = i3test::open_standard_window($x);
sleep(0.25);
}
my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
#####################################################################
# verify that there is no dock window yet
my $primary = first { $_->primary } @{$screens};
# TODO: focus the primary screen before
-
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->map;
-
-sleep 0.25;
+my $window = open_window($x, {
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
my $rect = $window->rect;
is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
$window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 50, height => 40));
-sleep 0.25;
+sync_with_i3 $x;
@docked = get_dock_clients('top');
is(@docked, 1, 'one dock client found');
$window->destroy;
-sleep 0.25;
+wait_for_unmap $x;
@docked = get_dock_clients();
is(@docked, 0, 'no more dock clients');
# check if it gets placed on bottom (by coordinates)
#####################################################################
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 1000, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->map;
-
-sleep 0.25;
+$window = open_window($x, {
+ rect => [ 0, 1000, 30, 30 ],
+ background_color => '#FF0000',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
my $rect = $window->rect;
is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
$window->destroy;
-sleep 0.25;
+wait_for_unmap $x;
@docked = get_dock_clients();
is(@docked, 0, 'no more dock clients');
# check if it gets placed on bottom (by hint)
#####################################################################
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 1000, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
+$window = open_window($x, {
+ dont_map => 1,
+ rect => [ 0, 1000, 30, 30 ],
+ background_color => '#FF0000',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
$window->_create();
$window->map;
-sleep 0.25;
+wait_for_map $x;
@docked = get_dock_clients('top');
is(@docked, 1, 'dock client on top');
$window->destroy;
-sleep 0.25;
+wait_for_unmap $x;
@docked = get_dock_clients();
is(@docked, 0, 'no more dock clients');
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 1000, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
+$window = open_window($x, {
+ dont_map => 1,
+ rect => [ 0, 1000, 30, 30 ],
+ background_color => '#FF0000',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
$window->_create();
$window->map;
-sleep 0.25;
+wait_for_map $x;
@docked = get_dock_clients('bottom');
is(@docked, 1, 'dock client on bottom');
# regression test: transient dock client
#####################################################################
-my $fwindow = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
+$fwindow = open_window($x, {
+ dont_map => 1,
+ background_color => '#FF0000',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
$fwindow->transient_for($window);
$fwindow->map;
-sleep 0.25;
+wait_for_map $x;
does_i3_live;
# vim:ts=4:sw=4:expandtab
use i3test;
-use X11::XCB qw(:all);
-use Digest::SHA1 qw(sha1_base64);
-
-BEGIN {
- use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
+use File::Temp;
my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace;
cmd 'split h';
# Create two windows and make sure focus switching works
#####################################################################
-my $top = open_standard_window($x);
-sleep 0.25;
-my $mid = open_standard_window($x);
-sleep 0.25;
-my $bottom = open_standard_window($x);
-sleep 0.25;
-
-diag("top id = " . $top->id);
-diag("mid id = " . $mid->id);
-diag("bottom id = " . $bottom->id);
+my $top = open_window($x);
+my $mid = open_window($x);
+my $bottom = open_window($x);
#
# Returns the input focus after sending the given command to i3 via IPC
-# end sleeping for half a second to make sure i3 reacted
+# and syncing with i3
#
sub focus_after {
my $msg = shift;
cmd $msg;
+ sync_with_i3($x);
return $x->input_focus;
}
# Now goto a mark which does not exist
#####################################################################
-my $random_mark = sha1_base64(rand());
+my $random_mark = mktemp('mark.XXXXXX');
$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
is($focus, $mid->id, "focus unchanged");
-$i3->command("mark $random_mark")->recv;
+cmd "mark $random_mark";
$focus = focus_after('focus left');
is($focus, $top->id, "Top window focused");
#!perl
# vim:ts=4:sw=4:expandtab
-# Beware that this test uses workspace 9 to perform some tests (it expects
-# the workspace to be empty).
-# TODO: skip it by default?
use i3test;
use X11::XCB qw(:all);
# Create a floating window and see if resizing works
#####################################################################
-# Create a floating window
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#C0C0C0',
- # replace the type with 'utility' as soon as the coercion works again in X11::XCB
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-sleep 0.25;
+my $window = open_floating_window($x);
# See if configurerequests cause window movements (they should not)
my ($a, $t) = $window->rect;
$window->rect(X11::XCB::Rect->new(x => $a->x, y => $a->y, width => $a->width, height => $a->height));
-sleep 0.25;
+sync_with_i3($x);
+
my ($na, $nt) = $window->rect;
is_deeply($na, $a, 'Rects are equal after configurerequest');
sub test_resize {
$window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 100, height => 100));
+ sync_with_i3($x);
+
my ($absolute, $top) = $window->rect;
# Make sure the width/height are different from what we’re gonna test, so
isnt($absolute->height, 500, 'height != 500');
$window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 300, height => 500));
- sleep 0.25;
+
+ sync_with_i3($x);
($absolute, $top) = $window->rect;
cmd 'split v';
-my $top = open_standard_window($x);
-my $bottom = open_standard_window($x);
+my $top = open_window($x);
+my $bottom = open_window($x);
my @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
is(@urgent, 0, 'no window got the urgent flag');
# Add the urgency hint, switch to a different workspace and back again
#####################################################################
$top->add_hint('urgency');
-sleep 0.5;
+sync_with_i3($x);
@content = @{get_ws_content($tmp)};
@urgent = grep { $_->{urgent} } @content;
is(@urgent, 0, 'no window got the urgent flag after focusing');
$top->add_hint('urgency');
-sleep 0.5;
+sync_with_i3($x);
@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
is(@urgent, 0, 'no window got the urgent flag after re-setting urgency hint');
my $otmp = fresh_workspace;
$top->add_hint('urgency');
-sleep 0.5;
+sync_with_i3($x);
$ws = get_ws($tmp);
ok($ws->{urgent}, 'urgent flag set on workspace');
}
my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace;
# one of both (depending on your screen resolution) will be positioned wrong.
####################################################################################
-my $left = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [0, 0, 30, 30],
- background_color => '#FF0000',
-);
-
-$left->name('Left');
-$left->map;
-
-my $right = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [0, 0, 30, 30],
- background_color => '#FF0000',
-);
-
-$right->name('Right');
-$right->map;
-
-sleep 0.25;
+my $left = open_window($x, { name => 'Left' });
+my $right = open_window($x, { name => 'Right' });
my ($abs, $rgeom) = $right->rect;
-my $child = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#C0C0C0',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-$child->name('Child window');
+my $child = open_floating_window($x, {
+ dont_map => 1,
+ name => 'Child window',
+ });
$child->client_leader($right);
$child->map;
-sleep 0.25;
+ok(wait_for_map($x), 'child window mapped');
my $cgeom;
($abs, $cgeom) = $child->rect;
cmp_ok($cgeom->x, '>=', $rgeom->x, 'Child X >= right container X');
-my $child2 = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#C0C0C0',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-$child2->name('Child window 2');
+my $child2 = open_floating_window($x, {
+ dont_map => 1,
+ name => 'Child window 2',
+ });
$child2->client_leader($left);
$child2->map;
-sleep 0.25;
+ok(wait_for_map($x), 'second child window mapped');
($abs, $cgeom) = $child2->rect;
cmp_ok(($cgeom->x + $cgeom->width), '<', $rgeom->x, 'child above left window');
# check wm_transient_for
-
-
-my $fwindow = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#FF0000',
-);
-
+my $fwindow = open_window($x, { dont_map => 1 });
$fwindow->transient_for($right);
$fwindow->map;
-sleep 0.25;
+ok(wait_for_map($x), 'transient window mapped');
my ($absolute, $top) = $fwindow->rect;
ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)');
# Create a parent window
#####################################################################
-my $window = $x->root->create_child(
-class => WINDOW_CLASS_INPUT_OUTPUT,
-rect => [ 0, 0, 30, 30 ],
-background_color => '#C0C0C0',
-);
-
-$window->name('Parent window');
+my $window = open_window($x, { dont_map => 1, name => 'Parent window' });
$window->map;
-sleep 0.25;
+ok(wait_for_map($x), 'parent window mapped');
#########################################################################
# Switch to a different workspace and open a child window. It should be opened
#########################################################################
fresh_workspace;
-my $child = $x->root->create_child(
-class => WINDOW_CLASS_INPUT_OUTPUT,
-rect => [ 0, 0, 30, 30 ],
-background_color => '#C0C0C0',
-);
-
-$child->name('Child window');
+my $child = open_window($x, { dont_map => 1, name => 'Child window' });
$child->client_leader($window);
$child->map;
-sleep 0.25;
+ok(wait_for_map($x), 'child window mapped');
isnt($x->input_focus, $child->id, "Child window focused");
ok(workspace_exists('prev'), 'workspace "prev" exists');
is(focused_ws(), 'prev', 'now on workspace prev');
+#####################################################################
+# check that the numbers are assigned/recognized correctly
+#####################################################################
+
+cmd "workspace 3: $tmp";
+my $ws = get_ws("3: $tmp");
+ok(defined($ws), "workspace 3: $tmp was created");
+is($ws->{num}, 3, 'workspace number is 3');
+
+cmd "workspace 0: $tmp";
+my $ws = get_ws("0: $tmp");
+ok(defined($ws), "workspace 0: $tmp was created");
+is($ws->{num}, 0, 'workspace number is 0');
+
+cmd "workspace aa: $tmp";
+my $ws = get_ws("aa: $tmp");
+ok(defined($ws), "workspace aa: $tmp was created");
+is($ws->{num}, -1, 'workspace number is -1');
+
done_testing;
# Open a new window
my $x = X11::XCB::Connection->new;
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#C0C0C0',
-);
-
-$window->map;
-# give it some time to be picked up by the window manager
-# TODO: better check for $window->mapped or something like that?
-# maybe we can even wait for getting mapped?
-my $c = 0;
-while (@{get_ws_content($tmp)} == 0 and $c++ < 5) {
- sleep 0.25;
-}
+my $window = open_window($x);
my $content = get_ws_content($tmp);
ok(@{$content} == 1, 'window mapped');
my $win = $content->[0];
# first test that matches which should not match this window really do
# not match it
######################################################################
-# TODO: use PCRE expressions
# TODO: specify more match types
-cmd q|[class="*"] kill|;
+# we can match on any (non-empty) class here since that window does not have
+# WM_CLASS set
+cmd q|[class=".*"] kill|;
cmd q|[con_id="99999"] kill|;
$content = get_ws_content($tmp);
my $id = $win->{id};
cmd qq|[con_id="$id"] kill|;
-# give i3 some time to pick up the UnmapNotify event
-sleep 0.25;
+wait_for_unmap $x;
cmd 'nop checking if its gone';
$content = get_ws_content($tmp);
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
);
$left->_create;
set_wm_class($left->id, 'special', 'special');
$left->name('left');
$left->map;
-sleep 0.25;
+ok(wait_for_map($x), 'left window mapped');
my $right = $x->root->create_child(
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
);
$right->_create;
set_wm_class($right->id, 'special', 'special');
$right->name('right');
$right->map;
-sleep 0.25;
+ok(wait_for_map($x), 'right window mapped');
# two windows should be here
$content = get_ws_content($tmp);
cmd '[class="special" title="left"] kill';
-sleep 0.25;
+sync_with_i3($x);
$content = get_ws_content($tmp);
is(@{$content}, 1, 'one window still there');
+######################################################################
+# check that regular expressions work
+######################################################################
+
+$tmp = fresh_workspace;
+
+$left = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
+);
+
+$left->_create;
+set_wm_class($left->id, 'special7', 'special7');
+$left->name('left');
+$left->map;
+ok(wait_for_map($x), 'left window mapped');
+
+# two windows should be here
+$content = get_ws_content($tmp);
+ok(@{$content} == 1, 'window opened');
+
+cmd '[class="^special[0-9]$"] kill';
+
+wait_for_unmap $x;
+
+$content = get_ws_content($tmp);
+is(@{$content}, 0, 'window killed');
+
+######################################################################
+# check that UTF-8 works when matching
+######################################################################
+
+$tmp = fresh_workspace;
+
+$left = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
+);
+
+$left->_create;
+set_wm_class($left->id, 'special7', 'special7');
+$left->name('ä 3');
+$left->map;
+ok(wait_for_map($x), 'left window mapped');
+
+# two windows should be here
+$content = get_ws_content($tmp);
+ok(@{$content} == 1, 'window opened');
+
+cmd '[title="^\w [3]$"] kill';
+
+wait_for_unmap $x;
+
+$content = get_ws_content($tmp);
+is(@{$content}, 0, 'window killed');
+
done_testing;
$middle = open_empty_con($i3);
# XXX: the $right empty con will be filled with the x11 window we are creating afterwards
$right = open_empty_con($i3);
-my $win = open_standard_window($x, '#00ff00');
+my $win = open_window($x, { background_color => '#00ff00' });
cmd qq|[con_id="$middle"] focus|;
$win->destroy;
#
use i3test;
-my $i3 = i3(get_socket_path());
-
my $x = X11::XCB::Connection->new;
my $tmp = fresh_workspace;
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my $win = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30),
- background_color => '#C0C0C0',
-);
-
+my $win = open_window($x, { dont_map => 1 });
# XXX: we should check screen size. in screens with an AR of 2.0,
# this is not a good idea.
my $aspect = X11::XCB::Sizehints::Aspect->new;
$aspect->max_den(300);
$win->_create;
$win->map;
-sleep 0.25;
+wait_for_map $x;
$win->hints->aspect($aspect);
$x->flush;
-sleep 0.25;
+sync_with_i3($x);
my $rect = $win->rect;
my $ar = $rect->width / $rect->height;
# 1: see if focus stays the same when toggling tiling/floating mode
#############################################################################
-my $first = open_standard_window($x);
-my $second = open_standard_window($x);
+my $first = open_window($x);
+my $second = open_window($x);
is($x->input_focus, $second->id, 'second window focused');
$tmp = fresh_workspace;
-$first = open_standard_window($x); # window 2
-$second = open_standard_window($x); # window 3
-my $third = open_standard_window($x); # window 4
+$first = open_window($x); # window 2
+$second = open_window($x); # window 3
+my $third = open_window($x); # window 4
is($x->input_focus, $third->id, 'last container focused');
cmd '[id="' . $second->id . '"] focus';
+sync_with_i3($x);
+
is($x->input_focus, $second->id, 'second con focused');
cmd 'floating enable';
# now kill the third one (it's floating). focus should stay unchanged
cmd '[id="' . $third->id . '"] kill';
-sleep 0.25;
+# TODO: wait for unmapnotify
+sync_with_i3($x);
is($x->input_focus, $second->id, 'second con still focused after killing third');
$tmp = fresh_workspace;
-$first = open_standard_window($x, '#ff0000'); # window 5
-$second = open_standard_window($x, '#00ff00'); # window 6
-my $third = open_standard_window($x, '#0000ff'); # window 7
+$first = open_window($x, '#ff0000'); # window 5
+$second = open_window($x, '#00ff00'); # window 6
+my $third = open_window($x, '#0000ff'); # window 7
is($x->input_focus, $third->id, 'last container focused');
cmd '[id="' . $second->id . '"] focus';
+sync_with_i3($x);
+
is($x->input_focus, $second->id, 'second con focused');
cmd 'floating enable';
# also floating
cmd 'kill';
-sleep 0.25;
+# TODO: wait for unmapnotify
+sync_with_i3($x);
is($x->input_focus, $third->id, 'third con focused');
cmd 'kill';
-
-sleep 0.25;
+# TODO: wait for unmapnotify
+sync_with_i3($x);
is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
$tmp = fresh_workspace;
-$first = open_standard_window($x, '#ff0000'); # window 5
+$first = open_window($x, { background_color => '#ff0000' }); # window 5
cmd 'split v';
cmd 'layout stacked';
-$second = open_standard_window($x, '#00ff00'); # window 6
-$third = open_standard_window($x, '#0000ff'); # window 7
+$second = open_window($x, { background_color => '#00ff00' }); # window 6
+$third = open_window($x, { background_color => '#0000ff' }); # window 7
is($x->input_focus, $third->id, 'last container focused');
cmd '[id="' . $second->id . '"] focus';
+sync_with_i3($x);
+
is($x->input_focus, $second->id, 'second con focused');
cmd 'floating enable';
-sleep 0.5;
+sync_with_i3($x);
# now kill the second one. focus should fall back to the third one, which is
# also floating
cmd 'kill';
-sleep 0.25;
+# TODO: wait for unmapnotify
+sync_with_i3($x);
-is($x->input_focus, $third->id, 'second con focused');
+is($x->input_focus, $third->id, 'third con focused');
cmd 'kill';
-
-sleep 0.25;
+# TODO: wait for unmapnotify
+sync_with_i3($x);
is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
$tmp = fresh_workspace;
-$first = open_standard_window($x, '#ff0000'); # window 8
-$second = open_standard_window($x, '#00ff00'); # window 9
+$first = open_window($x, { background_color => '#ff0000' }); # window 8
+$second = open_window($x, { background_color => '#00ff00' }); # window 9
+
+sync_with_i3($x);
is($x->input_focus, $second->id, 'second container focused');
cmd 'focus tiling';
-sleep 0.25;
+sync_with_i3($x);
is($x->input_focus, $first->id, 'first (tiling) container focused');
cmd 'focus floating';
-sleep 0.25;
+sync_with_i3($x);
is($x->input_focus, $second->id, 'second (floating) container focused');
cmd 'focus floating';
-sleep 0.25;
+sync_with_i3($x);
is($x->input_focus, $second->id, 'second (floating) container still focused');
cmd 'focus mode_toggle';
-sleep 0.25;
+sync_with_i3($x);
is($x->input_focus, $first->id, 'first (tiling) container focused');
cmd 'focus mode_toggle';
-sleep 0.25;
+sync_with_i3($x);
is($x->input_focus, $second->id, 'second (floating) container focused');
+#############################################################################
+# 6: see if switching floating focus using the focus left/right command works
+#############################################################################
+
+$tmp = fresh_workspace;
+
+$first = open_floating_window($x, { background_color => '#ff0000' });# window 10
+$second = open_floating_window($x, { background_color => '#00ff00' }); # window 11
+$third = open_floating_window($x, { background_color => '#0000ff' }); # window 12
+
+sync_with_i3($x);
+
+is($x->input_focus, $third->id, 'third container focused');
+
+cmd 'focus left';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second container focused');
+
+cmd 'focus left';
+
+sync_with_i3($x);
+
+is($x->input_focus, $first->id, 'first container focused');
+
+cmd 'focus left';
+
+sync_with_i3($x);
+
+is($x->input_focus, $third->id, 'focus wrapped to third container');
+
+cmd 'focus right';
+
+sync_with_i3($x);
+
+is($x->input_focus, $first->id, 'focus wrapped to first container');
+
+cmd 'focus right';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'focus on second container');
done_testing;
my $x = X11::XCB::Connection->new;
# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#C0C0C0',
- # replace the type with 'utility' as soon as the coercion works again in X11::XCB
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
+my $window = open_floating_window($x);
ok($window->mapped, 'Window is mapped');
# switch to a different workspace, see if the window is still mapped?
my $x = X11::XCB::Connection->new;
# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#C0C0C0',
- # replace the type with 'utility' as soon as the coercion works again in X11::XCB
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
+my $window = open_floating_window($x);
ok($window->mapped, 'Window is mapped');
# switch to a different workspace, see if the window is still mapped?
my $otmp = fresh_workspace;
-sleep 0.25;
+sync_with_i3($x);
ok(!$window->mapped, 'Window is not mapped after switching ws');
use i3test;
use X11::XCB qw(:all);
-use Time::HiRes qw(sleep);
BEGIN {
use_ok('X11::XCB::Window');
my $x = X11::XCB::Connection->new;
# Create a floating window
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#C0C0C0',
- # replace the type with 'utility' as soon as the coercion works again in X11::XCB
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
+my $window = open_floating_window($x);
ok($window->mapped, 'Window is mapped');
my $ws = get_ws($tmp);
is(@{$nodes}, 0, 'no tiling nodes');
# Create a tiling window
-my $twindow = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#C0C0C0',
-);
-
-isa_ok($twindow, 'X11::XCB::Window');
-
-$twindow->map;
-
-sleep 0.25;
+my $twindow = open_window($x);
($nodes, $focus) = get_ws_content($tmp);
$tmp = fresh_workspace;
-my $first = open_standard_window($x);
-my $second = open_standard_window($x);
+my $first = open_window($x);
+my $second = open_window($x);
cmd 'layout stacked';
is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
# Create a floating window
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#C0C0C0',
- # replace the type with 'utility' as soon as the coercion works again in X11::XCB
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
+my $window = open_floating_window($x);
ok($window->mapped, 'Window is mapped');
$ws = get_ws($tmp);
is(@{$ws->{floating_nodes}}, 1, 'one floating nodes');
is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
-my $third = open_standard_window($x);
+my $third = open_window($x);
$ws = get_ws($tmp);
cmd "workspace 93";
-open_standard_window($x);
+open_window($x);
my @ws = @{$i3->get_workspaces->recv};
my @f = grep { defined($_->{num}) && $_->{num} == 93 } @ws;
check_order('workspace order alright after opening 93');
cmd "workspace 92";
-open_standard_window($x);
+open_window($x);
check_order('workspace order alright after opening 92');
cmd "workspace 94";
-open_standard_window($x);
+open_window($x);
check_order('workspace order alright after opening 94');
cmd "workspace 96";
-open_standard_window($x);
+open_window($x);
check_order('workspace order alright after opening 96');
cmd "workspace foo";
-open_standard_window($x);
+open_window($x);
check_order('workspace order alright after opening foo');
cmd "workspace 91";
-open_standard_window($x);
+open_window($x);
check_order('workspace order alright after opening 91');
done_testing;
# bug introduced by 77d0d42ed2d7ac8cafe267c92b35a81c1b9491eb
use i3test;
use X11::XCB qw(:all);
-use Time::HiRes qw(sleep);
BEGIN {
use_ok('X11::XCB::Window');
my $tmp = fresh_workspace;
-my $left = open_standard_window($x);
-sleep 0.25;
-my $mid = open_standard_window($x);
-sleep 0.25;
-my $right = open_standard_window($x);
-sleep 0.25;
+my $left = open_window($x);
+my $mid = open_window($x);
+my $right = open_window($x);
+
+sync_with_i3($x);
diag("left = " . $left->id . ", mid = " . $mid->id . ", right = " . $right->id);
cmd 'split v';
-my $top = open_standard_window($x);
-sleep 0.25;
-my $bottom = open_standard_window($x);
-sleep 0.25;
+my $top = open_window($x);
+my $bottom = open_window($x);
+
+sync_with_i3($x);
diag("top = " . $top->id . ", bottom = " . $bottom->id);
cmd 'split v';
-$top = open_standard_window($x);
-sleep 0.25;
-$bottom = open_standard_window($x);
-sleep 0.25;
+$top = open_window($x);
+$bottom = open_window($x);
cmd 'split h';
cmd 'layout stacked';
$tmp = fresh_workspace;
-$top = open_standard_window($x);
-sleep 0.25;
+$top = open_window($x);
cmd 'floating enable';
my $tmp = fresh_workspace;
-my $left = open_standard_window($x);
-sleep 0.25;
-my $mid = open_standard_window($x);
-sleep 0.25;
-my $right = open_standard_window($x);
-sleep 0.25;
+my $left = open_window($x);
+my $mid = open_window($x);
+my $right = open_window($x);
cmd 'move before v';
cmd 'move after h';
# vim:ts=4:sw=4:expandtab
#
use X11::XCB qw(:all);
-use Time::HiRes qw(sleep);
use i3test;
BEGIN {
my $tmp = fresh_workspace;
-my $left = open_standard_window($x);
-sleep 0.25;
-my $mid = open_standard_window($x);
-sleep 0.25;
+my $left = open_window($x);
+my $mid = open_window($x);
cmd 'split v';
-my $bottom = open_standard_window($x);
-sleep 0.25;
+my $bottom = open_window($x);
my ($nodes, $focus) = get_ws_content($tmp);
# 1: open a floating window, get it mapped
#############################################################################
-my $x = X11::XCB::Connection->new;
-
# Create a floating window
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#C0C0C0',
- # replace the type with 'utility' as soon as the coercion works again in X11::XCB
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
+my $window = open_floating_window($x);
ok($window->mapped, 'Window is mapped');
($nodes, $focus) = get_ws_content($tmp);
my $tmp = fresh_workspace;
-my $left = open_standard_window($x);
-sleep 0.25;
-my $mid = open_standard_window($x);
-sleep 0.25;
-my $right = open_standard_window($x);
-sleep 0.25;
+my $left = open_window($x);
+my $mid = open_window($x);
+my $right = open_window($x);
# go to workspace level
cmd 'level up';
my $tmp = fresh_workspace;
# open a tiling window on the first workspace
-open_standard_window($x);
-sleep 0.25;
+open_window($x);
+#sleep 0.25;
my $first = get_focused($tmp);
# on a different ws, open a floating window
my $otmp = fresh_workspace;
-open_standard_window($x);
-sleep 0.25;
+open_window($x);
+#sleep 0.25;
my $float = get_focused($otmp);
cmd 'mode toggle';
-sleep 0.25;
+#sleep 0.25;
# move the floating con to first workspace
cmd "move workspace $tmp";
-sleep 0.25;
+#sleep 0.25;
# switch to the first ws and check focus
is(get_focused($tmp), $float, 'floating client correctly focused');
}
my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace;
# open a dock client
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->map;
-
-sleep 0.25;
+my $window = open_window($x, {
+ background_color => '#FF0000',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
#####################################################################
# check that we can find it in the layout tree at the expected position
$window->destroy;
-sleep 0.25;
+wait_for_unmap $x;
@docked = get_dock_clients;
is(@docked, 0, 'no dock clients found');
# create a dock client with a 1px border
#####################################################################
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- border => 1,
- rect => [ 0, 0, 30, 20],
- background_color => '#00FF00',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->map;
-
-sleep 0.25;
+$window = open_window($x, {
+ border => 1,
+ rect => [ 0, 0, 30, 20 ],
+ background_color => '#00FF00',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
@docked = get_dock_clients;
is(@docked, 1, 'one dock client found');
my $x = X11::XCB::Connection->new;
# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 400, 150],
- background_color => '#C0C0C0',
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
+my $window = open_window($x, { rect => [ 0, 0, 400, 150 ] });
my ($absolute, $top) = $window->rect;
cmp_ok($absolute->{height}, '>', 150, 'i3 raised the height');
cmd 'floating toggle';
-sleep 0.25;
+sync_with_i3($x);
($absolute, $top) = $window->rect;
}
my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace;
# open a dock client
#####################################################################
-my $first = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$first->map;
-
-sleep 0.25;
+my $first = open_window($x, {
+ background_color => '#FF0000',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
#####################################################################
# Open a second dock client
#####################################################################
-my $second = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$second->map;
-
-sleep 0.25;
+my $second = open_window($x, {
+ background_color => '#FF0000',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
#####################################################################
# Kill the second dock client
}
my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace;
# open a window with 200x80
#####################################################################
-my $first = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 200, 80],
- background_color => '#FF0000',
-);
-
-$first->map;
-
-sleep 0.25;
+my $first = open_window($x, {
+ rect => [ 0, 0, 200, 80],
+ background_color => '#FF0000',
+ });
#####################################################################
# Open a second window with 300x90
#####################################################################
-my $second = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 300, 90],
- background_color => '#00FF00',
-);
-
-$second->map;
-
-sleep 0.25;
+my $second = open_window($x, {
+ rect => [ 0, 0, 300, 90],
+ background_color => '#00FF00',
+ });
#####################################################################
# Set the parent to floating
# open the left window
#####################################################################
-my $left = open_standard_window($x, '#ff0000');
+my $left = open_window($x, { background_color => '#ff0000' });
is($x->input_focus, $left->id, 'left window focused');
# Open the right window
#####################################################################
-my $right = open_standard_window($x, '#00ff00');
+my $right = open_window($x, { background_color => '#00ff00' });
diag("right = " . $right->id);
# Open a third window
#####################################################################
-my $third = open_standard_window($x, '#0000ff');
+my $third = open_window($x, {
+ background_color => '#0000ff',
+ name => 'Third window',
+ dont_map => 1,
+ });
+
+$third->map;
+
+sync_with_i3 $x;
diag("third = " . $third->id);
# verify that the third window has the focus
-sleep 0.25;
+sync_with_i3($x);
is($x->input_focus, $third->id, 'third window focused');
}
my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace;
# open a window, verify it’s not in fullscreen mode
#####################################################################
-my $win = open_standard_window($x);
+my $win = open_window($x);
my $nodes = get_ws_content $tmp;
is(@$nodes, 1, 'exactly one client');
use i3test;
use v5.10;
-BEGIN {
- use_ok('EV');
- use_ok('AnyEvent');
- use_ok('X11::XCB::Window');
- use_ok('X11::XCB::Event::Generic');
- use_ok('X11::XCB::Event::MapNotify');
- use_ok('X11::XCB::Event::ClientMessage');
-}
-
my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
subtest 'Window without WM_TAKE_FOCUS', sub {
+ fresh_workspace;
- my $tmp = fresh_workspace;
-
- my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
- event_mask => [ 'structure_notify' ],
- );
-
- $window->name('Window 1');
- $window->map;
-
- my $cv = AE::cv;
-
- my $prep = EV::prepare sub {
- $x->flush;
- };
-
- my $check = EV::check sub {
- while (defined(my $event = $x->poll_for_event)) {
- if ($event->response_type == 161) {
- # clientmessage
- $cv->send(0);
- }
- }
- };
+ my $window = open_window($x);
- my $w = EV::io $x->get_file_descriptor, EV::READ, sub {
- # do nothing, we only need this watcher so that EV picks up the events
- };
-
- # Trigger timeout after 1 second
- my $t = AE::timer 1, 0, sub {
- $cv->send(1);
- };
-
- my $result = $cv->recv;
- ok($result, 'cv result');
+ ok(!wait_for_event($x, 1, sub { $_[0]->{response_type} == 161 }), 'did not receive ClientMessage');
done_testing;
};
subtest 'Window with WM_TAKE_FOCUS', sub {
+ fresh_workspace;
- my $tmp = fresh_workspace;
+ my $take_focus = $x->atom(name => 'WM_TAKE_FOCUS');
- my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
- event_mask => [ 'structure_notify' ],
- protocols => [ $x->atom(name => 'WM_TAKE_FOCUS') ],
- );
+ my $window = open_window($x, {
+ dont_map => 1,
+ protocols => [ $take_focus ],
+ });
- $window->name('Window 1');
$window->map;
- my $cv = AE::cv;
-
- my $prep = EV::prepare sub {
- $x->flush;
- };
-
- my $check = EV::check sub {
- while (defined(my $event = $x->poll_for_event)) {
- if ($event->response_type == 161) {
- $cv->send($event->data);
- }
- }
- };
-
- my $w = EV::io $x->get_file_descriptor, EV::READ, sub {
- # do nothing, we only need this watcher so that EV picks up the events
- };
-
- my $t = AE::timer 1, 0, sub {
- say "timer!";
- $cv->send(undef);
- };
-
- my $result = $cv->recv;
- ok(defined($result), 'got a ClientMessage');
- if (defined($result)) {
- my ($data, $time) = unpack("L2", $result);
- is($data, $x->atom(name => 'WM_TAKE_FOCUS')->id, 'first uint32_t contains WM_TAKE_FOCUS atom');
- }
+ ok(wait_for_event($x, 1, sub {
+ return 0 unless $_[0]->{response_type} == 161;
+ my ($data, $time) = unpack("L2", $_[0]->{data});
+ return ($data == $take_focus->id);
+ }), 'got ClientMessage with WM_TAKE_FOCUS atom');
done_testing;
};
# default case: socket will be created in /tmp/i3-<username>/ipc-socket.<pid>
#####################################################################
-my ($fh, $tmpfile) = tempfile();
+my ($fh, $tmpfile) = tempfile('/tmp/i3-test-config.XXXXXX', UNLINK => 1);
say $fh "# i3 config file (v4)";
say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
close($fh);
$socketpath = $tmpdir . "/config.sock";
ok(! -e $socketpath, "$socketpath does not exist yet");
-($fh, $tmpfile) = tempfile();
+($fh, $tmpfile) = tempfile('/tmp/i3-test-config.XXXXXX', UNLINK => 1);
say $fh "# i3 config file (v4)";
say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
say $fh "ipc-socket $socketpath";
my $x = X11::XCB::Connection->new;
my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace;
-my $window = open_standard_window($x);
+my $window = open_window($x);
sub get_border_style {
my @content = @{get_ws_content($tmp)};
$window->add_hint('urgency');
-sleep 0.25;
+sync_with_i3($x);
does_i3_live;
use X11::XCB qw(:all);
use i3test;
-BEGIN {
- use_ok('X11::XCB::Window');
- use_ok('X11::XCB::Event::Generic');
- use_ok('X11::XCB::Event::MapNotify');
- use_ok('X11::XCB::Event::ClientMessage');
-}
-
my $x = X11::XCB::Connection->new;
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
- event_mask => [ 'structure_notify' ],
-);
-
-$window->name('Window 1');
-$window->map;
-
-diag('window mapped');
+my $window = open_window($x);
-sleep 0.5;
+sync_with_i3($x);
is($window->state, ICCCM_WM_STATE_NORMAL, 'WM_STATE normal');
$window->unmap;
-sleep 0.5;
+wait_for_unmap $x;
is($window->state, ICCCM_WM_STATE_WITHDRAWN, 'WM_STATE withdrawn');
# Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when
# unmapped.
#
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
use i3test;
my $x = X11::XCB::Connection->new;
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
- my $first = open_standard_window($x);
- my $second = open_standard_window($x);
+ my $first = open_window($x);
+ my $second = open_window($x);
+
+ sync_with_i3 $x;
is($x->input_focus, $second->id, 'second window focused');
ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
);
$window->name('Border window');
$window->map;
-sleep 0.25;
+wait_for_map $x;
my @content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
is($content[0]->{border}, 'normal', 'normal border');
$window->unmap;
-sleep 0.25;
+wait_for_unmap $x;
my @content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 0, 'no more nodes');
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
);
$window->_create;
set_wm_class($window->id, 'borderless', 'borderless');
$window->name('Borderless window');
$window->map;
-sleep 0.25;
+wait_for_map $x;
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
is($content[0]->{border}, 'none', 'no border');
$window->unmap;
-sleep 0.25;
+wait_for_unmap $x;
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 0, 'no more nodes');
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
);
$window->name('special title');
$window->map;
-sleep 0.25;
+wait_for_map $x;
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
is($content[0]->{border}, 'normal', 'normal border');
$window->name('special borderless title');
-sleep 0.25;
+sync_with_i3 $x;
@content = @{get_ws_content($tmp)};
is($content[0]->{border}, 'none', 'no border');
$window->name('special title');
-sleep 0.25;
+sync_with_i3 $x;
cmd 'border normal';
is($content[0]->{border}, 'normal', 'border reset to normal');
$window->name('special borderless title');
-sleep 0.25;
+sync_with_i3 $x;
@content = @{get_ws_content($tmp)};
is($content[0]->{border}, 'normal', 'still normal border');
$window->unmap;
-sleep 0.25;
+wait_for_unmap $x;
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 0, 'no more nodes');
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
);
$window->name('special mark title');
$window->map;
-sleep 0.25;
+wait_for_map $x;
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
is($content[0]->{border}, 'none', 'no border');
-my $other = open_standard_window($x);
+my $other = open_window($x);
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 2, 'two nodes');
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
);
$window->_create;
set_wm_class($window->id, 'borderless', 'borderless');
$window->name('usethis');
$window->map;
-sleep 0.25;
+wait_for_map $x;
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
is($content[0]->{border}, 'none', 'no border');
$window->unmap;
-sleep 0.25;
+wait_for_unmap $x;
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 0, 'no nodes on this workspace now');
set_wm_class($window->id, 'borderless', 'borderless');
$window->name('notthis');
$window->map;
-sleep 0.25;
+wait_for_map $x;
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
);
$window->_create;
set_wm_class($window->id, 'bar', 'foo');
$window->name('usethis');
$window->map;
-sleep 0.25;
+wait_for_map $x;
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
);
$window->_create;
set_wm_class($window->id, 'bar', 'foo');
$window->name('usethis');
$window->map;
-sleep 0.25;
+wait_for_map $x;
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
);
$window->_create;
set_wm_class($window->id, 'bar', 'foo');
$window->name('usethis');
$window->map;
-sleep 0.25;
+wait_for_map $x;
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
exit_gracefully($process->pid);
+##############################################################
+# 8: check that the role criterion works properly
+##############################################################
+
+# this configuration is broken because "asdf" is not a valid integer
+# the for_window should therefore recognize this error and don’t add the
+# assignment
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [window_role="i3test"] border none
+EOT
+
+$process = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+
+my $atomname = $x->atom(name => 'WM_WINDOW_ROLE');
+my $atomtype = $x->atom(name => 'STRING');
+$x->change_property(
+ PROP_MODE_REPLACE,
+ $window->id,
+ $atomname->id,
+ $atomtype->id,
+ 8,
+ length("i3test") + 1,
+ "i3test\x00"
+);
+
+$window->name('usethis');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'none', 'no border (window_role)');
+
+exit_gracefully($process->pid);
+
+##############################################################
+# 9: another test for the window_role, but this time it changes
+# *after* the window has been mapped
+##############################################################
+
+# this configuration is broken because "asdf" is not a valid integer
+# the for_window should therefore recognize this error and don’t add the
+# assignment
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [window_role="i3test"] border none
+EOT
+
+$process = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+
+$window->name('usethis');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'normal', 'normal border (window_role 2)');
+
+$atomname = $x->atom(name => 'WM_WINDOW_ROLE');
+$atomtype = $x->atom(name => 'STRING');
+$x->change_property(
+ PROP_MODE_REPLACE,
+ $window->id,
+ $atomname->id,
+ $atomtype->id,
+ 8,
+ length("i3test") + 1,
+ "i3test\x00"
+);
+
+$x->flush;
+
+sync_with_i3 $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'none', 'no border (window_role 2)');
+
+exit_gracefully($process->pid);
+
done_testing;
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
);
$window->_create;
set_wm_class($window->id, 'special', 'special');
$window->name('special window');
$window->map;
-sleep 0.25;
+wait_for_map $x;
ok(@{get_ws_content($tmp)} == 1, 'special window got managed to current (random) workspace');
$window->destroy;
-sleep 0.25;
-
#####################################################################
# start a window and see that it gets assigned to a formerly unused
# workspace
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
);
$window->_create;
set_wm_class($window->id, 'special', 'special');
$window->name('special window');
$window->map;
-sleep 0.25;
+wait_for_map $x;
ok(@{get_ws_content($tmp)} == 0, 'still no containers');
ok("targetws" ~~ @{get_workspace_names()}, 'targetws exists');
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
);
$window->_create;
set_wm_class($window->id, 'special', 'special');
$window->name('special window');
$window->map;
-sleep 0.25;
+
+# We use sync_with_i3 instead of wait_for_map here because i3 will not actually
+# map the window -- it will be assigned to a different workspace and will only
+# be mapped once you switch to that workspace
+sync_with_i3 $x;
ok(@{get_ws_content($tmp)} == 0, 'still no containers');
ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws');
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
);
$window->_create;
set_wm_class($window->id, 'special', 'special');
$window->name('special window');
$window->map;
+wait_for_map $x;
+
+my $content = get_ws($tmp);
+ok(@{$content->{nodes}} == 0, 'no tiling cons');
+ok(@{$content->{floating_nodes}} == 1, 'one floating con');
+
+$window->destroy;
+
+exit_gracefully($process->pid);
+
sleep 0.25;
+#####################################################################
+# make sure that assignments are case-insensitive in the old syntax.
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+assign "special" → ~
+EOT
+
+$process = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+my $workspaces = get_workspace_names;
+ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
+
+my $window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+set_wm_class($window->id, 'SPEcial', 'SPEcial');
+$window->name('special window');
+$window->map;
+wait_for_map $x;
+
my $content = get_ws($tmp);
ok(@{$content->{nodes}} == 0, 'no tiling cons');
ok(@{$content->{floating_nodes}} == 1, 'one floating con');
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
my @docked = get_dock_clients;
-is(@docked, 0, 'no dock clients yet');
+# We expect i3-nagbar as the first dock client due to using the old assign
+# syntax
+is(@docked, 1, 'one dock client yet');
my $window = $x->root->create_child(
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#0000ff',
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ event_mask => [ 'structure_notify' ],
);
$window->_create;
set_wm_class($window->id, 'special', 'special');
$window->name('special window');
$window->map;
-sleep 0.25;
+wait_for_map $x;
my $content = get_ws($tmp);
ok(@{$content->{nodes}} == 0, 'no tiling cons');
ok(@{$content->{floating_nodes}} == 0, 'one floating con');
@docked = get_dock_clients;
-is(@docked, 1, 'no dock clients yet');
+is(@docked, 2, 'two dock clients now');
$window->destroy;
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my $first = open_standard_window($x);
-my $second = open_standard_window($x);
+my $first = open_window($x);
+my $second = open_window($x);
+
+sync_with_i3($x);
is($x->input_focus, $second->id, 'second window focused');
ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-$first = open_standard_window($x);
-$second = open_standard_window($x);
+$first = open_window($x);
+$second = open_window($x);
+
+sync_with_i3($x);
is($x->input_focus, $second->id, 'second window focused');
my @content = @{get_ws_content($tmp)};
#####################################################################
cmd 'focus parent';
-my $right_top = open_standard_window($x);
-my $right_bot = open_standard_window($x);
+my $right_top = open_window($x);
+my $right_bot = open_window($x);
@content = @{get_ws_content($tmp)};
is(@content, 2, 'two cons at workspace level after focus parent');
fresh_workspace;
-open_standard_window($x);
-open_standard_window($x);
+open_window($x);
+open_window($x);
cmd 'layout stacking';
sleep 1;
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my $first = open_standard_window($x);
-my $second = open_standard_window($x);
+my $first = open_window($x);
+my $second = open_window($x);
cmd 'layout tabbed';
cmd 'focus parent';
-my $third = open_standard_window($x);
+my $third = open_window($x);
is($x->input_focus, $third->id, 'third window focused');
cmd 'focus left';
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-$first = open_standard_window($x);
-$second = open_standard_window($x);
+$first = open_window($x);
+$second = open_window($x);
cmd 'layout tabbed';
cmd 'focus parent';
-$third = open_standard_window($x);
+$third = open_window($x);
+
+sync_with_i3($x);
+
is($x->input_focus, $third->id, 'third window focused');
cmd 'focus left';
sub migrate_config {
my ($config) = @_;
- my ($fh, $tmpfile) = tempfile();
+ my ($fh, $tmpfile) = tempfile('/tmp/i3-migrate-cfg.XXXXXX', UNLINK => 1);
print $fh $config;
close($fh);
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# checks if the IPC message type get_marks works correctly
+#
+use i3test;
+
+sub get_marks {
+ return i3(get_socket_path())->get_marks->recv;
+}
+
+##############################################################
+# 1: check that get_marks returns no marks yet
+##############################################################
+
+my $tmp = fresh_workspace;
+
+my $marks = get_marks();
+cmp_deeply($marks, [], 'no marks set so far');
+
+##############################################################
+# 2: check that setting a mark is reflected in the get_marks reply
+##############################################################
+
+cmd 'open';
+cmd 'mark foo';
+
+cmp_deeply(get_marks(), [ 'foo' ], 'mark foo set');
+
+##############################################################
+# 3: check that the mark is gone after killing the container
+##############################################################
+
+cmd 'kill';
+
+cmp_deeply(get_marks(), [ ], 'mark gone');
+
+##############################################################
+# 4: check that duplicate marks are included twice in the get_marks reply
+##############################################################
+
+cmd 'open';
+cmd 'mark bar';
+
+cmd 'open';
+cmd 'mark bar';
+
+cmp_deeply(get_marks(), [ 'bar', 'bar' ], 'duplicate mark found twice');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Tests the new_window and new_float config option.
+#
+
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+#####################################################################
+# 1: check that new windows start with 'normal' border unless configured
+# otherwise
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $process = launch_with_config($config);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_window($x);
+
+my @content = @{get_ws_content($tmp)};
+ok(@content == 1, 'one container opened');
+is($content[0]->{border}, 'normal', 'border normal by default');
+
+exit_gracefully($process->pid);
+
+#####################################################################
+# 2: check that new tiling windows start with '1pixel' border when
+# configured
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+new_window 1pixel
+EOT
+
+$process = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_window($x);
+
+@content = @{get_ws_content($tmp)};
+ok(@content == 1, 'one container opened');
+is($content[0]->{border}, '1pixel', 'border normal by default');
+
+exit_gracefully($process->pid);
+
+#####################################################################
+# 3: check that new floating windows start with 'normal' border unless
+# configured otherwise
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+$process = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_floating_window($x);
+
+my $wscontent = get_ws($tmp);
+my @floating = @{$wscontent->{floating_nodes}};
+ok(@floating == 1, 'one floating container opened');
+my $floatingcon = $floating[0];
+is($floatingcon->{nodes}->[0]->{border}, 'normal', 'border normal by default');
+
+exit_gracefully($process->pid);
+
+#####################################################################
+# 4: check that new floating windows start with '1pixel' border when
+# configured
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+new_float 1pixel
+EOT
+
+$process = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_floating_window($x);
+
+$wscontent = get_ws($tmp);
+@floating = @{$wscontent->{floating_nodes}};
+ok(@floating == 1, 'one floating container opened');
+$floatingcon = $floating[0];
+is($floatingcon->{nodes}->[0]->{border}, '1pixel', 'border normal by default');
+
+exit_gracefully($process->pid);
+
+done_testing;
use X11::XCB::Window;
use X11::XCB qw(:all);
use AnyEvent::I3;
+use EV;
use List::Util qw(first);
use List::MoreUtils qw(lastval);
use Time::HiRes qw(sleep);
use v5.10;
use Exporter ();
-our @EXPORT = qw(get_workspace_names get_unused_workspace fresh_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window get_dock_clients cmd does_i3_live exit_gracefully workspace_exists focused_ws get_socket_path launch_with_config);
+our @EXPORT = qw(
+ get_workspace_names
+ get_unused_workspace
+ fresh_workspace
+ get_ws_content
+ get_ws
+ get_focused
+ open_empty_con
+ open_window
+ open_floating_window
+ get_dock_clients
+ cmd
+ sync_with_i3
+ does_i3_live
+ exit_gracefully
+ workspace_exists
+ focused_ws
+ get_socket_path
+ launch_with_config
+ wait_for_event
+ wait_for_map
+ wait_for_unmap
+);
my $tester = Test::Builder->new();
my $_cached_socket_path = undef;
+my $_sync_window = undef;
my $tmp_socket_path = undef;
BEGIN {
goto \&Exporter::import;
}
-sub open_standard_window {
- my ($x, $color) = @_;
+#
+# Waits for the next event and calls the given callback for every event to
+# determine if this is the event we are waiting for.
+#
+# Can be used to wait until a window is mapped, until a ClientMessage is
+# received, etc.
+#
+# wait_for_event $x, 0.25, sub { $_[0]->{response_type} == MAP_NOTIFY };
+#
+sub wait_for_event {
+ my ($x, $timeout, $cb) = @_;
- $color ||= '#c0c0c0';
+ my $cv = AE::cv;
- my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => $color,
- );
+ my $prep = EV::prepare sub {
+ $x->flush;
+ };
- $window->name('Window ' . counter_window());
- $window->map;
+ my $check = EV::check sub {
+ while (defined(my $event = $x->poll_for_event)) {
+ if ($cb->($event)) {
+ $cv->send(1);
+ last;
+ }
+ }
+ };
+
+ my $watcher = EV::io $x->get_file_descriptor, EV::READ, sub {
+ # do nothing, we only need this watcher so that EV picks up the events
+ };
+
+ # Trigger timeout after $timeout seconds (can be fractional)
+ my $timeout = AE::timer $timeout, 0, sub { warn "timeout"; $cv->send(0) };
- sleep(0.25);
+ my $result = $cv->recv;
+ return $result;
+}
+
+# thin wrapper around wait_for_event which waits for MAP_NOTIFY
+# make sure to include 'structure_notify' in the window’s event_mask attribute
+sub wait_for_map {
+ my ($x) = @_;
+ wait_for_event $x, 1, sub { $_[0]->{response_type} == MAP_NOTIFY };
+}
+
+# Wrapper around wait_for_event which waits for UNMAP_NOTIFY. Also calls
+# sync_with_i3 to make sure i3 also picked up and processed the UnmapNotify
+# event.
+sub wait_for_unmap {
+ my ($x) = @_;
+ wait_for_event $x, 1, sub { $_[0]->{response_type} == UNMAP_NOTIFY };
+ sync_with_i3($x);
+}
+
+#
+# Opens a new window (see X11::XCB::Window), maps it, waits until it got mapped
+# and synchronizes with i3.
+#
+# set dont_map to a true value to avoid mapping
+#
+# default values:
+# class => WINDOW_CLASS_INPUT_OUTPUT
+# rect => [ 0, 0, 30, 30 ]
+# background_color => '#c0c0c0'
+# event_mask => [ 'structure_notify' ]
+# name => 'Window <n>'
+#
+sub open_window {
+ my ($x, $args) = @_;
+ my %args = ($args ? %$args : ());
+ my $dont_map = delete $args{dont_map};
+
+ $args{class} //= WINDOW_CLASS_INPUT_OUTPUT;
+ $args{rect} //= [ 0, 0, 30, 30 ];
+ $args{background_color} //= '#c0c0c0';
+ $args{event_mask} //= [ 'structure_notify' ];
+ $args{name} //= 'Window ' . counter_window();
+
+ my $window = $x->root->create_child(%args);
+
+ return $window if $dont_map;
+
+ $window->map;
+ wait_for_map($x);
+ # We sync with i3 here to make sure $x->input_focus is updated.
+ sync_with_i3($x);
return $window;
}
+# Thin wrapper around open_window which sets window_type to
+# _NET_WM_WINDOW_TYPE_UTILITY to make the window floating.
+sub open_floating_window {
+ my ($x, $args) = @_;
+ my %args = ($args ? %$args : ());
+
+ $args{window_type} = $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY');
+
+ return open_window($x, \%args);
+}
+
sub open_empty_con {
my ($i3) = @_;
}
}
+#
+# Sends an I3_SYNC ClientMessage with a random value to the root window.
+# i3 will reply with the same value, but, due to the order of events it
+# processes, only after all other events are done.
+#
+# This can be used to ensure the results of a cmd 'focus left' are pushed to
+# X11 and that $x->input_focus returns the correct value afterwards.
+#
+# See also docs/testsuite for a long explanation
+#
+sub sync_with_i3 {
+ my ($x) = @_;
+
+ # Since we need a (mapped) window for receiving a ClientMessage, we create
+ # one on the first call of sync_with_i3. It will be re-used in all
+ # subsequent calls.
+ if (!defined($_sync_window)) {
+ $_sync_window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => X11::XCB::Rect->new(x => -15, y => -15, width => 10, height => 10 ),
+ override_redirect => 1,
+ background_color => '#ff0000',
+ event_mask => [ 'structure_notify' ],
+ );
+
+ $_sync_window->map;
+
+ wait_for_event $x, 0.5, sub { $_[0]->{response_type} == MAP_NOTIFY };
+ }
+
+ my $root = $x->get_root_window();
+ # Generate a random number to identify this particular ClientMessage.
+ my $myrnd = int(rand(255)) + 1;
+
+ # Generate a ClientMessage, see xcb_client_message_t
+ my $msg = pack "CCSLLLLLLL",
+ CLIENT_MESSAGE, # response_type
+ 32, # format
+ 0, # sequence
+ $root, # destination window
+ $x->atom(name => 'I3_SYNC')->id,
+
+ $_sync_window->id, # data[0]: our own window id
+ $myrnd, # data[1]: a random value to identify the request
+ 0,
+ 0,
+ 0;
+
+ # Send it to the root window -- since i3 uses the SubstructureRedirect
+ # event mask, it will get the ClientMessage.
+ $x->send_event(0, $root, EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
+
+ # now wait until the reply is here
+ return wait_for_event $x, 1, sub {
+ my ($event) = @_;
+ # TODO: const
+ return 0 unless $event->{response_type} == 161;
+
+ my ($win, $rnd) = unpack "LL", $event->{data};
+ return ($rnd == $myrnd);
+ };
+}
+
sub does_i3_live {
my $tree = i3(get_socket_path())->get_tree->recv;
my @nodes = @{$tree->{nodes}};
if (!$exited) {
kill(9, $pid) or die "could not kill i3";
}
+
+ if ($socketpath =~ m,^/tmp/i3-test-socket-,) {
+ unlink($socketpath);
+ }
}
# Gets the socket path from the I3_SOCKET_PATH atom stored on the X11 root window
say $fh "ipc-socket $tmp_socket_path";
close($fh);
- my $i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
+ # Use $ENV{LOGPATH}, gets set in complete-run.pl. We append instead of
+ # overwrite because there might be multiple instances of i3 running during
+ # one test case.
+ my $i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >>$ENV{LOGPATH} 2>&1";
my $process = Proc::Background->new($i3cmd);
- sleep 1;
+ sleep 1.25;
# force update of the cached socket path in lib/i3test
get_socket_path(0);