Merge branch 'next'

This commit is contained in:
Michael Stapelberg 2012-12-12 00:18:23 +01:00
commit 2bf7793d4d
151 changed files with 6960 additions and 1271 deletions

1
.gitignore vendored
View File

@ -14,6 +14,7 @@ testcases/_Inline
testcases/inc
testcases/META.yml
test.commands_parser
test.config_parser
*.output
*.tab.*
*.yy.c

View File

@ -30,7 +30,7 @@ dist: distclean
[ ! -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 generate-command-parser.pl i3-sensible-* i3.config.keycodes DEPENDS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.xsession.desktop i3.applications.desktop pseudo-doc.doxygen common.mk Makefile i3-${VERSION}
cp i3-migrate-config-to-v4 generate-command-parser.pl i3-sensible-* i3-dmenu-desktop i3.config.keycodes DEPENDS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.xsession.desktop i3.applications.desktop pseudo-doc.doxygen common.mk Makefile i3-${VERSION}
cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar i3-dump-log yajl-fallback include man parser-specs testcases i3-${VERSION}
# Only copy toplevel documentation (important stuff)
mkdir i3-${VERSION}/docs

View File

@ -22,7 +22,7 @@ We also made the orientation (horizontal/vertical) part of the layout
To change a splith container into a splitv container, use either "layout
splitv" or "layout toggle split". The latter command is used in the
default config as mod+l (formerly "layout default"). In case you have
default config as mod+e (formerly "layout default"). In case you have
"layout default" in your config file, it is recommended to just replace
it by "layout toggle split", which will work as "layout default" did
before when pressing it once, but toggle between horizontal/vertical

107
RELEASE-NOTES-4.4 Normal file
View File

@ -0,0 +1,107 @@
┌──────────────────────────────┐
│ Release notes for i3 v4.4 │
└──────────────────────────────┘
This is the i3 v4.4. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
An important under-the-hood change is that we now use the same parser
infrastructure for the configuration file as we do for the commands. This
makes maintenance and contributions easier and lets us finally escape the
insanity that is bison/flex.
In case there is a bug and your existing config does not work as expected
anymore, try using the --force-old-config-parser-v4.4-only flag when starting
i3 and please report a bug. This option will only be present in v4.4, so if
you dont report a bug, you are willingly breaking your own config file.
Apart from that, there have been several little fixes and additions which make
i3 pay more attention to detail, particularly in the floating window area of
the code. See the changes/bugfixes list for more information.
┌────────────────────────────┐
│ Changes in v4.4 │
└────────────────────────────┘
• add i3-dmenu-desktop, a dmenu wrapper which parses application .desktop
files and executes them.
• also use a custom parser for the config file
• i3.xsession.desktop is now standards-compliant
• ipc: you can now subscribe to an event called 'mode' (for binding modes)
• implement "move container to workspace back_and_forth"
• implement delayed urgency hint reset
• make "move workspace number" accept a default workspace name after the
number
• i3bar: allow child to specify start/stop signals to use in hide mode
• i3bar: add "urgent" to protocol, it unhides i3bar when in hide mode
• make parent of urgent containers also urgent
• add descriptive title to split containers (no more "another container")
• click to focus: clicking the root window focuses the relevant workspace
• display appropriate cursors when resizing or moving floating windows
• implement variable border widths for pixel/normal
• Implement moving workspaces as if theyre regular containers
• Maintain relative positioning when moving floating windows between outputs
• Focus the relevant workspace when clicking any container
• docs/ipc: remove unnecessary newline
• docs/ipc: add a warning to use an existing library
• shmlog: remove O_TRUNC flag for shm_open, we truncate on our own
• un-fullscreen as needed when moving fullscreen containers
• improve startup sequence termination conditions
• allow floating cons to be reached using 'focus parent'
• grab keys with all permutations of lock and numlock
• allow workspace contents to be moved if there are only floating children
• allow 'focus <direction>' to move out of non-global fullscreen containers
• exit with a proper error message when there are no outputs available
• skip floating cons in focus <child|parent> and stop them from being split
• focus windows when middle-clicking
• skip floating windows in the focus stack when moving through the tree
• docs/userguide: use $mod consistently
• keycode default config: s/bindcode/bindsym/
• implement smart popup_during_fullscreen mode
• docs/testsuite: add "installing the dependencies" section
• introduce new command to rename focused workspace
• libi3: use "pango:" prefix instead of "xft:" to avoid confusion
• ipc: add "current" and "old" containers to workspace events
• i3bar: add current binding mode indicator
• resizing floating windows now obeys the minimum/maximum size
• docs/userguide: document new_float option
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• Bugfix: get_output_next() now works with non-aligned RandR setups
• Bugfix: close empty workspaces after cross-output move
• Bugfix: fix bottom line of tabbed decoration not continuous
• Bugfix: use correct coordinates for windows which are opened on a newly
created workspace due to assignments
• Bugfix: properly react to windows being unmapped before we can reparent
• Bugfix: send non-floating window with floating parent to scratchpad
• docs/userguide: document how to "un-scratchpad" a window
• Bugfix: dont crash when dragged floating window closes
• Bugfix: draw h-split indicator at the correct position
• make the resize command honor criteria
• Bugfix: with one ws per output, dont crash on cross-output moves
• Bugfix: correctly move floating windows to invisible workspaces
cross-output
• Bugfix: set workspace_layout in create_workspace_on_output
• fix fullscreen focus bug and corresponding test flaw
• i3bar: bugfix: dont send workspace command when at beginning/end of workspace
• Bugfix: force rendering when the parents orientation changed
• Bugfix: fix workspace back_and_forth after displaying a scratchpad window
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
Adrien Schildknecht, aksr, bitonic, chrysn, Conley Moorhous, darkraven, Deiz,
Emil Mikulic, Feh, flo, Francesco Mazzoli, hax404, joepd, Kacper Kowalik,
Markus, meaneye, Merovius, Michael Walle, moju, Moritz, noxxun, Oliver
Kiddle, Pauli Ervi, Pavel Löbl, Piotr, pkordy, Quentin Glidic, Sascha Kruse,
Sebastian Ullrich, Simon Elsbrock, slowpoke, strcat, Tblue, Tim, whitequark,
xeen, Yaroslav Molochko
-- Michael Stapelberg, 2012-12-12

View File

@ -195,6 +195,7 @@ ifeq ($(V),0)
# echo-ing vars
V_ASCIIDOC = echo ASCIIDOC $@;
V_POD2HTML = echo POD2HTML $@;
V_POD2MAN = echo POD2MAN $@;
V_A2X = echo A2X $@;
endif

View File

@ -1,6 +1,12 @@
#!/usr/bin/env perl
# vim:ts=4:sw=4:expandtab
# renders the layout tree using asymptote
#
# ./dump-asy.pl
# will render the entire tree
# ./dump-asy.pl 'name'
# will render the tree starting from the node with the specified name,
# e.g. ./dump-asy.pl 2 will render workspace 2 and below
use strict;
use warnings;
@ -9,7 +15,7 @@ use AnyEvent::I3;
use File::Temp;
use v5.10;
my $i3 = i3("/tmp/nestedcons");
my $i3 = i3();
my $tree = $i3->get_tree->recv;
@ -26,7 +32,13 @@ sub dump_node {
my $w = (defined($n->{window}) ? $n->{window} : "N");
my $na = $n->{name};
$na =~ s/#/\\#/g;
my $name = "($na, $o, $w)";
$na =~ s/_/\\_/g;
$na =~ s/~/\\textasciitilde{}/g;
my $type = 'leaf';
if (!defined($n->{window})) {
$type = $n->{orientation} . '-split';
}
my $name = qq|\\"$na\\" ($type)|;
print $tmp "TreeNode n" . $n->{id} . " = makeNode(";
@ -36,10 +48,29 @@ sub dump_node {
dump_node($_, $n) for @{$n->{nodes}};
}
dump_node($tree);
say $tmp "draw(n" . $tree->{id} . ", (0, 0));";
sub find_node_with_name {
my ($node, $name) = @_;
return $node if ($node->{name} eq $name);
for my $child (@{$node->{nodes}}) {
my $res = find_node_with_name($child, $name);
return $res if defined($res);
}
return undef;
}
my $start = shift;
my $root;
if ($start) {
# Find the specified node in the tree
$root = find_node_with_name($tree, $start);
} else {
$root = $tree;
}
dump_node($root);
say $tmp "draw(n" . $root->{id} . ", (0, 0));";
close($tmp);
my $rep = "$tmp";
$rep =~ s/asy$/eps/;
system("cd /tmp && asy $tmp && gv $rep && rm $rep");
system("cd /tmp && asy $tmp && gv --scale=-1000 --noresize --widgetless $rep && rm $rep");

12
debian/changelog vendored
View File

@ -1,8 +1,14 @@
i3-wm (4.2.1-0) unstable; urgency=low
i3-wm (4.4-1) experimental; urgency=low
* NOT YET RELEASED
* New upstream release
-- Michael Stapelberg <michael@stapelberg.de> Fri, 27 Jan 2012 19:34:11 +0000
-- Michael Stapelberg <stapelberg@debian.org> Tue, 11 Dec 2012 22:52:56 +0100
i3-wm (4.3-1) experimental; urgency=low
* New upstream release
-- Michael Stapelberg <stapelberg@debian.org> Wed, 19 Sep 2012 18:13:40 +0200
i3-wm (4.2-1) unstable; urgency=low

1
debian/i3-wm.docs vendored
View File

@ -1,5 +1,4 @@
docs/debugging.html
docs/debugging-release-version.html
docs/hacking-howto.html
docs/i3bar-protocol.html
docs/userguide.html

View File

@ -8,4 +8,5 @@ man/i3-migrate-config-to-v4.1
man/i3-sensible-pager.1
man/i3-sensible-editor.1
man/i3-sensible-terminal.1
man/i3-dmenu-desktop.1
man/i3bar.1

View File

@ -1,3 +1,11 @@
Description: list x-terminal-emulator as one of i3-sensible-terminals choices
Author: Michael Stapelberg <stapelberg@debian.org>
Origin: vendor
Forwarded: not-needed
Last-Update: 2011-12-28
---
Index: i3-4.1.1/man/i3-sensible-terminal.man
===================================================================
--- i3-4.1.1.orig/man/i3-sensible-terminal.man 2011-12-28 23:56:55.487581000 +0100

View File

@ -1,7 +1,15 @@
Index: i3-4.1.1/i3-sensible-terminal
Description: i3-sensible-terminal: try x-terminal-emulator first
Author: Michael Stapelberg <stapelberg@debian.org>
Origin: vendor
Forwarded: not-needed
Last-Update: 2012-09-19
---
Index: i3-4.3/i3-sensible-terminal
===================================================================
--- i3-4.1.1.orig/i3-sensible-terminal 2011-12-28 23:51:52.455610236 +0100
+++ i3-4.1.1/i3-sensible-terminal 2011-12-28 23:52:00.826775027 +0100
--- i3-4.3.orig/i3-sensible-terminal 2012-09-19 18:08:09.000000000 +0200
+++ i3-4.3/i3-sensible-terminal 2012-09-19 18:32:06.393883488 +0200
@@ -4,11 +4,7 @@
#
# This script tries to exec a terminal emulator by trying some known terminal
@ -10,8 +18,8 @@ Index: i3-4.1.1/i3-sensible-terminal
-# 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.
-for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm; do
+for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm; do
-for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do
+for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do
if which $terminal > /dev/null 2>&1; then
exec $terminal "$@"
fi

2
debian/rules vendored
View File

@ -38,7 +38,7 @@ override_dh_auto_build:
$(MAKE) -C docs
override_dh_installchangelogs:
dh_installchangelogs RELEASE-NOTES-4.2
dh_installchangelogs RELEASE-NOTES-4.4
override_dh_install:
$(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install

View File

@ -1,14 +1,14 @@
Debugging i3: How To
====================
Michael Stapelberg <michael@i3wm.org>
February 2012
December 2012
This document describes how to debug i3 suitably for sending us useful bug
reports, even if you have no clue of C programming.
This document describes how to debug i3 to send us useful bug
reports, even if you have no knowledge of C programming.
First of all: Thank you for being interested in debugging i3. It really means
Thank you for being interested in debugging i3. It really means
something to us to get your bug fixed. If you have any questions about the
debugging and/or need further help, do not hesitate to contact us!
process and/or need further help, do not hesitate to contact us!
== Verify you are using the latest (development) version
@ -21,25 +21,41 @@ i3 version 4.1.2-248-g51728ba (2012-02-12, branch "next")
Your version can look like this:
4.1.2::
You are using a release version. Please
upgrade to a development version first, or read
link:debugging-release-version.html[Debugging i3: How To (release version)].
4.1.2 (release version)::
You are using a release version. In many cases, bugs are already
fixed in the development version of i3. If they arent, we will still ask you
to reproduce your error with the most recent development version of i3.
Therefore, please upgrade to a development version if you can.
4.1.2-248-g51728ba::
4.1.2-248-g51728ba (development version)::
Your version is 248 commits newer than 4.1.2, and the git revision of your
version is +51728ba+. Go to http://code.i3wm.org/i3/commit/?h=next and see if
the line "commit" starts with the same revision. If so, you are using the
latest version.
Development versions of i3 have several properties which make debugging easier:
Development versions of i3 have logging enabled by default and are compiled
with debug symbols.
1. Shared memory debug logging is enabled by default. You do not have to enable
logging explicitly.
2. Core dumps are enabled by default.
3. If you are using a version from the Debian/Ubuntu autobuilder, it is
compiled without optimization. Debug symbols are available in the i3-wm-dbg
package. When compiling i3 yourself, debug mode is the default.
== Enabling logging
If you are using a development version (see previous section), you dont need
to do anything -- skip to section 3.
If you are using a release version with a custom +~/.xsession+ (or xinitrc)
file, execute i3 with a line like this:
----------------------------------
# Use 25 MiB of RAM for debug logs
exec i3 --shmlog-size=26214400
----------------------------------
If you are *NOT* using an +~/.xsession+ file but you just chose "i3" from the
list of sessions in your desktop manager (gdm, lxdm, …), edit
+/usr/share/xsessions/i3.desktop+ and replace the +Exec=i3+ line with:
------------------------------
Exec=i3 --shmlog-size=26214400
------------------------------
== Obtaining the debug logfile
@ -48,35 +64,39 @@ crashed, the logfile provides all information necessary to debug the problem.
To save a compressed version of the logfile (suitable for attaching it to a
bugreport), use:
--------------------------------------------------------------------
i3-dump-log | gzip -9c > /tmp/i3.log.gz
--------------------------------------------------------------------
This command does not depend on i3 (it also works when i3 currently displays
the crash dialog), but it requires a working X11 connection. When running it
from a virtual console (Ctrl-Alt-F1), use:
--------------------------------------------------------------------
DISPLAY=:0 i3-dump-log | gzip -9c > /tmp/i3.log.gz
--------------------------------------------------------------------
This command does not depend on i3 (it also works while i3 displays
the crash dialog), but it requires a working X11 connection.
== Compiling with debug symbols
To actually get useful backtraces, you should make sure that your version of i3
is compiled with debug symbols:
------------------------------------------------------------------------------
$ file `which i3`
/usr/bin/i3: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically
linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
------------------------------------------------------------------------------
Notice the +not stripped+, which is the important part. If you have a version
which is stripped, please check whether your distribution provides debug
symbols (package +i3-wm-dbg+ on Debian for example) or if you can turn off
stripping. If nothing helps, please build i3 from source.
== Obtaining a backtrace
When i3 displays its crash dialog, do the following:
Once you have made sure that your i3 is compiled with debug symbols and the C
debugger +gdb+ is installed on your machine, you can let i3 generate a
backtrace in the crash dialog.
1. Switch to a virtual console (Ctrl-Alt-F1) or login from a different computer
2. Generate a backtrace (see below)
3. Switch back to the crash dialog (Ctrl-Alt-F7)
4. Restart i3 in-place (you will keep your session), continue working
This is how you get a backtrace from a running i3 process:
-----------------
I3PID=$(pidof i3)
gdb /proc/$I3PID/exe $I3PID \
--batch --quiet \
--ex 'backtrace full' > /tmp/i3-backtrace.txt 2>&1
-----------------
After pressing "b" in the crash dialog, you will get a file called
+/tmp/i3-backtrace.%d.%d.txt+ where the first +%d+ is replaced by i3s process
id (PID) and the second one is incremented each time you generate a backtrace,
starting at 0.
== Sending bug reports/debugging on IRC

View File

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

View File

@ -5,8 +5,7 @@ ASCIIDOC = asciidoc
I3POD2HTML = ./docs/i3-pod2html
ASCIIDOC_NOTOC_TARGETS = \
docs/debugging.html \
docs/debugging-release-version.html
docs/debugging.html
ASCIIDOC_TOC_TARGETS = \
docs/hacking-howto.html \

View File

@ -44,10 +44,15 @@ understand the old protocol version, but in order to use the new one, you need
to provide the correct version. The header block is terminated by a newline and
consists of a single JSON hash:
*Example*:
----------------
*Minimal example*:
------------------------------
{ "version": 1 }
----------------
------------------------------
*All features example*:
------------------------------
{ "version": 1, "stop_signal": 10, "cont_signal": 12 }
------------------------------
(Note that before i3 v4.3 the precise format had to be +{"version":1}+,
byte-for-byte.)
@ -93,6 +98,19 @@ You can find an example of a shell script which can be used as your
+status_command+ in the bar configuration at
http://code.stapelberg.de/git/i3/tree/contrib/trivial-bar-script.sh?h=next
=== Header in detail
version::
The version number (as an integer) of the i3bar protocol you will use.
stop_signal::
Specify to i3bar the signal (as an integer) to send to stop your
processing.
The default value (if none is specified) is SIGSTOP.
cont_signal::
Specify to i3bar the signal (as an integer)to send to continue your
processing.
The default value (if none is specified) is SIGCONT.
=== Blocks in detail
full_text::
@ -116,6 +134,16 @@ color::
when it is associated.
Colors are specified in hex (like in HTML), starting with a leading
hash sign. For example, +#ff0000+ means red.
min_width::
The minimum width (in pixels) of the block. If the content of the
+full_text+ key take less space than the specified min_width, the block
will be padded to the left and/or the right side, according to the +align+
key. This is useful when you want to prevent the whole status line to shift
when value take more or less space between each iteration.
align::
Align text on the +center+ (default), +right+ or +left+ of the block, when
the minimum width of the latter, specified by the +min_width+ key, is not
reached.
name and instance::
Every block should have a unique +name+ (string) entry so that it can
be easily identified in scripts which process the output. i3bar
@ -148,6 +176,8 @@ An example of a block which uses all possible entries follows:
"full_text": "E: 10.0.0.1 (1000 Mbit/s)",
"short_text": "10.0.0.1",
"color": "#00ff00",
"min_width": 300,
"align": "right",
"urgent": false,
"name": "ethernet",
"instance": "eth0"

View File

@ -1,7 +1,7 @@
IPC interface (interprocess communication)
==========================================
Michael Stapelberg <michael@i3wm.org>
August 2012
October 2012
This document describes how to interface with i3 from a separate process. This
is useful for example to remote-control i3 (to write test cases for example) or
@ -19,6 +19,13 @@ calling +i3 --get-socketpath+.
All i3 utilities, like +i3-msg+ and +i3-input+ will read the +I3_SOCKET_PATH+
X11 property, stored on the X11 root window.
[WARNING]
.Use an existing library!
There are existing libraries for many languages. You can have a look at
<<libraries>> or search the web if your language of choice is not mentioned.
Usually, it is not necessary to implement low-level communication with i3
directly.
== Establishing a connection
To establish a connection, simply open the IPC socket. The following code
@ -82,7 +89,7 @@ So, a typical message could look like this:
Or, as a hexdump:
------------------------------------------------------------------------------
00000000 69 33 2d 69 70 63 04 00 00 00 00 00 00 00 65 78 |i3-ipc........ex|
00000010 69 74 0a |it.|
00000010 69 74 |it|
------------------------------------------------------------------------------
To generate and send such a message, you could use the following code in Perl:
@ -274,6 +281,8 @@ name (string)::
border (string)::
Can be either "normal", "none" or "1pixel", dependending on the
containers border style.
current_border_width (integer)::
Number of pixels of the border width.
layout (string)::
Can be either "splith", "splitv", "stacked", "tabbed", "dockarea" or
"output".
@ -610,6 +619,8 @@ workspace (0)::
output (1)::
Sent when RandR issues a change notification (of either screens,
outputs, CRTCs or output properties).
mode (2)::
Sent whenever i3 changes its binding mode.
*Example:*
--------------------------------------------------------------------
@ -635,9 +646,29 @@ This event consists of a single serialized map containing a property
+change (string)+ which indicates the type of the change ("focus", "init",
"empty", "urgent").
Moreover, when the change is "focus", an +old (object)+ and a +current
(object)+ properties will be present with the previous and current
workspace respectively. When the first switch occurs (when i3 focuses
the workspace visible at the beginning) there is no previous
workspace, and the +old+ property will be set to +null+. Also note
that if the previous is empty it will get destroyed when switching,
but will still be present in the "old" property.
*Example:*
---------------------
{ "change": "focus" }
{
"change": "focus",
"current": {
"id": 28489712,
"type":4,
...
}
"old": {
"id": 28489715,
"type": 4,
...
}
}
---------------------
=== output event
@ -651,7 +682,21 @@ This event consists of a single serialized map containing a property
{ "change": "unspecified" }
---------------------------
== See also
=== mode event
This event consists of a single serialized map containing a property
+change (string)+ which holds the name of current mode in use. The name
is the same as specified in config when creating a mode. The default
mode is simply named default.
*Example:*
---------------------------
{ "change": "default" }
---------------------------
== See also (existing libraries)
[[libraries]]
For some languages, libraries are available (so you dont have to implement
all this on your own). This list names some (if you wrote one, please let me

View File

@ -63,6 +63,35 @@ For several reasons, the i3 testsuite has been implemented in Perl:
Please do not start programming language flamewars at this point.
=== Installing the dependencies
As usual with Perl programs, the testsuite ships with a +Makefile.PL+.
This file specifies which Perl modules the testsuite depends on and can be used
to install all of them.
Perl modules are distributed via CPAN, and there is the official, standard CPAN
client, simply called +cpan+. It comes with every Perl installation and can be
used to install the testsuite. Many users prefer to use the more modern
+cpanminus+ instead, though (because it asks no questions and just works):
.Installing testsuite dependencies using cpanminus (preferred)
--------------------------------------------------------------------------------
$ cd ~/i3/testcases
$ sudo apt-get install cpanminus
$ sudo cpanm .
--------------------------------------------------------------------------------
If you dont want to use cpanminus for some reason, the same works with cpan:
.Installing testsuite dependencies using cpan
--------------------------------------------------------------------------------
$ cd ~/i3/testcases
$ sudo cpan .
--------------------------------------------------------------------------------
In case you dont have root permissions, you can also install into your home
directory, see http://michael.stapelberg.de/cpan/
=== Mechanisms
==== Script: complete-run

View File

@ -12,28 +12,28 @@ contact us on IRC (preferred) or post your question(s) on the mailing list.
For the "too long; didnt read" people, here is an overview of the default
keybindings (click to see the full size image):
*Keys to use with mod (alt):*
*Keys to use with $mod (Alt):*
image:keyboard-layer1.png["Keys to use with mod (alt)",width=600,link="keyboard-layer1.png"]
image:keyboard-layer1.png["Keys to use with $mod (Alt)",width=600,link="keyboard-layer1.png"]
*Keys to use with Shift+mod:*
*Keys to use with Shift+$mod:*
image:keyboard-layer2.png["Keys to use with Shift+mod",width=600,link="keyboard-layer2.png"]
image:keyboard-layer2.png["Keys to use with Shift+$mod",width=600,link="keyboard-layer2.png"]
The red keys are the modifiers you need to press (by default), the blue keys
are your homerow.
== Using i3
Throughout this guide, the keyword +mod+ will be used to refer to the
configured modifier. This is the alt key (Mod1) by default, with windows (Mod4)
Throughout this guide, the keyword +$mod+ will be used to refer to the
configured modifier. This is the Alt key (Mod1) by default, with windows (Mod4)
being a popular alternative.
=== Opening terminals and moving around
One very basic operation is opening a new terminal. By default, the keybinding
for this is mod+Enter, that is Alt+Enter in the default configuration. By
pressing mod+Enter, a new terminal will be opened. It will fill the whole
for this is $mod+Enter, that is Alt+Enter in the default configuration. By
pressing $mod+Enter, a new terminal will be opened. It will fill the whole
space available on your screen.
image:single_terminal.png[Single terminal]
@ -48,9 +48,9 @@ image:two_terminals.png[Two terminals]
To move the focus between the two terminals, you can use the direction keys
which you may know from the editor +vi+. However, in i3, your homerow is used
for these keys (in +vi+, the keys are shifted to the left by one for
compatibility with most keyboard layouts). Therefore, +mod+J+ is left, +mod+K+
is down, +mod+L+ is up and `mod+;` is right. So, to switch between the
terminals, use +mod+K+ or +mod+L+. Of course, you can also use the arrow keys.
compatibility with most keyboard layouts). Therefore, +$mod+J+ is left, +$mod+K+
is down, +$mod+L+ is up and `$mod+;` is right. So, to switch between the
terminals, use +$mod+K+ or +$mod+L+. Of course, you can also use the arrow keys.
At the moment, your workspace is split (it contains two terminals) in a
specific direction (horizontal by default). Every window can be split
@ -61,8 +61,8 @@ windows.
TODO: picture of the tree
To split a window vertically, press +mod+v+ before you create the new window.
To split it horizontally, press +mod+h+.
To split a window vertically, press +$mod+v+ before you create the new window.
To split it horizontally, press +$mod+h+.
=== Changing the container layout
@ -80,15 +80,15 @@ tabbed::
The same principle as +stacking+, but the list of windows at the top is only
a single line which is vertically split.
To switch modes, press +mod+e+ for splith/splitv (it toggles), +mod+s+ for
stacking and +mod+w+ for tabbed.
To switch modes, press +$mod+e+ for splith/splitv (it toggles), +$mod+s+ for
stacking and +$mod+w+ for tabbed.
image:modes.png[Container modes]
=== Toggling fullscreen mode for a window
To display a window in fullscreen mode or to go out of fullscreen mode again,
press +mod+f+.
press +$mod+f+.
There is also a global fullscreen mode in i3 in which the client will span all
available outputs (the command is +fullscreen global+).
@ -96,7 +96,7 @@ available outputs (the command is +fullscreen global+).
=== Opening other applications
Aside from opening applications from a terminal, you can also use the handy
+dmenu+ which is opened by pressing +mod+d+ by default. Just type the name
+dmenu+ which is opened by pressing +$mod+d+ by default. Just type the name
(or a part of it) of the application which you want to open. The corresponding
application has to be in your +$PATH+ for this to work.
@ -108,7 +108,7 @@ create a keybinding for starting the application directly. See the section
If an application does not provide a mechanism for closing (most applications
provide a menu, the escape key or a shortcut like +Control+W+ to close), you
can press +mod+Shift+q+ to kill a window. For applications which support
can press +$mod+Shift+q+ to kill a window. For applications which support
the WM_DELETE protocol, this will correctly close the application (saving
any modifications or doing other cleanup). If the application doesnt support
the WM_DELETE protocol your X server will kill the window and the behaviour
@ -118,7 +118,7 @@ depends on the application.
Workspaces are an easy way to group a set of windows. By default, you are on
the first workspace, as the bar on the bottom left indicates. To switch to
another workspace, press +mod+num+ where +num+ is the number of the workspace
another workspace, press +$mod+num+ where +num+ is the number of the workspace
you want to use. If the workspace does not exist yet, it will be created.
A common paradigm is to put the web browser on one workspace, communication
@ -132,7 +132,7 @@ focus to that screen.
=== Moving windows to workspaces
To move a window to another workspace, simply press +mod+Shift+num+ where
To move a window to another workspace, simply press +$mod+Shift+num+ where
+num+ is (like when switching workspaces) the number of the target workspace.
Similarly to switching workspaces, the target workspace will be created if
it does not yet exist.
@ -148,11 +148,11 @@ columns/rows with your keyboard.
=== Restarting i3 inplace
To restart i3 inplace (and thus get into a clean state if there is a bug, or
to upgrade to a newer version of i3) you can use +mod+Shift+r+.
to upgrade to a newer version of i3) you can use +$mod+Shift+r+.
=== Exiting i3
To cleanly exit i3 without killing your X server, you can use +mod+Shift+e+.
To cleanly exit i3 without killing your X server, you can use +$mod+Shift+e+.
=== Floating
@ -162,7 +162,7 @@ paradigm but can be useful for some corner cases like "Save as" dialog
windows, or toolbar windows (GIMP or similar). Those windows usually set the
appropriate hint and are opened in floating mode by default.
You can toggle floating mode for a window by pressing +mod+Shift+Space+. By
You can toggle floating mode for a window by pressing +$mod+Shift+Space+. By
dragging the windows titlebar with your mouse you can move the window
around. By grabbing the borders and moving them you can resize the window. You
can also do that by using the <<floating_modifier>>.
@ -202,7 +202,7 @@ orientation (horizontal, vertical or unspecified) and the orientation depends
on the layout the container is in (vertical for splitv and stacking, horizontal
for splith and tabbed). So, in our example with the workspace, the default
layout of the workspace +Container+ is splith (most monitors are widescreen
nowadays). If you change the layout to splitv (+mod+l+ in the default config)
nowadays). If you change the layout to splitv (+$mod+l+ in the default config)
and *then* open two terminals, i3 will configure your windows like this:
image::tree-shot2.png["shot2",title="Vertical Workspace Orientation"]
@ -212,8 +212,8 @@ Lets assume you have two terminals on a workspace (with splith layout, that i
horizontal orientation), focus is on the right terminal. Now you want to open
another terminal window below the current one. If you would just open a new
terminal window, it would show up to the right due to the splith layout.
Instead, press +mod+v+ to split the container with the splitv layout (to
open a +Horizontal Split Container+, use +mod+h+). Now you can open a new
Instead, press +$mod+v+ to split the container with the splitv layout (to
open a +Horizontal Split Container+, use +$mod+h+). Now you can open a new
terminal and it will open below the current one:
image::tree-layout1.png["Layout",float="right"]
@ -248,7 +248,7 @@ single workspace on which you open three terminal windows. All these terminal
windows are directly attached to one node inside i3s layout tree, the
workspace node. By default, the workspace nodes orientation is +horizontal+.
Now you move one of these terminals down (+mod+k+ by default). The workspace
Now you move one of these terminals down (+$mod+k+ by default). The workspace
nodes orientation will be changed to +vertical+. The terminal window you moved
down is directly attached to the workspace and appears on the bottom of the
screen. A new (horizontal) container was created to accomodate the other two
@ -316,13 +316,15 @@ and fall back to a working font.
*Syntax*:
------------------------------
font <X core font description>
font xft:<a FreeType font description>
font pango:[family list] [style options] [size]
------------------------------
*Examples*:
--------------------------------------------------------------
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
font xft:DejaVu Sans Mono 10
font pango:DejaVu Sans Mono 10
font pango:DejaVu Sans Mono, Terminus Bold Semi-Condensed 11
font pango:Terminus 11x
--------------------------------------------------------------
[[keybindings]]
@ -362,10 +364,10 @@ bindcode [--release] [Modifiers+]keycode command
*Examples*:
--------------------------------
# Fullscreen
bindsym mod+f fullscreen
bindsym $mod+f fullscreen
# Restart
bindsym mod+Shift+r restart
bindsym $mod+Shift+r restart
# Notebook-specific hotkeys
bindcode 214 exec --no-startup-id /home/michael/toggle_beamer.sh
@ -479,11 +481,13 @@ workspace_layout tabbed
=== Border style for new windows
This option determines which border style new windows will have. The default is
"normal".
"normal". Note that new_float applies only to windows which are starting out as
floating windows, e.g. dialog windows.
*Syntax*:
---------------------------------------------
new_window <normal|1pixel|none>
new_window <normal|1pixel|none|pixel>
new_float <normal|1pixel|none|pixel>
---------------------------------------------
*Example*:
@ -491,6 +495,19 @@ new_window <normal|1pixel|none>
new_window 1pixel
---------------------
The "normal" and "pixel" border styles support an optional border width in
pixels:
*Example*:
---------------------
# The same as new_window none
new_window pixel 0
# A 3 px border
new_window pixel 3
---------------------
=== Hiding vertical borders
You can hide vertical borders adjacent to the screen edges using
@ -790,21 +807,23 @@ focus_follows_mouse no
When you are in fullscreen mode, some applications still open popup windows
(take Xpdf for example). This is because these applications may not be aware
that they are in fullscreen mode (they do not check the corresponding hint).
There are two things which are possible to do in this situation:
There are three things which are possible to do in this situation:
1. Just ignore the popup (dont map it). This wont interrupt you while you are
1. Display the popup if it belongs to the fullscreen application only. This is
the default and should be reasonable behavior for most users.
2. Just ignore the popup (dont map it). This wont interrupt you while you are
in fullscreen. However, some apps might react badly to this (deadlock until
you go out of fullscreen).
2. Leave fullscreen mode. This is the default.
3. Leave fullscreen mode.
*Syntax*:
-------------------------------------------------
popup_during_fullscreen <ignore|leave_fullscreen>
popup_during_fullscreen <smart|ignore|leave_fullscreen>
-------------------------------------------------
*Example*:
------------------------------
popup_during_fullscreen ignore
popup_during_fullscreen smart
------------------------------
=== Focus wrapping
@ -862,7 +881,7 @@ This configuration directive enables automatic +workspace back_and_forth+ (see
For instance: Assume you are on workspace "1: www" and switch to "2: IM" using
mod+2 because somebody sent you a message. You dont need to remember where you
came from now, you can just press mod+2 again to switch back to "1: www".
came from now, you can just press $mod+2 again to switch back to "1: www".
*Syntax*:
--------------------------------------
@ -874,6 +893,30 @@ workspace_auto_back_and_forth <yes|no>
workspace_auto_back_and_forth yes
---------------------------------
=== Delaying urgency hint reset on workspace change
If an application on another workspace sets an urgency hint, switching to this
workspace may lead to immediate focus of the application, which also means the
window decoration color would be immediately resetted to +client.focused+. This
may make it unnecessarily hard to tell which window originally raised the
event.
In order to prevent this, you can tell i3 to delay resetting the urgency state
by a certain time using the +force_display_urgency_hint+ directive. Setting the
value to 0 disables this feature.
The default is 500ms.
*Syntax*:
---------------------------------------
force_display_urgency_hint <timeout> ms
---------------------------------------
*Example*:
---------------------------------
force_display_urgency_hint 500 ms
---------------------------------
== Configuring i3bar
The bar at the bottom of your monitor is drawn by a separate process called
@ -1079,7 +1122,7 @@ font <font>
--------------------------------------------------------------
bar {
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
font xft:DejaVu Sans Mono 10
font pango:DejaVu Sans Mono 10
}
--------------------------------------------------------------
@ -1170,7 +1213,7 @@ the following keybinding:
*Example*:
--------------------------------------------------------
bindsym mod+x move container to workspace 3; workspace 3
bindsym $mod+x move container to workspace 3; workspace 3
--------------------------------------------------------
[[command_criteria]]
@ -1182,10 +1225,10 @@ which have the class Firefox, use:
*Example*:
------------------------------------
bindsym mod+x [class="Firefox"] kill
bindsym $mod+x [class="Firefox"] kill
# same thing, but case-insensitive
bindsym mod+x [class="(?i)firefox"] kill
bindsym $mod+x [class="(?i)firefox"] kill
------------------------------------
The criteria which are currently implemented are:
@ -1231,10 +1274,10 @@ exec [--no-startup-id] command
*Example*:
------------------------------
# Start the GIMP
bindsym mod+g exec gimp
bindsym $mod+g exec gimp
# Start the terminal emulator urxvt which is not yet startup-notification-aware
bindsym mod+Return exec --no-startup-id urxvt
bindsym $mod+Return exec --no-startup-id urxvt
------------------------------
The +--no-startup-id+ parameter disables startup-notification support for this
@ -1265,8 +1308,8 @@ split <vertical|horizontal>
*Example*:
------------------------------
bindsym mod+v split vertical
bindsym mod+h split horizontal
bindsym $mod+v split vertical
bindsym $mod+h split horizontal
------------------------------
=== Manipulating layout
@ -1287,21 +1330,21 @@ layout toggle [split|all]
*Examples*:
--------------
bindsym mod+s layout stacking
bindsym mod+l layout toggle split
bindsym mod+w layout tabbed
bindsym $mod+s layout stacking
bindsym $mod+l layout toggle split
bindsym $mod+w layout tabbed
# Toggle between stacking/tabbed/split:
bindsym mod+x layout toggle
bindsym $mod+x layout toggle
# Toggle between stacking/tabbed/splith/splitv:
bindsym mod+x layout toggle all
bindsym $mod+x layout toggle all
# Toggle fullscreen
bindsym mod+f fullscreen
bindsym $mod+f fullscreen
# Toggle floating/tiling
bindsym mod+t floating toggle
bindsym $mod+t floating toggle
--------------
=== Focusing/Moving containers
@ -1343,36 +1386,36 @@ relevant for floating containers. The default amount is 10 pixels.
*Examples*:
----------------------
# Focus container on the left, bottom, top, right:
bindsym mod+j focus left
bindsym mod+k focus down
bindsym mod+l focus up
bindsym mod+semicolon focus right
bindsym $mod+j focus left
bindsym $mod+k focus down
bindsym $mod+l focus up
bindsym $mod+semicolon focus right
# Focus parent container
bindsym mod+u focus parent
bindsym $mod+u focus parent
# Focus last floating/tiling container
bindsym mod+g focus mode_toggle
bindsym $mod+g focus mode_toggle
# Focus the output right to the current one
bindsym mod+x focus output right
bindsym $mod+x focus output right
# Focus the big output
bindsym mod+x focus output HDMI-2
bindsym $mod+x focus output HDMI-2
# Move container to the left, bottom, top, right:
bindsym mod+j move left
bindsym mod+k move down
bindsym mod+l move up
bindsym mod+semicolon move right
bindsym $mod+j move left
bindsym $mod+k move down
bindsym $mod+l move up
bindsym $mod+semicolon move right
# Move container, but make floating containers
# move more than the default
bindsym mod+j move left 20 px
bindsym $mod+j move left 20 px
# Move floating container to the center
# of all outputs
bindsym mod+c move absolute position center
bindsym $mod+c move absolute position center
----------------------
=== Changing (named) workspaces/moving to workspaces
@ -1395,38 +1438,40 @@ RandR output.
[[back_and_forth]]
To switch back to the previously focused workspace, use +workspace
back_and_forth+.
back_and_forth+; likewise, you can move containers to the previously focused
workspace using +move container to workspace back_and_forth+.
*Syntax*:
-----------------------------------
workspace <next|prev|next_on_output|prev_on_output>
workspace back_and_forth
workspace <name>
workspace number <number>
workspace number <name>
move [window|container] [to] workspace <name>
move [window|container] [to] workspace number <number>
move [window|container] [to] workspace number <name>
move [window|container] [to] workspace <prev|next|current>
-----------------------------------
*Examples*:
-------------------------
bindsym mod+1 workspace 1
bindsym mod+2 workspace 2
bindsym $mod+1 workspace 1
bindsym $mod+2 workspace 2
...
bindsym mod+Shift+1 move container to workspace 1
bindsym mod+Shift+2 move container to workspace 2
bindsym $mod+Shift+1 move container to workspace 1
bindsym $mod+Shift+2 move container to workspace 2
...
# switch between the current and the previously focused one
bindsym mod+b workspace back_and_forth
bindsym $mod+b workspace back_and_forth
bindsym $mod+Shift+b move container to workspace back_and_forth
# move the whole workspace to the next output
bindsym mod+x move workspace to output right
bindsym $mod+x move workspace to output right
# move firefox to current workspace
bindsym mod+F1 [class="Firefox"] move workspace current
bindsym $mod+F1 [class="Firefox"] move workspace current
-------------------------
==== Named workspaces
@ -1436,7 +1481,7 @@ workspace command, you can use an arbitrary name:
*Example*:
-------------------------
bindsym mod+1 workspace mail
bindsym $mod+1 workspace mail
...
-------------------------
@ -1445,8 +1490,8 @@ number, like this:
*Example*:
-------------------------
bindsym mod+1 workspace 1: mail
bindsym mod+2 workspace 2: www
bindsym $mod+1 workspace 1: mail
bindsym $mod+2 workspace 2: www
...
-------------------------
@ -1456,25 +1501,36 @@ workspaces are ordered the way they appeared. When they start with a number, i3
will order them numerically. Also, you will be able to use +workspace number 1+
to switch to the workspace which begins with number 1, regardless of which name
it has. This is useful in case you are changing the workspaces name
dynamically.
dynamically. To combine both commands you can use +workspace number 1: mail+ to
specify a default name if there's currently no workspace starting with a "1".
==== Renaming workspaces
You can rename workspaces. This might be useful to start with the default
numbered workspaces, do your work, and rename the workspaces afterwards to
reflect whats actually on them.
reflect whats actually on them. You can also omit the old name to rename
the currently focused workspace. This is handy if you wan't to use the
rename command with +i3-input+.
*Syntax*:
----------------------------------------------------
rename workspace <old_name> to <new_name>
rename workspace to <new_name>
----------------------------------------------------
*Examples*:
------------------------------------------------
--------------------------------------------------------------------------
i3-msg 'rename workspace 5 to 6'
i3-msg 'rename workspace 1 to "1: www"'
i3-msg 'rename workspace "1: www" to "10: www"'
------------------------------------------------
i3-msg 'rename workspace to "2: mail"
bindsym $mod+r exec i3-input -F 'rename workspace to %s' -P 'New name: '
--------------------------------------------------------------------------
=== Moving workspaces to a different screen
See <<move_to_outputs>> for how to move a container/workspace to a different
RandR output.
=== Moving containers/workspaces to RandR outputs
@ -1494,10 +1550,10 @@ move workspace to output <<left|right|down|up>|<output>>
--------------------------------------------------------
# Move the current workspace to the next output
# (effectively toggles when you only have two outputs)
bindsym mod+x move workspace to output right
bindsym $mod+x move workspace to output right
# Put this window on the presentation output.
bindsym mod+x move container to output VGA1
bindsym $mod+x move container to output VGA1
--------------------------------------------------------
[[resizingconfig]]
@ -1548,7 +1604,7 @@ mode "resize" {
}
# Enter resize mode
bindsym mod+r mode "resize"
bindsym $mod+r mode "resize"
----------------------------------------------------------------------
=== Jumping to specific windows
@ -1569,7 +1625,7 @@ with criteria for that.
*Examples*:
------------------------------------------------
# Get me to the next open VIM instance
bindsym mod+a [class="urxvt" title="VIM"] focus
bindsym $mod+a [class="urxvt" title="VIM"] focus
------------------------------------------------
=== VIM-like marks (mark/goto)
@ -1605,10 +1661,10 @@ TODO: make i3-input replace %s
*Examples*:
---------------------------------------
# Read 1 character and mark the current window with this character
bindsym mod+m exec i3-input -p 'mark ' -l 1 -P 'Mark: '
bindsym $mod+m exec i3-input -p 'mark ' -l 1 -P 'Mark: '
# Read 1 character and go to the window with the character
bindsym mod+g exec i3-input -p 'goto ' -l 1 -P 'Goto: '
bindsym $mod+g exec i3-input -p 'goto ' -l 1 -P 'Goto: '
---------------------------------------
Alternatively, if you do not want to mess with +i3-input+, you could create
@ -1625,9 +1681,9 @@ There is also +border toggle+ which will toggle the different border styles.
*Examples*:
----------------------------
bindsym mod+t border normal
bindsym mod+y border 1pixel
bindsym mod+u border none
bindsym $mod+t border normal
bindsym $mod+y border 1pixel
bindsym $mod+u border none
----------------------------
[[stack-limit]]
@ -1672,9 +1728,9 @@ however you dont need to (simply killing your X session is fine as well).
*Examples*:
----------------------------
bindsym mod+Shift+r restart
bindsym mod+Shift+w reload
bindsym mod+Shift+e exit
bindsym $mod+Shift+r restart
bindsym $mod+Shift+w reload
bindsym $mod+Shift+e exit
----------------------------
=== Scratchpad
@ -1685,7 +1741,9 @@ invisible until you show it again. There is no way to open that workspace.
Instead, when using +scratchpad show+, the window will be shown again, as a
floating window, centered on your current workspace (using +scratchpad show+ on
a visible scratchpad window will make it hidden again, so you can have a
keybinding to toggle).
keybinding to toggle). Note that this is just a normal floating window, so if
you want to "remove it from scratchpad", you can simple make it tiling again
(+floating toggle+).
As the name indicates, this is useful for having a window with your favorite
editor always at hand. However, you can also use this for other permanently
@ -1702,10 +1760,10 @@ scratchpad show
*Examples*:
------------------------------------------------
# Make the currently focused window a scratchpad
bindsym mod+Shift+minus move scratchpad
bindsym $mod+Shift+minus move scratchpad
# Show the first scratchpad window
bindsym mod+minus scratchpad show
bindsym $mod+minus scratchpad show
# Show the sup-mail scratchpad window, if any.
bindsym mod4+s [title="^Sup ::"] scratchpad show
@ -1818,6 +1876,8 @@ have more than one monitor:
3. If you have many workspaces on many monitors, it might get hard to keep
track of which window you put where. Thus, you can use vim-like marks to
quickly switch between windows. See <<vim_like_marks>>.
4. For information on how to move existing workspaces between monitors,
see <<_moving_containers_workspaces_to_randr_outputs>>.
== i3 and the rest of your software world

View File

@ -12,8 +12,18 @@
use strict;
use warnings;
use Data::Dumper;
use Getopt::Long;
use v5.10;
my $input = '';
my $prefix = '';
my $result = GetOptions(
'input=s' => \$input,
'prefix=s' => \$prefix
);
die qq|Input file "$input" does not exist!| unless -e $input;
# reads in a whole file
sub slurp {
open my $fh, '<', shift;
@ -24,8 +34,6 @@ sub slurp {
# Stores the different states.
my %states;
# XXX: dont hardcode input and output
my $input = '../parser-specs/commands.spec';
my @raw_lines = split("\n", slurp($input));
my @lines;
@ -101,24 +109,30 @@ for my $line (@lines) {
# Second step: Generate the enum values for all states.
# It is important to keep the order the same, so we store the keys once.
my @keys = keys %states;
# We sort descendingly by length to be able to replace occurences of the state
# name even when one states name is included in another ones (like FOR_WINDOW
# is in FOR_WINDOW_COMMAND).
my @keys = sort { length($b) <=> length($a) } keys %states;
open(my $enumfh, '>', 'GENERATED_enums.h');
open(my $enumfh, '>', "GENERATED_${prefix}_enums.h");
# XXX: we might want to have a way to do this without a trailing comma, but gcc
# seems to eat it.
my %statenum;
say $enumfh 'typedef enum {';
my $cnt = 0;
for my $state (@keys, '__CALL') {
say $enumfh " $state = $cnt,";
$statenum{$state} = $cnt;
$cnt++;
}
say $enumfh '} cmdp_state;';
close($enumfh);
# Third step: Generate the call function.
open(my $callfh, '>', 'GENERATED_call.h');
say $callfh 'static void GENERATED_call(const int call_identifier, struct CommandResult *result) {';
open(my $callfh, '>', "GENERATED_${prefix}_call.h");
my $resultname = uc(substr($prefix, 0, 1)) . substr($prefix, 1) . 'Result';
say $callfh "static void GENERATED_call(const int call_identifier, struct $resultname *result) {";
say $callfh ' switch (call_identifier) {';
my $call_id = 0;
for my $state (@keys) {
@ -132,13 +146,23 @@ for my $state (@keys) {
$next_state ||= 'INITIAL';
my $fmt = $cmd;
# Replace the references to identified literals (like $workspace) with
# calls to get_string().
# calls to get_string(). Also replaces state names (like FOR_WINDOW)
# with their ID (useful for cfg_criteria_init(FOR_WINDOW) e.g.).
$cmd =~ s/$_/$statenum{$_}/g for @keys;
$cmd =~ s/\$([a-z_]+)/get_string("$1")/g;
# Used only for debugging/testing.
$cmd =~ s/\&([a-z_]+)/get_long("$1")/g;
# For debugging/testing, we print the call using printf() and thus need
# to generate a format string. The format uses %d for <number>s,
# literal numbers or state IDs and %s for NULL, <string>s and literal
# strings.
$fmt =~ s/$_/%d/g for @keys;
$fmt =~ s/\$([a-z_]+)/%s/g;
$fmt =~ s/\&([a-z_]+)/%ld/g;
$fmt =~ s/"([a-z0-9_]+)"/%s/g;
$fmt =~ s/(?:-?|\b)[0-9]+\b/%d/g;
say $callfh " case $call_id:";
say $callfh " result->next_state = $next_state;";
say $callfh '#ifndef TEST_PARSER';
my $real_cmd = $cmd;
if ($real_cmd =~ /\(\)/) {
@ -152,9 +176,14 @@ for my $state (@keys) {
$cmd =~ s/[^(]+\(//;
$cmd =~ s/\)$//;
$cmd = ", $cmd" if length($cmd) > 0;
$cmd =~ s/, NULL//g;
say $callfh qq| fprintf(stderr, "$fmt\\n"$cmd);|;
# The cfg_criteria functions have side-effects which are important for
# testing. They are implemented as stubs in the test parser code.
if ($real_cmd =~ /^cfg_criteria/) {
say $callfh qq| $real_cmd;|;
}
say $callfh '#endif';
say $callfh " state = $next_state;";
say $callfh " break;";
$token->{next_state} = "call $call_id";
$call_id++;
@ -162,17 +191,18 @@ for my $state (@keys) {
}
say $callfh ' default:';
say $callfh ' printf("BUG in the parser. state = %d\n", call_identifier);';
say $callfh ' assert(false);';
say $callfh ' }';
say $callfh '}';
close($callfh);
# Fourth step: Generate the token datastructures.
open(my $tokfh, '>', 'GENERATED_tokens.h');
open(my $tokfh, '>', "GENERATED_${prefix}_tokens.h");
for my $state (@keys) {
my $tokens = $states{$state};
say $tokfh 'cmdp_token tokens_' . $state . '[' . scalar @$tokens . '] = {';
say $tokfh 'static cmdp_token tokens_' . $state . '[' . scalar @$tokens . '] = {';
for my $token (@$tokens) {
my $call_identifier = 0;
my $token_name = $token->{token};
@ -192,7 +222,7 @@ for my $state (@keys) {
say $tokfh '};';
}
say $tokfh 'cmdp_token_ptr tokens[' . scalar @keys . '] = {';
say $tokfh 'static cmdp_token_ptr tokens[' . scalar @keys . '] = {';
for my $state (@keys) {
my $tokens = $states{$state};
say $tokfh ' { tokens_' . $state . ', ' . scalar @$tokens . ' },';

View File

@ -13,7 +13,7 @@
#endif
/* For systems without getline, fall back to fgetln */
#if defined(__APPLE__) || (defined(__FreeBSD__) && __FreeBSD_version < 800000) || defined(__OpenBSD__)
#if defined(__APPLE__) || (defined(__FreeBSD__) && __FreeBSD_version < 800000)
#define USE_FGETLN
#elif defined(__FreeBSD__)
/* Defining this macro before including stdio.h is necessary in order to have

View File

@ -1,5 +1,5 @@
#ifndef _XCB_H
#define _XCB_H
#ifndef I3_XCB_H
#define I3_XCB_H
/* from X11/keysymdef.h */
#define XCB_NUM_LOCK 0xff7f

467
i3-dmenu-desktop Executable file
View File

@ -0,0 +1,467 @@
#!/usr/bin/env perl
# vim:ts=4:sw=4:expandtab
#
# © 2012 Michael Stapelberg
#
# No dependencies except for perl ≥ v5.10
use strict;
use warnings;
use Data::Dumper;
use IPC::Open2;
use POSIX qw(locale_h);
use File::Find;
use File::Basename qw(basename);
use File::Temp qw(tempfile);
use Getopt::Long;
use Pod::Usage;
use v5.10;
use utf8;
use open ':encoding(utf8)';
binmode STDOUT, ':utf8';
binmode STDERR, ':utf8';
# reads in a whole file
sub slurp {
open(my $fh, '<', shift) or die "$!";
local $/;
<$fh>;
}
my $entry_type = 'both';
my $dmenu_cmd = 'dmenu -i';
my $result = GetOptions(
'dmenu=s' => \$dmenu_cmd,
'entry-type=s' => \$entry_type,
'version' => sub {
say "dmenu-desktop 1.2 © 2012 Michael Stapelberg";
exit 0;
},
'help' => sub {
pod2usage(-exitval => 0);
});
die "Could not parse command line options" unless $result;
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ Convert LC_MESSAGES into an ordered list of suffixes to search for in the ┃
# ┃ .desktop files (e.g. “Name[de_DE@euro]” for LC_MESSAGES=de_DE.UTF-8@euro ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
# For details on how the transformation of LC_MESSAGES to a list of keys that
# should be looked up works, refer to “Localized values for keys” of the
# “Desktop Entry Specification”:
# http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s04.html
my $lc_messages = setlocale(LC_MESSAGES);
# Ignore the encoding (e.g. .UTF-8)
$lc_messages =~ s/\.[^@]+//g;
my @suffixes = ($lc_messages);
# _COUNTRY and @MODIFIER are present
if ($lc_messages =~ /_[^@]+@/) {
my $no_modifier = $lc_messages;
$no_modifier =~ s/@.*//g;
push @suffixes, $no_modifier;
my $no_country = $lc_messages;
$no_country =~ s/_[^@]+//g;
push @suffixes, $no_country;
}
# Strip _COUNTRY and @MODIFIER if present
$lc_messages =~ s/[_@].*//g;
push @suffixes, $lc_messages;
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ Read all .desktop files and store the values in which we are interested. ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
my %desktops;
# See http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables
my $xdg_data_home = $ENV{XDG_DATA_HOME};
$xdg_data_home = $ENV{HOME} . '/.local/share' if
!defined($xdg_data_home) ||
$xdg_data_home eq '' ||
! -d $xdg_data_home;
my $xdg_data_dirs = $ENV{XDG_DATA_DIRS};
$xdg_data_dirs = '/usr/local/share/:/usr/share/' if
!defined($xdg_data_dirs) ||
$xdg_data_dirs eq '';
my @searchdirs = ("$xdg_data_home/applications/");
for my $dir (split(':', $xdg_data_dirs)) {
push @searchdirs, "$dir/applications/";
}
# Cleanup the paths, maybe some application does not cope with double slashes
# (the field code %k is replaced with the .desktop file location).
@searchdirs = map { s,//,/,g; $_ } @searchdirs;
# To avoid errors by File::Finds find(), only pass existing directories.
@searchdirs = grep { -d $_ } @searchdirs;
find(
{
wanted => sub {
return unless substr($_, -1 * length('.desktop')) eq '.desktop';
my $relative = $File::Find::name;
# + 1 for the trailing /, which is missing in ::topdir.
substr($relative, 0, length($File::Find::topdir) + 1) = '';
# Dont overwrite files with the same relative path, we search in
# descending order of importance.
return if exists($desktops{$relative});
$desktops{$relative} = $File::Find::name;
},
no_chdir => 1,
},
@searchdirs
);
my %apps;
for my $file (values %desktops) {
my $base = basename($file);
# _ is an invalid character for a key, so we can use it for our own keys.
$apps{$base}->{_Location} = $file;
# Extract all “Name” and “Exec” keys from the [Desktop Entry] group
# and store them in $apps{$base}.
my %names;
my @lines = split("\n", slurp($file));
for my $line (@lines) {
my $first = substr($line, 0, 1);
next if $line eq '' || $first eq '#';
next unless ($line eq '[Desktop Entry]' ..
($first eq '[' &&
substr($line, -1) eq ']' &&
$line ne '[Desktop Entry]'));
next if $first eq '[';
my ($key, $value) = ($line =~ /^
(
[A-Za-z0-9-]+ # the spec specifies these as valid key characters
(?:\[[^]]+\])? # possibly, there as a locale suffix
)
\s* = \s* # whitespace around = should be ignored
(.*) # no restrictions on the values
$/x);
if ($key =~ /^Name/) {
$names{$key} = $value;
} elsif ($key eq 'Exec' ||
$key eq 'TryExec' ||
$key eq 'Type') {
$apps{$base}->{$key} = $value;
} elsif ($key eq 'NoDisplay' ||
$key eq 'Hidden' ||
$key eq 'StartupNotify' ||
$key eq 'Terminal') {
# Values of type boolean must either be string true or false,
# see “Possible value types”:
# http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s03.html
$apps{$base}->{$key} = ($value eq 'true');
}
}
for my $suffix (@suffixes) {
next unless exists($names{"Name[$suffix]"});
$apps{$base}->{Name} = $names{"Name[$suffix]"};
last;
}
# Fallback to unlocalized “Name”.
$apps{$base}->{Name} = $names{Name} unless exists($apps{$base}->{Name});
}
# %apps now looks like this:
#
# %apps = {
# 'evince.desktop' => {
# 'Exec' => 'evince %U',
# 'Name' => 'Dokumentenbetrachter',
# '_Location' => '/usr/share/applications/evince.desktop'
# },
# 'gedit.desktop' => {
# 'Exec' => 'gedit %U',
# 'Name' => 'gedit',
# '_Location' => '/usr/share/applications/gedit.desktop'
# }
# };
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ Turn %apps inside out to provide Name → filename lookup. ┃
# ┃ The Name is what we display in dmenu later. ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
my %choices;
for my $app (keys %apps) {
my $name = $apps{$app}->{Name};
# Dont try to use .desktop files which dont have Type=application
next if (!exists($apps{$app}->{Type}) ||
$apps{$app}->{Type} ne 'Application');
# Dont offer apps which have NoDisplay == true or Hidden == true.
# See http://wiki.xfce.org/howto/customize-menu#hide_menu_entries
# for the difference between NoDisplay and Hidden.
next if (exists($apps{$app}->{NoDisplay}) && $apps{$app}->{NoDisplay}) ||
(exists($apps{$app}->{Hidden}) && $apps{$app}->{Hidden});
if (exists($apps{$app}->{TryExec})) {
my $tryexec = $apps{$app}->{TryExec};
if (substr($tryexec, 0, 1) eq '/') {
# Skip if absolute path is not executable.
next unless -x $tryexec;
} else {
# Search in $PATH for the executable.
my $found = 0;
for my $path (split(':', $ENV{PATH})) {
next unless -x "$path/$tryexec";
$found = 1;
last;
}
next unless $found;
}
}
if ($entry_type eq 'name' || $entry_type eq 'both') {
if (exists($choices{$name})) {
# There are two .desktop files which contain the same “Name” value.
# Im not sure if that is allowed to happen, but we disambiguate the
# situation by appending “ (2)”, “ (3)”, etc. to the name.
#
# An example of this happening is exo-file-manager.desktop and
# thunar-settings.desktop, both of which contain “Name=File Manager”.
my $inc = 2;
$inc++ while exists($choices{"$name ($inc)"});
$name = "$name ($inc)";
}
$choices{$name} = $app;
}
if ($entry_type eq 'command' || $entry_type eq 'both') {
my ($command) = split(' ', $apps{$app}->{Exec});
$choices{basename($command)} = $app;
}
}
# %choices now looks like this:
#
# %choices = {
# 'Dokumentenbetrachter' => 'evince.desktop',
# 'gedit' => 'gedit.desktop'
# };
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ Run dmenu to ask the user for her choice ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
# open2 will just make dmenus STDERR go to our own STDERR.
my ($dmenu_out, $dmenu_in);
my $pid = open2($dmenu_out, $dmenu_in, $dmenu_cmd);
binmode $dmenu_in, ':utf8';
binmode $dmenu_out, ':utf8';
# Feed dmenu the possible choices.
say $dmenu_in $_ for sort keys %choices;
close($dmenu_in);
waitpid($pid, 0);
my $status = ($? >> 8);
# Pass on dmenus exit status if there was an error.
exit $status unless $status == 0;
my $choice = <$dmenu_out>;
my $app;
# Exact match: the user chose “Avidemux (GTK+)”
if (exists($choices{$choice})) {
$app = $apps{$choices{$choice}};
$choice = '';
} else {
# Not an exact match: the user entered “Avidemux (GTK+) ~/movie.mp4”
for my $possibility (keys %choices) {
next unless substr($choice, 0, length($possibility)) eq $possibility;
$app = $apps{$choices{$possibility}};
substr($choice, 0, length($possibility)) = '';
# Remove whitespace separating the entry and arguments.
$choice =~ s/^\s//g;
last;
}
if (!defined($app)) {
die "Invalid input: “$choice” does not match any application.";
}
}
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ Make i3 start the chosen application. ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
my $name = $app->{Name};
my $exec = $app->{Exec};
my $location = $app->{_Location};
# Quote as described by “The Exec key”:
# http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
sub quote {
my ($str) = @_;
$str =~ s/("|`|\$|\\)/\\$1/g;
$str = qq|"$str"| if $str ne "";
return $str;
}
$choice = quote($choice);
$location = quote($location);
# Remove deprecated field codes, as the spec dictates.
$exec =~ s/%[dDnNvm]//g;
# Replace filename field codes with the rest of the command line.
# Note that we assume the user uses precisely one file name,
# not multiple file names.
$exec =~ s/%[fF]/$choice/g;
# If the program works with URLs,
# we assume the user provided a URL instead of a filename.
# As per the spec, there must be at most one of %f, %u, %F or %U present.
$exec =~ s/%[uU]/$choice/g;
# The translated name of the application.
$exec =~ s/%c/$name/g;
# XXX: Icons are not implemented. Is the complexity (looking up the path if
# only a name is given) actually worth it?
#$exec =~ s/%i/--icon $icon/g;
# location of .desktop file
$exec =~ s/%k/$location/g;
# Literal % characters are represented as %%.
$exec =~ s/%%/%/g;
my $nosn = '';
my $cmd;
if (exists($app->{Terminal}) && $app->{Terminal}) {
# For applications which specify “Terminal=true” (e.g. htop.desktop),
# we need to create a temporary script that contains the full command line
# as the syntax for starting commands with arguments varies from terminal
# emulator to terminal emulator.
# Then, we launch that script with i3-sensible-terminal.
my ($fh, $filename) = tempfile();
binmode($fh, ':utf8');
say $fh <<EOT;
#!/bin/sh
rm $filename
exec $exec
EOT
close($fh);
chmod 0755, $filename;
$cmd = qq|exec i3-sensible-terminal -e "$filename"|;
} else {
# i3 executes applications by passing the argument to i3s “exec” command
# as-is to $SHELL -c. The i3 parser supports quoted strings: When a string
# starts with a double quote ("), everything is parsed as-is until the next
# double quote which is NOT preceded by a backslash (\).
#
# Therefore, we escape all double quotes (") by replacing them with \"
$exec =~ s/"/\\"/g;
if (exists($app->{StartupNotify}) && !$app->{StartupNotify}) {
$nosn = '--no-startup-id';
}
$cmd = qq|exec $nosn "$exec"|;
}
system('i3-msg', $cmd) == 0 or die "Could not launch i3-msg: $?";
=encoding utf-8
=head1 NAME
i3-dmenu-desktop - run .desktop files with dmenu
=head1 SYNOPSIS
i3-dmenu-desktop [--dmenu='dmenu -i'] [--entry-type=both]
=head1 DESCRIPTION
i3-dmenu-desktop is a script which extracts the (localized) name from
application .desktop files, offers the user a choice via dmenu(1) and then
starts the chosen application via i3 (for startup notification support).
The advantage of using .desktop files instead of dmenu_run(1) is that dmenu_run
offers B<all> binaries in your $PATH, including non-interactive utilities like
"sed". Also, .desktop files contain a proper name, information about whether
the application runs in a terminal and whether it supports startup
notifications.
The .desktop files are searched in $XDG_DATA_HOME/applications (by default
$HOME/.local/share/applications) and in the "applications" subdirectory of each
entry of $XDG_DATA_DIRS (by default /usr/local/share/:/usr/share/).
Files with the same name in $XDG_DATA_HOME/applications take precedence over
files in $XDG_DATA_DIRS, so that you can overwrite parts of the system-wide
.desktop files by copying them to your local directory and making changes.
i3-dmenu-desktop displays the "Name" value in the localized version depending
on LC_MESSAGES as specified in the Desktop Entry Specification.
You can pass a filename or URL (%f/%F and %u/%U field codes in the .desktop
file respectively) by appending it to the name of the application. E.g., if you
want to launch "GNU Emacs 24" with the patch /tmp/foobar.txt, you would type
"emacs", press TAB, type " /tmp/foobar.txt" and press ENTER.
.desktop files with Terminal=true are started using i3-sensible-terminal(1).
.desktop files with NoDisplay=true or Hidden=true are skipped.
UTF-8 is supported, of course, but dmenu does not support displaying all
glyphs. E.g., xfce4-terminal.desktop's Name[fi]=Pääte will be displayed just
fine, but not its Name[ru]=Терминал.
=head1 OPTIONS
=over
=item B<--dmenu=command>
Execute command instead of 'dmenu -i'. This option can be used to pass custom
parameters to dmenu, or to make i3-dmenu-desktop start a custom (patched?)
version of dmenu.
=item B<--entry-type=type>
Display the (localized) "Name" (type = name) or the command (type = command) or
both (type = both) in dmenu.
Examples are "GNU Image Manipulation Program" (type = name), "gimp" (type =
command) and both (type = both).
=back
=head1 VERSION
Version 1.2
=head1 AUTHOR
Michael Stapelberg, C<< <michael at i3wm.org> >>
=head1 LICENSE AND COPYRIGHT
Copyright 2012 Michael Stapelberg.
This program is free software; you can redistribute it and/or modify it
under the terms of the BSD license.
=cut

View File

@ -1,5 +1,5 @@
#ifndef _I3_INPUT
#define _I3_INPUT
#ifndef I3_INPUT
#define I3_INPUT
#include <err.h>

View File

@ -1,5 +1,5 @@
#ifndef _I3_NAGBAR
#define _I3_NAGBAR
#ifndef I3_NAGBAR
#define I3_NAGBAR
#include <err.h>

View File

@ -15,8 +15,8 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# The font above is very space-efficient, that is, it looks good, sharp and
# clear in small sizes. However, if you need a lot of unicode glyphs or
# right-to-left text rendering, you should instead use pango for rendering and
# chose an xft font, such as:
# font xft:DejaVu Sans Mono 10
# chose a FreeType font, such as:
# font pango:DejaVu Sans Mono 10
# use Mouse+Mod1 to drag floating windows to their wanted position
floating_modifier Mod1
@ -29,6 +29,10 @@ bindsym Mod1+Shift+q kill
# start dmenu (a program launcher)
bindsym Mod1+d exec dmenu_run
# There also is the (new) i3-dmenu-desktop which only displays applications
# shipping a .desktop file. It is a wrapper around dmenu, so you need that
# installed.
# bindsym Mod1+d exec --no-startup-id i3-dmenu-desktop
# change focus
bindsym Mod1+j focus left

View File

@ -16,8 +16,8 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# The font above is very space-efficient, that is, it looks good, sharp and
# clear in small sizes. However, if you need a lot of unicode glyphs or
# right-to-left text rendering, you should instead use pango for rendering and
# chose an xft font, such as:
# font xft:DejaVu Sans Mono 10
# chose a FreeType font, such as:
# font pango:DejaVu Sans Mono 10
# Use Mouse+$mod to drag floating windows to their wanted position
floating_modifier $mod
@ -30,6 +30,10 @@ bindcode $mod+Shift+24 kill
# start dmenu (a program launcher)
bindcode $mod+40 exec dmenu_run
# There also is the (new) i3-dmenu-desktop which only displays applications
# shipping a .desktop file. It is a wrapper around dmenu, so you need that
# installed.
# bindsym $mod+d exec --no-startup-id i3-dmenu-desktop
# change focus
bindcode $mod+44 focus left
@ -79,7 +83,7 @@ bindcode $mod+65 focus mode_toggle
bindcode $mod+38 focus parent
# focus the child container
#bindcode $mod+d focus child
#bindsym $mod+d focus child
# switch to workspace
bindcode $mod+10 workspace 1

View File

@ -10,8 +10,31 @@
#ifndef CHILD_H_
#define CHILD_H_
#include <stdbool.h>
#define STDIN_CHUNK_SIZE 1024
typedef struct {
pid_t pid;
/**
* The version number is an uint32_t to avoid machines with different sizes of
* 'int' to allow different values here. Its highly unlikely we ever exceed
* even an int8_t, but still
*/
uint32_t version;
bool stopped;
/**
* The signal requested by the client to inform it of the hidden state of i3bar
*/
int stop_signal;
/**
* The signal requested by the client to inform it of theun hidden state of i3bar
*/
int cont_signal;
} i3bar_child;
/*
* Start a child-process with the specified command and reroute stdin.
* We actually start a $SHELL to execute the command so we don't have to care

View File

@ -27,16 +27,28 @@ struct rect_t {
int h;
};
typedef enum {
ALIGN_LEFT,
ALIGN_CENTER,
ALIGN_RIGHT
} blockalign_t;
/* This data structure represents one JSON dictionary, multiple of these make
* up one status line. */
struct status_block {
i3String *full_text;
char *color;
uint32_t min_width;
blockalign_t align;
/* The amount of pixels necessary to render this block. This variable is
bool urgent;
/* The amount of pixels necessary to render this block. These variables are
* only temporarily used in refresh_statusline(). */
uint32_t width;
uint32_t x_offset;
uint32_t x_append;
TAILQ_ENTRY(status_block) blocks;
};
@ -48,10 +60,11 @@ TAILQ_HEAD(statusline_head, status_block) statusline_head;
#include "outputs.h"
#include "util.h"
#include "workspaces.h"
#include "mode.h"
#include "trayclients.h"
#include "xcb.h"
#include "config.h"
#include "libi3.h"
#include "determine_json_version.h"
#include "parse_json_header.h"
#endif

View File

@ -1,29 +0,0 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3bar - an xcb-based status- and ws-bar for i3
* © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
*
* determine_json_version.c: Determines the JSON protocol version based on the
* first line of input from a child program.
*
*/
#ifndef DETERMINE_JSON_VERSION_H_
#define DETERMINE_JSON_VERSION_H_
#include <stdint.h>
/*
* Determines the JSON i3bar protocol version from the given buffer. In case
* the buffer does not contain valid JSON, or no version field is found, this
* function returns -1. The amount of bytes consumed by parsing the header is
* returned in *consumed (if non-NULL).
*
* The return type is an int32_t to avoid machines with different sizes of
* 'int' to allow different values here. Its highly unlikely we ever exceed
* even an int8_t, but still
*
*/
int32_t determine_json_version(const unsigned char *buffer, int length, unsigned int *consumed);
#endif

31
i3bar/include/mode.h Normal file
View File

@ -0,0 +1,31 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3bar - an xcb-based status- and ws-bar for i3
* © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
*
* mode.c: Handle mode-event and show current binding mode in the bar
*
*/
#ifndef MODE_H_
#define MODE_H_
#include <xcb/xproto.h>
#include "common.h"
/* Name of current binding mode and its render width */
struct mode {
i3String *name;
int width;
};
typedef struct mode mode;
/*
* Start parsing the received json-string
*
*/
void parse_mode_json(char *json);
#endif

View File

@ -0,0 +1,26 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3bar - an xcb-based status- and ws-bar for i3
* © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
*
* parse_json_header.c: Parse the JSON protocol header to determine
* protocol version and features.
*
*/
#ifndef PARSE_JSON_HEADER_H_
#define PARSE_JSON_HEADER_H_
#include <stdint.h>
/**
* Parse the JSON protocol header to determine protocol version and features.
* In case the buffer does not contain a valid header (invalid JSON, or no
* version field found), the 'correct' field of the returned header is set to
* false. The amount of bytes consumed by parsing the header is returned in
* *consumed (if non-NULL).
*
*/
void parse_json_header(i3bar_child *child, const unsigned char *buffer, int length, unsigned int *consumed);
#endif

View File

@ -16,6 +16,8 @@
#undef MIN
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#define STARTS_WITH(string, len, needle) ((len >= strlen(needle)) && strncasecmp(string, needle, strlen(needle)) == 0)
/* Securely free p */
#define FREE(p) do { \
if (p != NULL) { \

View File

@ -110,7 +110,7 @@ void reconfig_windows(void);
* Render the bars, with buttons and statusline
*
*/
void draw_bars(void);
void draw_bars(bool force_unhide);
/*
* Redraw the bars, i.e. simply copy the buffer to the barwindow
@ -118,4 +118,10 @@ void draw_bars(void);
*/
void redraw_bars(void);
/*
* Set the current binding mode
*
*/
void set_current_mode(struct mode *mode);
#endif

View File

@ -25,19 +25,20 @@
#include "common.h"
/* Global variables for child_*() */
pid_t child_pid;
i3bar_child child = { 0 };
/* stdin- and sigchild-watchers */
ev_io *stdin_io;
ev_child *child_sig;
/* JSON parser for stdin */
bool first_line = true;
bool plaintext = false;
yajl_callbacks callbacks;
yajl_handle parser;
typedef struct parser_ctx {
/* True if one of the parsed blocks was urgent */
bool has_urgent;
/* A copy of the last JSON map key. */
char *last_map_key;
@ -69,6 +70,8 @@ void cleanup(void) {
ev_child_stop(main_loop, child_sig);
FREE(child_sig);
}
memset(&child, 0, sizeof(i3bar_child));
}
/*
@ -109,6 +112,14 @@ static int stdin_map_key(void *context, const unsigned char *key, unsigned int l
return 1;
}
static int stdin_boolean(void *context, int val) {
parser_ctx *ctx = context;
if (strcasecmp(ctx->last_map_key, "urgent") == 0) {
ctx->block.urgent = val;
}
return 1;
}
#if YAJL_MAJOR >= 2
static int stdin_string(void *context, const unsigned char *val, size_t len) {
#else
@ -121,6 +132,27 @@ static int stdin_string(void *context, const unsigned char *val, unsigned int le
if (strcasecmp(ctx->last_map_key, "color") == 0) {
sasprintf(&(ctx->block.color), "%.*s", len, val);
}
if (strcasecmp(ctx->last_map_key, "align") == 0) {
if (len == strlen("left") && !strncmp((const char*)val, "left", strlen("left"))) {
ctx->block.align = ALIGN_LEFT;
} else if (len == strlen("right") && !strncmp((const char*)val, "right", strlen("right"))) {
ctx->block.align = ALIGN_RIGHT;
} else {
ctx->block.align = ALIGN_CENTER;
}
}
return 1;
}
#if YAJL_MAJOR >= 2
static int stdin_integer(void *context, long long val) {
#else
static int stdin_integer(void *context, long val) {
#endif
parser_ctx *ctx = context;
if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
ctx->block.min_width = (uint32_t)val;
}
return 1;
}
@ -132,6 +164,8 @@ static int stdin_end_map(void *context) {
* i3bar doesnt crash and the user gets an annoying message. */
if (!new_block->full_text)
new_block->full_text = i3string_from_utf8("SPEC VIOLATION (null)");
if (new_block->urgent)
ctx->has_urgent = true;
TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
return 1;
}
@ -148,11 +182,10 @@ static int stdin_end_array(void *context) {
}
/*
* Callbalk for stdin. We read a line from stdin and store the result
* in statusline
* Helper function to read stdin
*
*/
void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
int fd = watcher->fd;
int n = 0;
int rec = 0;
@ -173,8 +206,9 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
/* end of file, kill the watcher */
ELOG("stdin: received EOF\n");
cleanup();
draw_bars();
return;
draw_bars(false);
*ret_buffer_len = -1;
return NULL;
}
rec += n;
@ -185,51 +219,94 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
}
if (*buffer == '\0') {
FREE(buffer);
return;
rec = -1;
}
*ret_buffer_len = rec;
return buffer;
}
unsigned char *json_input = buffer;
if (first_line) {
DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
/* Detect whether this is JSON or plain text. */
unsigned int consumed = 0;
/* At the moment, we dont care for the version. This might change
* in the future, but for now, we just discard it. */
plaintext = (determine_json_version(buffer, buffer_len, &consumed) == -1);
if (plaintext) {
/* In case of plaintext, we just add a single block and change its
* full_text pointer later. */
struct status_block *new_block = scalloc(sizeof(struct status_block));
TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
} else {
json_input += consumed;
rec -= consumed;
}
first_line = false;
}
if (!plaintext) {
yajl_status status = yajl_parse(parser, json_input, rec);
static void read_flat_input(char *buffer, int length) {
struct status_block *first = TAILQ_FIRST(&statusline_head);
/* Clear the old buffer if any. */
I3STRING_FREE(first->full_text);
/* Remove the trailing newline and terminate the string at the same
* time. */
if (buffer[length-1] == '\n' || buffer[length-1] == '\r')
buffer[length-1] = '\0';
else buffer[length] = '\0';
first->full_text = i3string_from_utf8(buffer);
}
static bool read_json_input(unsigned char *input, int length) {
yajl_status status = yajl_parse(parser, input, length);
bool has_urgent = false;
#if YAJL_MAJOR >= 2
if (status != yajl_status_ok) {
if (status != yajl_status_ok) {
#else
if (status != yajl_status_ok && status != yajl_status_insufficient_data) {
if (status != yajl_status_ok && status != yajl_status_insufficient_data) {
#endif
fprintf(stderr, "[i3bar] Could not parse JSON input (code %d): %.*s\n",
status, rec, json_input);
}
fprintf(stderr, "[i3bar] Could not parse JSON input (code %d): %.*s\n",
status, length, input);
} else if (parser_context.has_urgent) {
has_urgent = true;
}
return has_urgent;
}
/*
* Callbalk for stdin. We read a line from stdin and store the result
* in statusline
*
*/
void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
int rec;
unsigned char *buffer = get_buffer(watcher, &rec);
if (buffer == NULL)
return;
bool has_urgent = false;
if (child.version > 0) {
has_urgent = read_json_input(buffer, rec);
} else {
struct status_block *first = TAILQ_FIRST(&statusline_head);
/* Clear the old buffer if any. */
I3STRING_FREE(first->full_text);
/* Remove the trailing newline and terminate the string at the same
* time. */
if (buffer[rec-1] == '\n' || buffer[rec-1] == '\r')
buffer[rec-1] = '\0';
else buffer[rec] = '\0';
first->full_text = i3string_from_utf8((const char *)buffer);
read_flat_input((char*)buffer, rec);
}
free(buffer);
draw_bars();
draw_bars(has_urgent);
}
/*
* Callbalk for stdin first line. We read the first line to detect
* whether this is JSON or plain text
*
*/
void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
int rec;
unsigned char *buffer = get_buffer(watcher, &rec);
if (buffer == NULL)
return;
DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
/* Detect whether this is JSON or plain text. */
unsigned int consumed = 0;
/* At the moment, we dont care for the version. This might change
* in the future, but for now, we just discard it. */
parse_json_header(&child, buffer, rec, &consumed);
if (child.version > 0) {
/* If hide-on-modifier is set, we start of by sending the
* child a SIGSTOP, because the bars aren't mapped at start */
if (config.hide_on_modifier) {
stop_child();
}
read_json_input(buffer + consumed, rec - consumed);
} else {
/* In case of plaintext, we just add a single block and change its
* full_text pointer later. */
struct status_block *new_block = scalloc(sizeof(struct status_block));
TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
read_flat_input((char*)buffer, rec);
}
free(buffer);
ev_io_stop(main_loop, stdin_io);
ev_io_init(stdin_io, &stdin_io_cb, STDIN_FILENO, EV_READ);
ev_io_start(main_loop, stdin_io);
}
/*
@ -240,7 +317,7 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
*/
void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
ELOG("Child (pid: %d) unexpectedly exited with status %d\n",
child_pid,
child.pid,
watcher->rstatus);
cleanup();
}
@ -255,7 +332,9 @@ void start_child(char *command) {
/* Allocate a yajl parser which will be used to parse stdin. */
memset(&callbacks, '\0', sizeof(yajl_callbacks));
callbacks.yajl_map_key = stdin_map_key;
callbacks.yajl_boolean = stdin_boolean;
callbacks.yajl_string = stdin_string;
callbacks.yajl_integer = stdin_integer;
callbacks.yajl_start_array = stdin_start_array;
callbacks.yajl_end_array = stdin_end_array;
callbacks.yajl_start_map = stdin_start_map;
@ -268,14 +347,13 @@ void start_child(char *command) {
parser = yajl_alloc(&callbacks, NULL, &parser_context);
#endif
child_pid = 0;
if (command != NULL) {
int fd[2];
if (pipe(fd) == -1)
err(EXIT_FAILURE, "pipe(fd)");
child_pid = fork();
switch (child_pid) {
child.pid = fork();
switch (child.pid) {
case -1:
ELOG("Couldn't fork(): %s\n", strerror(errno));
exit(EXIT_FAILURE);
@ -298,12 +376,6 @@ void start_child(char *command) {
dup2(fd[0], STDIN_FILENO);
/* If hide-on-modifier is set, we start of by sending the
* child a SIGSTOP, because the bars aren't mapped at start */
if (config.hide_on_modifier) {
stop_child();
}
break;
}
}
@ -312,12 +384,12 @@ void start_child(char *command) {
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
stdin_io = smalloc(sizeof(ev_io));
ev_io_init(stdin_io, &stdin_io_cb, STDIN_FILENO, EV_READ);
ev_io_init(stdin_io, &stdin_io_first_line_cb, STDIN_FILENO, EV_READ);
ev_io_start(main_loop, stdin_io);
/* We must cleanup, if the child unexpectedly terminates */
child_sig = smalloc(sizeof(ev_child));
ev_child_init(child_sig, &child_sig_cb, child_pid, 0);
ev_child_init(child_sig, &child_sig_cb, child.pid, 0);
ev_child_start(main_loop, child_sig);
atexit(kill_child_at_exit);
@ -328,9 +400,10 @@ void start_child(char *command) {
*
*/
void kill_child_at_exit(void) {
if (child_pid != 0) {
kill(child_pid, SIGCONT);
kill(child_pid, SIGTERM);
if (child.pid > 0) {
if (child.cont_signal > 0 && child.stopped)
kill(child.pid, child.cont_signal);
kill(child.pid, SIGTERM);
}
}
@ -340,12 +413,12 @@ void kill_child_at_exit(void) {
*
*/
void kill_child(void) {
if (child_pid != 0) {
kill(child_pid, SIGCONT);
kill(child_pid, SIGTERM);
if (child.pid > 0) {
if (child.cont_signal > 0 && child.stopped)
kill(child.pid, child.cont_signal);
kill(child.pid, SIGTERM);
int status;
waitpid(child_pid, &status, 0);
child_pid = 0;
waitpid(child.pid, &status, 0);
cleanup();
}
}
@ -355,8 +428,9 @@ void kill_child(void) {
*
*/
void stop_child(void) {
if (child_pid != 0) {
kill(child_pid, SIGSTOP);
if (child.stop_signal > 0 && !child.stopped) {
child.stopped = true;
kill(child.pid, child.stop_signal);
}
}
@ -365,7 +439,8 @@ void stop_child(void) {
*
*/
void cont_child(void) {
if (child_pid != 0) {
kill(child_pid, SIGCONT);
if (child.cont_signal > 0 && child.stopped) {
child.stopped = false;
kill(child.pid, child.cont_signal);
}
}

View File

@ -1,104 +0,0 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3bar - an xcb-based status- and ws-bar for i3
* © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
*
* determine_json_version.c: Determines the JSON protocol version based on the
* first line of input from a child program.
*
*/
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <err.h>
#include <ev.h>
#include <stdbool.h>
#include <stdint.h>
#include <yajl/yajl_common.h>
#include <yajl/yajl_parse.h>
#include <yajl/yajl_version.h>
static bool version_key;
static int32_t version_number;
#if YAJL_MAJOR >= 2
static int version_integer(void *ctx, long long val) {
#else
static int version_integer(void *ctx, long val) {
#endif
if (version_key)
version_number = (uint32_t)val;
return 1;
}
#if YAJL_MAJOR >= 2
static int version_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) {
#else
static int version_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) {
#endif
version_key = (stringlen == strlen("version") &&
strncmp((const char*)stringval, "version", strlen("version")) == 0);
return 1;
}
static yajl_callbacks version_callbacks = {
NULL, /* null */
NULL, /* boolean */
&version_integer,
NULL, /* double */
NULL, /* number */
NULL, /* string */
NULL, /* start_map */
&version_map_key,
NULL, /* end_map */
NULL, /* start_array */
NULL /* end_array */
};
/*
* Determines the JSON i3bar protocol version from the given buffer. In case
* the buffer does not contain valid JSON, or no version field is found, this
* function returns -1. The amount of bytes consumed by parsing the header is
* returned in *consumed (if non-NULL).
*
* The return type is an int32_t to avoid machines with different sizes of
* 'int' to allow different values here. Its highly unlikely we ever exceed
* even an int8_t, but still
*
*/
int32_t determine_json_version(const unsigned char *buffer, int length, unsigned int *consumed) {
#if YAJL_MAJOR >= 2
yajl_handle handle = yajl_alloc(&version_callbacks, NULL, NULL);
/* Allow trailing garbage. yajl 1 always behaves that way anyways, but for
* yajl 2, we need to be explicit. */
yajl_config(handle, yajl_allow_trailing_garbage, 1);
#else
yajl_parser_config parse_conf = { 0, 0 };
yajl_handle handle = yajl_alloc(&version_callbacks, &parse_conf, NULL, NULL);
#endif
version_key = false;
version_number = -1;
yajl_status state = yajl_parse(handle, buffer, length);
if (state != yajl_status_ok) {
version_number = -1;
if (consumed != NULL)
*consumed = 0;
} else {
if (consumed != NULL)
*consumed = yajl_get_bytes_consumed(handle);
}
yajl_free(handle);
return version_number;
}

View File

@ -42,7 +42,7 @@ void got_command_reply(char *reply) {
void got_workspace_reply(char *reply) {
DLOG("Got Workspace-Data!\n");
parse_workspaces_json(reply);
draw_bars();
draw_bars(false);
}
/*
@ -71,7 +71,7 @@ void got_output_reply(char *reply) {
kick_tray_clients(o_walk);
}
draw_bars();
draw_bars(false);
}
/*
@ -138,10 +138,22 @@ void got_output_event(char *event) {
}
}
/*
* Called, when a mode-event arrives (i3 changed binding mode).
*
*/
void got_mode_event(char *event) {
DLOG("Got Mode Event!\n");
parse_mode_json(event);
draw_bars(false);
}
/* Data-structure to easily call the reply-handlers later */
handler_t event_handlers[] = {
&got_workspace_event,
&got_output_event
&got_output_event,
&got_mode_event
};
/*
@ -297,8 +309,8 @@ void destroy_connection(void) {
*/
void subscribe_events(void) {
if (config.disable_ws) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\" ]");
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\" ]");
} else {
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\" ]");
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\", \"mode\" ]");
}
}

141
i3bar/src/mode.c Normal file
View File

@ -0,0 +1,141 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3bar - an xcb-based status- and ws-bar for i3
* © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
*
* mode.c: Handle mode-event and show current binding mode in the bar
*
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <yajl/yajl_parse.h>
#include <yajl/yajl_version.h>
#include "common.h"
/* A datatype to pass through the callbacks to save the state */
struct mode_json_params {
char *json;
char *cur_key;
mode *mode;
};
/*
* Parse a string (change)
*
*/
#if YAJL_MAJOR >= 2
static int mode_string_cb(void *params_, const unsigned char *val, size_t len) {
#else
static int mode_string_cb(void *params_, const unsigned char *val, unsigned int len) {
#endif
struct mode_json_params *params = (struct mode_json_params*) params_;
if (!strcmp(params->cur_key, "change")) {
/* Save the name */
params->mode->name = i3string_from_utf8_with_length((const char *)val, len);
/* Save its rendered width */
params->mode->width = predict_text_width(params->mode->name);
DLOG("Got mode change: %s\n", i3string_as_utf8(params->mode->name));
FREE(params->cur_key);
return 1;
}
return 0;
}
/*
* Parse a key.
*
* Essentially we just save it in the parsing-state
*
*/
#if YAJL_MAJOR >= 2
static int mode_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) {
#else
static int mode_map_key_cb(void *params_, const unsigned char *keyVal, unsigned int keyLen) {
#endif
struct mode_json_params *params = (struct mode_json_params*) params_;
FREE(params->cur_key);
params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1));
strncpy(params->cur_key, (const char*) keyVal, keyLen);
params->cur_key[keyLen] = '\0';
return 1;
}
/* A datastructure to pass all these callbacks to yajl */
yajl_callbacks mode_callbacks = {
NULL,
NULL,
NULL,
NULL,
NULL,
&mode_string_cb,
NULL,
&mode_map_key_cb,
NULL,
NULL,
NULL
};
/*
* Start parsing the received json-string
*
*/
void parse_mode_json(char *json) {
/* FIXME: Fasciliate stream-processing, i.e. allow starting to interpret
* JSON in chunks */
struct mode_json_params params;
mode binding;
params.cur_key = NULL;
params.json = json;
params.mode = &binding;
yajl_handle handle;
yajl_status state;
#if YAJL_MAJOR < 2
yajl_parser_config parse_conf = { 0, 0 };
handle = yajl_alloc(&mode_callbacks, &parse_conf, NULL, (void*) &params);
#else
handle = yajl_alloc(&mode_callbacks, NULL, (void*) &params);
#endif
state = yajl_parse(handle, (const unsigned char*) json, strlen(json));
/* FIXME: Propper errorhandling for JSON-parsing */
switch (state) {
case yajl_status_ok:
break;
case yajl_status_client_canceled:
#if YAJL_MAJOR < 2
case yajl_status_insufficient_data:
#endif
case yajl_status_error:
ELOG("Could not parse mode-event!\n");
exit(EXIT_FAILURE);
break;
}
/* We don't want to indicate default binding mode */
if (strcmp("default", i3string_as_utf8(params.mode->name)) == 0)
I3STRING_FREE(params.mode->name);
/* Set the new binding mode */
set_current_mode(&binding);
yajl_free(handle);
FREE(params.cur_key);
}

View File

@ -0,0 +1,133 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3bar - an xcb-based status- and ws-bar for i3
* © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
*
* parse_json_header.c: Parse the JSON protocol header to determine
* protocol version and features.
*
*/
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <err.h>
#include <ev.h>
#include <stdbool.h>
#include <stdint.h>
#include <yajl/yajl_common.h>
#include <yajl/yajl_parse.h>
#include <yajl/yajl_version.h>
#include "common.h"
static enum {
KEY_VERSION,
KEY_STOP_SIGNAL,
KEY_CONT_SIGNAL,
NO_KEY
} current_key;
#if YAJL_MAJOR >= 2
static int header_integer(void *ctx, long long val) {
#else
static int header_integer(void *ctx, long val) {
#endif
i3bar_child *child = ctx;
switch (current_key) {
case KEY_VERSION:
child->version = val;
break;
case KEY_STOP_SIGNAL:
child->stop_signal = val;
break;
case KEY_CONT_SIGNAL:
child->cont_signal = val;
break;
default:
break;
}
return 1;
}
#define CHECK_KEY(name) (stringlen == strlen(name) && \
STARTS_WITH((const char*)stringval, stringlen, name))
#if YAJL_MAJOR >= 2
static int header_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) {
#else
static int header_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) {
#endif
if (CHECK_KEY("version")) {
current_key = KEY_VERSION;
} else if (CHECK_KEY("stop_signal")) {
current_key = KEY_STOP_SIGNAL;
} else if (CHECK_KEY("cont_signal")) {
current_key = KEY_CONT_SIGNAL;
}
return 1;
}
static yajl_callbacks version_callbacks = {
NULL, /* null */
NULL, /* boolean */
&header_integer,
NULL, /* double */
NULL, /* number */
NULL, /* string */
NULL, /* start_map */
&header_map_key,
NULL, /* end_map */
NULL, /* start_array */
NULL /* end_array */
};
static void child_init(i3bar_child *child) {
child->version = 0;
child->stop_signal = SIGSTOP;
child->cont_signal = SIGCONT;
}
/*
* Parse the JSON protocol header to determine protocol version and features.
* In case the buffer does not contain a valid header (invalid JSON, or no
* version field found), the 'correct' field of the returned header is set to
* false. The amount of bytes consumed by parsing the header is returned in
* *consumed (if non-NULL).
*
*/
void parse_json_header(i3bar_child *child, const unsigned char *buffer, int length, unsigned int *consumed) {
child_init(child);
current_key = NO_KEY;
#if YAJL_MAJOR >= 2
yajl_handle handle = yajl_alloc(&version_callbacks, NULL, child);
/* Allow trailing garbage. yajl 1 always behaves that way anyways, but for
* yajl 2, we need to be explicit. */
yajl_config(handle, yajl_allow_trailing_garbage, 1);
#else
yajl_parser_config parse_conf = { 0, 0 };
yajl_handle handle = yajl_alloc(&version_callbacks, &parse_conf, NULL, child);
#endif
yajl_status state = yajl_parse(handle, buffer, length);
if (state != yajl_status_ok) {
child_init(child);
if (consumed != NULL)
*consumed = 0;
} else {
if (consumed != NULL)
*consumed = yajl_get_bytes_consumed(handle);
}
yajl_free(handle);
}

View File

@ -74,6 +74,9 @@ ev_check *xcb_chk;
ev_io *xcb_io;
ev_io *xkb_io;
/* The name of current binding mode */
static mode binding;
/* The parsed colors */
struct xcb_colors_t {
uint32_t bar_fg;
@ -120,10 +123,31 @@ void refresh_statusline(void) {
continue;
block->width = predict_text_width(block->full_text);
/* Compute offset and append for text aligment in min_width. */
if (block->min_width <= block->width) {
block->x_offset = 0;
block->x_append = 0;
} else {
uint32_t padding_width = block->min_width - block->width;
switch (block->align) {
case ALIGN_LEFT:
block->x_append = padding_width;
break;
case ALIGN_RIGHT:
block->x_offset = padding_width;
break;
case ALIGN_CENTER:
block->x_offset = padding_width / 2;
block->x_append = padding_width / 2 + padding_width % 2;
break;
}
}
/* If this is not the last block, add some pixels for a separator. */
if (TAILQ_NEXT(block, blocks) != NULL)
block->width += 9;
statusline_width += block->width;
statusline_width += block->width + block->x_offset + block->x_append;
}
/* If the statusline is bigger than our screen we need to make sure that
@ -144,8 +168,8 @@ void refresh_statusline(void) {
uint32_t colorpixel = (block->color ? get_colorpixel(block->color) : colors.bar_fg);
set_font_colors(statusline_ctx, colorpixel, colors.bar_bg);
draw_text(block->full_text, statusline_pm, statusline_ctx, x, 0, block->width);
x += block->width;
draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, 0, block->width);
x += block->width + block->x_offset + block->x_append;
if (TAILQ_NEXT(block, blocks) != NULL) {
/* This is not the last block, draw a separator. */
@ -302,16 +326,24 @@ void handle_button(xcb_button_press_event_t *event) {
}
break;
case 4:
/* Mouse wheel down. We select the next ws */
if (cur_ws != TAILQ_FIRST(walk->workspaces)) {
cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq);
}
/* Mouse wheel up. We select the previous ws, if any.
* If there is no more workspace, dont even send the workspace
* command, otherwise (with workspace auto_back_and_forth) wed end
* up on the wrong workspace. */
if (cur_ws == TAILQ_FIRST(walk->workspaces))
return;
cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq);
break;
case 5:
/* Mouse wheel up. We select the previos ws */
if (cur_ws != TAILQ_LAST(walk->workspaces, ws_head)) {
cur_ws = TAILQ_NEXT(cur_ws, tailq);
}
/* Mouse wheel down. We select the next ws, if any.
* If there is no more workspace, dont even send the workspace
* command, otherwise (with workspace auto_back_and_forth) wed end
* up on the wrong workspace. */
if (cur_ws == TAILQ_LAST(walk->workspaces, ws_head))
return;
cur_ws = TAILQ_NEXT(cur_ws, tailq);
break;
}
@ -531,7 +563,7 @@ static void handle_client_message(xcb_client_message_event_t* event) {
/* Trigger an update to copy the statusline text to the appropriate
* position */
configure_trayclients();
draw_bars();
draw_bars(false);
}
}
}
@ -559,7 +591,7 @@ static void handle_unmap_notify(xcb_unmap_notify_event_t* event) {
/* Trigger an update, we now have more space for the statusline */
configure_trayclients();
draw_bars();
draw_bars(false);
return;
}
}
@ -624,13 +656,13 @@ static void handle_property_notify(xcb_property_notify_event_t *event) {
xcb_unmap_window(xcb_connection, trayclient->win);
trayclient->mapped = map_it;
configure_trayclients();
draw_bars();
draw_bars(false);
} else if (!trayclient->mapped && map_it) {
/* need to map the window */
xcb_map_window(xcb_connection, trayclient->win);
trayclient->mapped = map_it;
configure_trayclients();
draw_bars();
draw_bars(false);
}
free(xembedr);
}
@ -1398,12 +1430,15 @@ void reconfig_windows(void) {
* Render the bars, with buttons and statusline
*
*/
void draw_bars(void) {
void draw_bars(bool unhide) {
DLOG("Drawing Bars...\n");
int i = 0;
refresh_statusline();
static char *last_urgent_ws = NULL;
bool walks_away = true;
i3_output *outputs_walk;
SLIST_FOREACH(outputs_walk, outputs, slist) {
if (!outputs_walk->active) {
@ -1460,8 +1495,6 @@ void draw_bars(void) {
}
i3_ws *ws_walk;
static char *last_urgent_ws = NULL;
bool has_urgent = false, walks_away = true;
TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
DLOG("Drawing Button for WS %s at x = %d, len = %d\n", i3string_as_utf8(ws_walk->name), i, ws_walk->name_width);
@ -1486,13 +1519,11 @@ void draw_bars(void) {
fg_color = colors.urgent_ws_fg;
bg_color = colors.urgent_ws_bg;
border_color = colors.urgent_ws_border;
has_urgent = true;
unhide = true;
if (!ws_walk->focused) {
FREE(last_urgent_ws);
last_urgent_ws = sstrdup(i3string_as_utf8(ws_walk->name));
}
/* The urgent-hint should get noticed, so we unhide the bars shortly */
unhide_bars();
}
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
uint32_t vals_border[] = { border_color, border_color };
@ -1520,16 +1551,56 @@ void draw_bars(void) {
set_font_colors(outputs_walk->bargc, fg_color, bg_color);
draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, ws_walk->name_width);
i += 10 + ws_walk->name_width + 1;
}
if (!has_urgent && !mod_pressed && walks_away) {
FREE(last_urgent_ws);
hide_bars();
if (binding.name) {
uint32_t fg_color = colors.urgent_ws_fg;
uint32_t bg_color = colors.urgent_ws_bg;
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
uint32_t vals_border[] = { colors.urgent_ws_border, colors.urgent_ws_border };
xcb_change_gc(xcb_connection,
outputs_walk->bargc,
mask,
vals_border);
xcb_rectangle_t rect_border = { i, 0, binding.width + 10, font.height + 4 };
xcb_poly_fill_rectangle(xcb_connection,
outputs_walk->buffer,
outputs_walk->bargc,
1,
&rect_border);
uint32_t vals[] = { bg_color, bg_color };
xcb_change_gc(xcb_connection,
outputs_walk->bargc,
mask,
vals);
xcb_rectangle_t rect = { i + 1, 1, binding.width + 8, font.height + 2 };
xcb_poly_fill_rectangle(xcb_connection,
outputs_walk->buffer,
outputs_walk->bargc,
1,
&rect);
set_font_colors(outputs_walk->bargc, fg_color, bg_color);
draw_text(binding.name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, binding.width);
}
i = 0;
}
if (!mod_pressed) {
if (unhide) {
/* The urgent-hint should get noticed, so we unhide the bars shortly */
unhide_bars();
} else if (walks_away) {
FREE(last_urgent_ws);
hide_bars();
}
}
redraw_bars();
}
@ -1554,3 +1625,13 @@ void redraw_bars(void) {
xcb_flush(xcb_connection);
}
}
/*
* Set the current binding mode
*
*/
void set_current_mode(struct mode *current) {
I3STRING_FREE(binding.name);
binding = *current;
return;
}

View File

@ -10,8 +10,8 @@
* compile-time.
*
*/
#ifndef _ALL_H
#define _ALL_H
#ifndef I3_ALL_H
#define I3_ALL_H
#include <assert.h>
#include <stdbool.h>
@ -79,6 +79,8 @@
#include "scratchpad.h"
#include "commands.h"
#include "commands_parser.h"
#include "config_directives.h"
#include "config_parser.h"
#include "fake_outputs.h"
#include "display_version.h"

View File

@ -7,8 +7,8 @@
* assignments.c: Assignments for specific windows (for_window).
*
*/
#ifndef _ASSIGNMENTS_H
#define _ASSIGNMENTS_H
#ifndef I3_ASSIGNMENTS_H
#define I3_ASSIGNMENTS_H
/**
* Checks the list of assignments for the given window and runs all matching

View File

@ -7,8 +7,8 @@
* click.c: Button press (mouse click) events.
*
*/
#ifndef _CLICK_H
#define _CLICK_H
#ifndef I3_CLICK_H
#define I3_CLICK_H
/**
* The button press X callback. This function determines whether the floating

View File

@ -7,8 +7,8 @@
* cmdparse.y: the parser for commands you send to i3 (or bind on keys)
*
*/
#ifndef _CMDPARSE_H
#define _CMDPARSE_H
#ifndef I3_CMDPARSE_H
#define I3_CMDPARSE_H
char *parse_cmd(const char *new);

View File

@ -7,26 +7,14 @@
* commands.c: all command functions (see commands_parser.c)
*
*/
#ifndef _COMMANDS_H
#define _COMMANDS_H
#ifndef I3_COMMANDS_H
#define I3_COMMANDS_H
#include "commands_parser.h"
/** The beginning of the prototype for every cmd_ function. */
#define I3_CMD Match *current_match, struct CommandResult *cmd_output
/*
* Helper data structure for an operation window (window on which the operation
* will be performed). Used to build the TAILQ owindows.
*
*/
typedef struct owindow {
Con *con;
TAILQ_ENTRY(owindow) owindows;
} owindow;
typedef TAILQ_HEAD(owindows_head, owindow) owindows_head;
/**
* Initializes the specified 'Match' data structure and the initial state of
* commands.c for matching target windows of a command.
@ -55,6 +43,12 @@ void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue);
*/
void cmd_move_con_to_workspace(I3_CMD, char *which);
/**
* Implementation of 'move [window|container] [to] workspace back_and_forth'.
*
*/
void cmd_move_con_to_workspace_back_and_forth(I3_CMD);
/**
* Implementation of 'move [window|container] [to] workspace <name>'.
*
@ -77,7 +71,7 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz
* Implementation of 'border normal|none|1pixel|toggle'.
*
*/
void cmd_border(I3_CMD, char *border_style_str);
void cmd_border(I3_CMD, char *border_style_str, char *border_width);
/**
* Implementation of 'nop <comment>'.
@ -152,7 +146,7 @@ void cmd_move_workspace_to_output(I3_CMD, char *name);
void cmd_split(I3_CMD, char *direction);
/**
* Implementaiton of 'kill [window|client]'.
* Implementation of 'kill [window|client]'.
*
*/
void cmd_kill(I3_CMD, char *kill_mode_str);
@ -212,25 +206,25 @@ void cmd_layout(I3_CMD, char *layout_str);
void cmd_layout_toggle(I3_CMD, char *toggle_mode);
/**
* Implementaiton of 'exit'.
* Implementation of 'exit'.
*
*/
void cmd_exit(I3_CMD);
/**
* Implementaiton of 'reload'.
* Implementation of 'reload'.
*
*/
void cmd_reload(I3_CMD);
/**
* Implementaiton of 'restart'.
* Implementation of 'restart'.
*
*/
void cmd_restart(I3_CMD);
/**
* Implementaiton of 'open'.
* Implementation of 'open'.
*
*/
void cmd_open(I3_CMD);

View File

@ -7,8 +7,8 @@
* commands.c: all command functions (see commands_parser.c)
*
*/
#ifndef _COMMANDS_PARSER_H
#define _COMMANDS_PARSER_H
#ifndef I3_COMMANDS_PARSER_H
#define I3_COMMANDS_PARSER_H
#include <yajl/yajl_gen.h>
@ -27,6 +27,11 @@ struct CommandResult {
/* Whether the command requires calling tree_render. */
bool needs_tree_render;
/* The next state to transition to. Passed to the function so that we can
* determine the next state as a result of a function call, like
* cfg_criteria_pop_state() does. */
int next_state;
};
struct CommandResult *parse_command(const char *input);

View File

@ -9,8 +9,8 @@
* ).
*
*/
#ifndef _CON_H
#define _CON_H
#ifndef I3_CON_H
#define I3_CON_H
/**
* Create a new container (and attach it to the given parent, if not NULL).
@ -33,6 +33,18 @@ void con_focus(Con *con);
*/
bool con_is_leaf(Con *con);
/*
* Returns true if a container should be considered split.
*
*/
bool con_is_split(Con *con);
/**
* Returns true if this node has regular or floating children.
*
*/
bool con_has_children(Con *con);
/**
* Returns true if this node accepts a window (if the node swallows windows,
* it might already have swallowed enough and cannot hold any more).
@ -66,6 +78,12 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation);
*/
Con *con_get_fullscreen_con(Con *con, int fullscreen_mode);
/**
* Returns true if the container is internal, such as __i3_scratch
*
*/
bool con_is_internal(Con *con);
/**
* Returns true if the node is floating.
*
@ -244,7 +262,7 @@ int con_border_style(Con *con);
* floating window.
*
*/
void con_set_border_style(Con *con, int border_style);
void con_set_border_style(Con *con, int border_style, int border_width);
/**
* This function changes the layout of a given container. Use it to handle
@ -293,4 +311,23 @@ Rect con_minimum_size(Con *con);
*/
bool con_fullscreen_permits_focusing(Con *con);
/**
* Checks if the given container has an urgent child.
*
*/
bool con_has_urgent_child(Con *con);
/**
* Make all parent containers urgent if con is urgent or clear the urgent flag
* of all parent containers if there are no more urgent children left.
*
*/
void con_update_parents_urgency(Con *con);
/**
* Create a string representing the subtree under con.
*
*/
char *con_get_tree_representation(Con *con);
#endif

View File

@ -10,8 +10,8 @@
* mode).
*
*/
#ifndef _CONFIG_H
#define _CONFIG_H
#ifndef I3_CONFIG_H
#define I3_CONFIG_H
#include <stdbool.h>
#include "queue.h"
@ -24,6 +24,8 @@ extern char *current_configpath;
extern Config config;
extern SLIST_HEAD(modes_head, Mode) modes;
extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs;
/* defined in src/cfgparse.y */
extern bool force_old_config_parser;
/**
* Used during the config file lexing/parsing to keep the state of the lexer
@ -98,6 +100,7 @@ struct Config {
int default_layout;
int container_stack_limit;
int container_stack_limit_value;
int default_border_width;
/** Default orientation for new containers */
int default_orientation;
@ -149,6 +152,13 @@ struct Config {
* between two workspaces. */
bool workspace_auto_back_and_forth;
/** By default, urgency is cleared immediately when switching to another
* workspace leads to focusing the con with the urgency hint. When having
* multiple windows on that workspace, the user needs to guess which
* application raised the event. To prevent this, the reset of the urgency
* flag can be delayed using an urgency timer. */
float workspace_urgency_timer;
/** The default border style for new windows. */
border_style_t default_border;
@ -181,8 +191,15 @@ struct Config {
/** What should happen when a new popup is opened during fullscreen mode */
enum {
PDF_LEAVE_FULLSCREEN = 0,
PDF_IGNORE = 1
/* display (and focus) the popup when it belongs to the fullscreen
* window only. */
PDF_SMART = 0,
/* leave fullscreen mode unconditionally */
PDF_LEAVE_FULLSCREEN = 1,
/* just ignore the popup, that is, dont map it */
PDF_IGNORE = 2,
} popup_during_fullscreen;
};

View File

@ -0,0 +1,78 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
*
* config_directives.h: all config storing functions (see config_parser.c)
*
*/
#ifndef I3_CONFIG_DIRECTIVES_H
#define I3_CONFIG_DIRECTIVES_H
#include "config_parser.h"
/** The beginning of the prototype for every cfg_ function. */
#define I3_CFG Match *current_match, struct ConfigResult *result
/* Defines a configuration function, that is, anything that can be called by
* using 'call cfg_foo()' in parser-specs/.*.spec. Useful so that we dont need
* to repeat the definition all the time. */
#define CFGFUN(name, ...) \
void cfg_ ## name (I3_CFG, ## __VA_ARGS__ )
/* The following functions are called by the config parser, see
* parser-specs/config.spec. They get the parsed parameters and store them in
* our data structures, e.g. cfg_font gets a font name and stores it in
* config.font.
*
* Since they are so similar, individual comments were omitted. */
CFGFUN(criteria_init, int _state);
CFGFUN(criteria_add, const char *ctype, const char *cvalue);
CFGFUN(criteria_pop_state);
CFGFUN(font, const char *font);
CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command);
CFGFUN(for_window, const char *command);
CFGFUN(floating_minimum_size, const long width, const long height);
CFGFUN(floating_maximum_size, const long width, const long height);
CFGFUN(default_orientation, const char *orientation);
CFGFUN(workspace_layout, const char *layout);
CFGFUN(workspace_back_and_forth, const char *value);
CFGFUN(focus_follows_mouse, const char *value);
CFGFUN(force_focus_wrapping, const char *value);
CFGFUN(force_xinerama, const char *value);
CFGFUN(fake_outputs, const char *outputs);
CFGFUN(force_display_urgency_hint, const long duration_ms);
CFGFUN(hide_edge_borders, const char *borders);
CFGFUN(assign, const char *workspace);
CFGFUN(ipc_socket, const char *path);
CFGFUN(restart_state, const char *path);
CFGFUN(popup_during_fullscreen, const char *value);
CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator);
CFGFUN(color_single, const char *colorclass, const char *color);
CFGFUN(floating_modifier, const char *modifiers);
CFGFUN(new_window, const char *windowtype, const char *border, const long width);
CFGFUN(workspace, const char *workspace, const char *output);
CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command);
CFGFUN(enter_mode, const char *mode);
CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command);
CFGFUN(bar_font, const char *font);
CFGFUN(bar_mode, const char *mode);
CFGFUN(bar_output, const char *output);
CFGFUN(bar_verbose, const char *verbose);
CFGFUN(bar_modifier, const char *modifier);
CFGFUN(bar_position, const char *position);
CFGFUN(bar_i3bar_command, const char *i3bar_command);
CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text);
CFGFUN(bar_socket_path, const char *socket_path);
CFGFUN(bar_tray_output, const char *output);
CFGFUN(bar_color_single, const char *colorclass, const char *color);
CFGFUN(bar_status_command, const char *command);
CFGFUN(bar_workspace_buttons, const char *value);
CFGFUN(bar_finish);
#endif

32
include/config_parser.h Normal file
View File

@ -0,0 +1,32 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
*
* config_parser.h: config parser-related definitions
*
*/
#ifndef I3_CONFIG_PARSER_H
#define I3_CONFIG_PARSER_H
#include <yajl/yajl_gen.h>
/*
* The result of a parse_config call. Currently unused, but the JSON output
* will be useful in the future when we implement a config parsing IPC command.
*
*/
struct ConfigResult {
/* The JSON generator to append a reply to. */
yajl_gen json_gen;
/* The next state to transition to. Passed to the function so that we can
* determine the next state as a result of a function call, like
* cfg_criteria_pop_state() does. */
int next_state;
};
struct ConfigResult *parse_config(const char *input, struct context *context);
#endif

View File

@ -7,8 +7,8 @@
* include/data.h: This file defines all data structures used by i3
*
*/
#ifndef _DATA_H
#define _DATA_H
#ifndef I3_DATA_H
#define I3_DATA_H
#define SN_API_NOT_YET_FROZEN 1
#include <libsn/sn-launcher.h>
@ -55,7 +55,7 @@ typedef struct Window i3Window;
*****************************************************************************/
typedef enum { D_LEFT, D_RIGHT, D_UP, D_DOWN } direction_t;
typedef enum { NO_ORIENTATION = 0, HORIZ, VERT } orientation_t;
typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_1PIXEL = 2 } border_style_t;
typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_PIXEL = 2 } border_style_t;
/** parameter to specify whether tree_close() and x_window_kill() should kill
* only this specific window or the whole X11 client */
@ -441,8 +441,6 @@ struct Assignment {
*/
struct Con {
bool mapped;
/** whether this is a split container or not */
bool split;
enum {
CT_ROOT = 0,
CT_OUTPUT = 1,
@ -486,6 +484,7 @@ struct Con {
/* the x11 border pixel attribute */
int border_width;
int current_border_width;
/* minimum increment size specified for the window (in pixels) */
int width_increment;
@ -497,6 +496,9 @@ struct Con {
* inside this container (if any) sets the urgency hint, for example. */
bool urgent;
/* timer used for disabling urgency */
struct ev_timer *urgency_timer;
/* ids/pixmap/graphics context for the frame window */
xcb_window_t frame;
xcb_pixmap_t pixmap;

View File

@ -8,8 +8,8 @@
* events. This code is from xcb-util.
*
*/
#ifndef _DEBUG_H
#define _DEBUG_H
#ifndef I3_DEBUG_H
#define I3_DEBUG_H
int handle_event(void *ignored, xcb_connection_t *c, xcb_generic_event_t *e);

View File

@ -7,8 +7,8 @@
* display_version.c: displays the running i3 version, runs as part of
* i3 --moreversion.
*/
#ifndef _DISPLAY_VERSION_H
#define _DISPLAY_VERSION_H
#ifndef I3_DISPLAY_VERSION_H
#define I3_DISPLAY_VERSION_H
/**
* Connects to i3 to find out the currently running version. Useful since it

View File

@ -7,8 +7,8 @@
* ewmh.c: Get/set certain EWMH properties easily.
*
*/
#ifndef _EWMH_C
#define _EWMH_C
#ifndef I3_EWMH_C
#define I3_EWMH_C
/**
* Updates _NET_CURRENT_DESKTOP with the current desktop number.

View File

@ -8,8 +8,8 @@
* which dont support multi-monitor in a useful way) and for our testsuite.
*
*/
#ifndef _FAKE_OUTPUTS_H
#define _FAKE_OUTPUTS_H
#ifndef I3_FAKE_OUTPUTS_H
#define I3_FAKE_OUTPUTS_H
/**
* Creates outputs according to the given specification.

View File

@ -7,8 +7,8 @@
* floating.c: Floating windows.
*
*/
#ifndef _FLOATING_H
#define _FLOATING_H
#ifndef I3_FLOATING_H
#define I3_FLOATING_H
#include "tree.h"
@ -99,6 +99,14 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event);
*/
void floating_resize_window(Con *con, const bool proportional, const xcb_button_press_event_t *event);
/**
* Called when a floating window is created or resized.
* This function resizes the window if its size is higher or lower than the
* configured maximum/minimum size, respectively.
*
*/
void floating_check_size(Con *floating_con);
#if 0
/**
* Changes focus in the given direction for floating clients.
@ -134,8 +142,8 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace);
*
*/
void drag_pointer(Con *con, const xcb_button_press_event_t *event,
xcb_window_t confine_to, border_t border, callback_t callback,
const void *extra);
xcb_window_t confine_to, border_t border, int cursor,
callback_t callback, const void *extra);
/**
* Repositions the CT_FLOATING_CON to have the coordinates specified by

View File

@ -8,8 +8,8 @@
* ).
*
*/
#ifndef _HANDLERS_H
#define _HANDLERS_H
#ifndef I3_HANDLERS_H
#define I3_HANDLERS_H
#include <xcb/randr.h>

View File

@ -7,8 +7,8 @@
* i3.h: global variables that are used all over i3.
*
*/
#ifndef _I3_H
#define _I3_H
#ifndef I3_I3_H
#define I3_I3_H
#include <sys/time.h>
#include <sys/resource.h>

View File

@ -8,8 +8,8 @@
* for the IPC interface to i3 (see docs/ipc for more information).
*
*/
#ifndef _I3_IPC_H
#define _I3_IPC_H
#ifndef I3_I3_IPC_H
#define I3_I3_IPC_H
/*
* Messages from clients to i3
@ -84,4 +84,7 @@
/* The output event will be triggered upon changes in the output list */
#define I3_IPC_EVENT_OUTPUT (I3_IPC_EVENT_MASK | 1)
/* The output event will be triggered upon mode changes */
#define I3_IPC_EVENT_MODE (I3_IPC_EVENT_MASK | 2)
#endif

View File

@ -7,8 +7,8 @@
* ipc.c: UNIX domain socket IPC (initialization, client handling, protocol).
*
*/
#ifndef _IPC_H
#define _IPC_H
#ifndef I3_IPC_H
#define I3_IPC_H
#include <ev.h>
#include <stdbool.h>

View File

@ -7,8 +7,8 @@
* key_press.c: key press handler
*
*/
#ifndef _KEY_PRESS_H
#define _KEY_PRESS_H
#ifndef I3_KEY_PRESS_H
#define I3_KEY_PRESS_H
/**
* There was a key press. We compare this key code with our bindings table and pass

View File

@ -8,8 +8,8 @@
* as i3-msg, i3-config-wizard,
*
*/
#ifndef _LIBI3_H
#define _LIBI3_H
#ifndef I3_LIBI3_H
#define I3_LIBI3_H
#include <stdbool.h>
#include <stdarg.h>

View File

@ -8,8 +8,8 @@
* restart.
*
*/
#ifndef _LOAD_LAYOUT_H
#define _LOAD_LAYOUT_H
#ifndef I3_LOAD_LAYOUT_H
#define I3_LOAD_LAYOUT_H
void tree_append_json(const char *filename);

View File

@ -7,8 +7,8 @@
* log.c: Logging functions.
*
*/
#ifndef _LOG_H
#define _LOG_H
#ifndef I3_LOG_H
#define I3_LOG_H
#include <stdarg.h>
#include <stdbool.h>

View File

@ -7,8 +7,8 @@
* manage.c: Initially managing new windows (or existing ones on restart).
*
*/
#ifndef _MANAGE_H
#define _MANAGE_H
#ifndef I3_MANAGE_H
#define I3_MANAGE_H
#include "data.h"

View File

@ -11,8 +11,8 @@
* match_matches_window() to find the windows affected by this command.
*
*/
#ifndef _MATCH_H
#define _MATCH_H
#ifndef I3_MATCH_H
#define I3_MATCH_H
/*
* Initializes the Match data structure. This function is necessary because the

View File

@ -7,8 +7,8 @@
* move.c: Moving containers into some direction.
*
*/
#ifndef _MOVE_H
#define _MOVE_H
#ifndef I3_MOVE_H
#define I3_MOVE_H
/**
* Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT,

View File

@ -7,8 +7,8 @@
* output.c: Output (monitor) related functions.
*
*/
#ifndef _OUTPUT_H
#define _OUTPUT_H
#ifndef I3_OUTPUT_H
#define I3_OUTPUT_H
/**
* Returns the output container below the given output container.

View File

@ -9,8 +9,8 @@
* (take your time to read it completely, it answers all questions).
*
*/
#ifndef _RANDR_H
#define _RANDR_H
#ifndef I3_RANDR_H
#define I3_RANDR_H
#include "data.h"
#include <xcb/randr.h>
@ -18,6 +18,11 @@
TAILQ_HEAD(outputs_head, xoutput);
extern struct outputs_head outputs;
typedef enum {
CLOSEST_OUTPUT = 0,
FARTHEST_OUTPUT = 1
} output_close_far_t;
/**
* We have just established a connection to the X server and need the initial
* XRandR information to setup workspaces for each screen.
@ -96,6 +101,6 @@ Output *get_output_most(direction_t direction, Output *current);
* Gets the output which is the next one in the given direction.
*
*/
Output *get_output_next(direction_t direction, Output *current);
Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far);
#endif

View File

@ -7,8 +7,8 @@
* regex.c: Interface to libPCRE (perl compatible regular expressions).
*
*/
#ifndef _REGEX_H
#define _REGEX_H
#ifndef I3_REGEX_H
#define I3_REGEX_H
/**
* Creates a new 'regex' struct containing the given pattern and a PCRE

View File

@ -8,8 +8,8 @@
* various rects. Needs to be pushed to X11 (see x.c) to be visible.
*
*/
#ifndef _RENDER_H
#define _RENDER_H
#ifndef I3_RENDER_H
#define I3_RENDER_H
/**
* "Renders" the given container (and its children), meaning that all rects are

View File

@ -7,8 +7,8 @@
* resize.c: Interactive resizing.
*
*/
#ifndef _RESIZE_H
#define _RESIZE_H
#ifndef I3_RESIZE_H
#define I3_RESIZE_H
int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);

View File

@ -7,8 +7,8 @@
* scratchpad.c: Scratchpad functions (TODO: more description)
*
*/
#ifndef _SCRATCHPAD_H
#define _SCRATCHPAD_H
#ifndef I3_SCRATCHPAD_H
#define I3_SCRATCHPAD_H
/**
* Moves the specified window to the __i3_scratch workspace, making it floating

View File

@ -8,8 +8,8 @@
* default (ringbuffer for storing the debug log).
*
*/
#ifndef _I3_SHMLOG_H
#define _I3_SHMLOG_H
#ifndef I3_I3_SHMLOG_H
#define I3_I3_SHMLOG_H
#include <stdint.h>
#include <pthread.h>

View File

@ -9,8 +9,8 @@
* to restart inplace).
*
*/
#ifndef _SIGHANDLER_H
#define _SIGHANDLER_H
#ifndef I3_SIGHANDLER_H
#define I3_SIGHANDLER_H
/**
* Setup signal handlers to safely handle SIGSEGV and SIGFPE

View File

@ -10,8 +10,8 @@
* the appropriate workspace.
*
*/
#ifndef _STARTUP_H
#define _STARTUP_H
#ifndef I3_STARTUP_H
#define I3_STARTUP_H
#define SN_API_NOT_YET_FROZEN 1
#include <libsn/sn-monitor.h>
@ -31,12 +31,27 @@
*/
void start_application(const char *command, bool no_startup_id);
/**
* Deletes a startup sequence, ignoring whether its timeout has elapsed.
* Useful when e.g. a window is moved between workspaces and its children
* shouldn't spawn on the original workspace.
*
*/
void startup_sequence_delete(struct Startup_Sequence *sequence);
/**
* Called by libstartup-notification when something happens
*
*/
void startup_monitor_event(SnMonitorEvent *event, void *userdata);
/**
* Gets the stored startup sequence for the _NET_STARTUP_ID of a given window.
*
*/
struct Startup_Sequence *startup_sequence_get(i3Window *cwindow,
xcb_get_property_reply_t *startup_id_reply, bool ignore_mapped_leader);
/**
* Checks if the given window belongs to a startup notification by checking if
* the _NET_STARTUP_ID property is set on the window (or on its leader, if its

View File

@ -7,8 +7,8 @@
* tree.c: Everything that primarily modifies the layout tree data structure.
*
*/
#ifndef _TREE_H
#define _TREE_H
#ifndef I3_TREE_H
#define I3_TREE_H
extern Con *croot;
/* TODO: i am not sure yet how much access to the focused container should

View File

@ -8,8 +8,8 @@
* also libi3).
*
*/
#ifndef _UTIL_H
#define _UTIL_H
#ifndef I3_UTIL_H
#define I3_UTIL_H
#include <err.h>

View File

@ -7,8 +7,8 @@
* window.c: Updates window attributes (X11 hints/properties).
*
*/
#ifndef _WINDOW_H
#define _WINDOW_H
#ifndef I3_WINDOW_H
#define I3_WINDOW_H
/**
* Updates the WM_CLASS (consisting of the class and instance) for the

View File

@ -8,8 +8,8 @@
* workspaces.
*
*/
#ifndef _WORKSPACE_H
#define _WORKSPACE_H
#ifndef I3_WORKSPACE_H
#define I3_WORKSPACE_H
#include "data.h"
#include "tree.h"
@ -95,6 +95,12 @@ Con* workspace_prev_on_output(void);
*/
void workspace_back_and_forth(void);
/**
* Returns the previously focused workspace con, or NULL if unavailable.
*
*/
Con *workspace_back_and_forth_get(void);
#if 0
/**
@ -168,4 +174,11 @@ void ws_force_orientation(Con *ws, orientation_t orientation);
*/
Con *workspace_attach_to(Con *ws);
/**
* Creates a new container and re-parents all of children from the given
* workspace into it.
*
* The container inherits the layout from the workspace.
*/
Con *workspace_encapsulate(Con *ws);
#endif

View File

@ -8,8 +8,8 @@
* render.c). Basically a big state machine.
*
*/
#ifndef _X_H
#define _X_H
#ifndef I3_X_H
#define I3_X_H
/** Stores the X11 window ID of the currently focused window */
extern xcb_window_t focused_id;

View File

@ -7,8 +7,8 @@
* xcb.c: Helper functions for easier usage of XCB
*
*/
#ifndef _XCB_H
#define _XCB_H
#ifndef I3_XCB_H
#define I3_XCB_H
#include "data.h"
#include "xcursor.h"

View File

@ -9,8 +9,8 @@
* older versions.
*
*/
#ifndef _XCB_COMPAT_H
#define _XCB_COMPAT_H
#ifndef I3_XCB_COMPAT_H
#define I3_XCB_COMPAT_H
#define xcb_icccm_get_wm_protocols_reply_t xcb_get_wm_protocols_reply_t
#define xcb_icccm_get_wm_protocols xcb_get_wm_protocols

View File

@ -7,8 +7,8 @@
* xcursor.c: libXcursor support for themed cursors.
*
*/
#ifndef _XCURSOR_CURSOR_H
#define _XCURSOR_CURSOR_H
#ifndef I3_XCURSOR_CURSOR_H
#define I3_XCURSOR_CURSOR_H
#include <X11/Xlib.h>
@ -16,7 +16,12 @@ enum xcursor_cursor_t {
XCURSOR_CURSOR_POINTER = 0,
XCURSOR_CURSOR_RESIZE_HORIZONTAL,
XCURSOR_CURSOR_RESIZE_VERTICAL,
XCURSOR_CURSOR_TOP_LEFT_CORNER,
XCURSOR_CURSOR_TOP_RIGHT_CORNER,
XCURSOR_CURSOR_BOTTOM_LEFT_CORNER,
XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER,
XCURSOR_CURSOR_WATCH,
XCURSOR_CURSOR_MOVE,
XCURSOR_CURSOR_MAX
};

View File

@ -9,8 +9,8 @@
* driver which does not support RandR in 2011 *sigh*.
*
*/
#ifndef _XINERAMA_H
#define _XINERAMA_H
#ifndef I3_XINERAMA_H
#define I3_XINERAMA_H
#include "data.h"

31
include/yajl_utils.h Normal file
View File

@ -0,0 +1,31 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
* yajl_utils.h
*
*/
#ifndef I3_YAJL_UTILS_H
#define I3_YAJL_UTILS_H
#include <yajl/yajl_gen.h>
#include <yajl/yajl_parse.h>
#include <yajl/yajl_version.h>
/* Shorter names for all those yajl_gen_* functions */
#define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__)
#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str))
#if YAJL_MAJOR >= 2
#define ygenalloc() yajl_gen_alloc(NULL)
#define yalloc(callbacks, client) yajl_alloc(callbacks, NULL, client)
typedef size_t ylength;
#else
#define ygenalloc() yajl_gen_alloc(NULL, NULL);
#define yalloc(callbacks, client) yajl_alloc(callbacks, NULL, NULL, client)
typedef unsigned int ylength;
#endif
#endif

View File

@ -142,7 +142,11 @@ i3Font load_font(const char *pattern, const bool fallback) {
#if PANGO_SUPPORT
/* Try to load a pango font if specified */
if (strlen(pattern) > strlen("xft:") && !strncmp(pattern, "xft:", strlen("xft:"))) {
if (strlen(pattern) > strlen("pango:") && !strncmp(pattern, "pango:", strlen("pango:"))) {
pattern += strlen("pango:");
if (load_pango_font(&font, pattern))
return font;
} else if (strlen(pattern) > strlen("xft:") && !strncmp(pattern, "xft:", strlen("xft:"))) {
pattern += strlen("xft:");
if (load_pango_font(&font, pattern))
return font;

View File

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

View File

@ -24,7 +24,8 @@ workspaces.
get_outputs::
Gets the current outputs. The reply will be a JSON-encoded list of outputs (see
the reply section).
the reply section of docs/ipc, e.g. at
http://i3wm.org/docs/ipc.html#_receiving_replies_from_i3).
get_tree::
Gets the layout tree. i3 uses a tree as data structure which includes every

View File

@ -1,10 +1,12 @@
DISTCLEAN_TARGETS += clean-mans
A2X = a2x
POD2MAN = pod2man
A2X_MAN_CALL = $(V_A2X)$(A2X) -f manpage --asciidoc-opts="-f man/asciidoc.conf" $(A2X_FLAGS) $<
POD2MAN_CALL = $(V_POD2MAN)$(POD2MAN) --utf8 $< > $@
MANS_1 = \
MANS_ASCIIDOC = \
man/i3.1 \
man/i3bar.1 \
man/i3-msg.1 \
@ -17,14 +19,21 @@ MANS_1 = \
man/i3-sensible-terminal.1 \
man/i3-dump-log.1
MANS_POD = \
man/i3-dmenu-desktop.1
MANS = \
$(MANS_1)
$(MANS_ASCIIDOC) \
$(MANS_POD)
mans: $(MANS)
$(MANS_1): %.1: %.man man/asciidoc.conf
$(MANS_ASCIIDOC): %.1: %.man man/asciidoc.conf
$(A2X_MAN_CALL)
$(MANS_POD): %.1: i3-dmenu-desktop
$(POD2MAN_CALL)
clean-mans:
for file in $(notdir $(MANS)); \
do \

View File

@ -61,10 +61,20 @@ state EXEC:
command = string
-> call cmd_exec($nosn, $command)
# border normal|none|1pixel|toggle
# border normal|none|1pixel|toggle|1pixel
state BORDER:
border_style = 'normal', 'none', '1pixel', 'toggle'
-> call cmd_border($border_style)
border_style = 'normal', 'pixel'
-> BORDER_WIDTH
border_style = 'none', 'toggle'
-> call cmd_border($border_style, "0")
border_style = '1pixel'
-> call cmd_border($border_style, "1")
state BORDER_WIDTH:
end
-> call cmd_border($border_style, "2")
border_width = word
-> call cmd_border($border_style, $border_width)
# layout default|stacked|stacking|tabbed|splitv|splith
# layout toggle [split|all]
@ -185,17 +195,30 @@ state RESIZE_TILING_OR:
-> call cmd_resize($way, $direction, $resize_px, $resize_ppt)
# rename workspace <name> to <name>
# rename workspace to <name>
state RENAME:
'workspace'
-> RENAME_WORKSPACE
state RENAME_WORKSPACE:
old_name = 'to'
-> RENAME_WORKSPACE_LIKELY_TO
old_name = word
-> RENAME_WORKSPACE_TO
state RENAME_WORKSPACE_LIKELY_TO:
'to'
-> RENAME_WORKSPACE_NEW_NAME
new_name = word
-> call cmd_rename_workspace(NULL, $new_name)
state RENAME_WORKSPACE_TO:
'to'
->
-> RENAME_WORKSPACE_NEW_NAME
state RENAME_WORKSPACE_NEW_NAME:
end
-> call cmd_rename_workspace(NULL, "to")
new_name = string
-> call cmd_rename_workspace($old_name, $new_name)
@ -243,6 +266,8 @@ state MOVE_WORKSPACE:
-> MOVE_WORKSPACE_TO_OUTPUT
workspace = 'next', 'prev', 'next_on_output', 'prev_on_output', 'current'
-> call cmd_move_con_to_workspace($workspace)
'back_and_forth'
-> call cmd_move_con_to_workspace_back_and_forth()
'number'
-> MOVE_WORKSPACE_NUMBER
workspace = string

446
parser-specs/config.spec Normal file
View File

@ -0,0 +1,446 @@
# vim:ts=2:sw=2:expandtab
#
# i3 - an improved dynamic tiling window manager
# © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
#
# parser-specs/config.spec: Specification file for generate-command-parser.pl
# which will generate the appropriate header files for our C parser.
#
# Use :source highlighting.vim in vim to get syntax highlighting
# for this file.
# TODO: should we implement an include statement for the criteria part so we DRY?
state INITIAL:
# We have an end token here for all the commands which just call some
# function without using an explicit 'end' token.
end ->
error ->
'#' -> IGNORE_LINE
'set' -> IGNORE_LINE
bindtype = 'bindsym', 'bindcode', 'bind' -> BINDING
'bar' -> BARBRACE
'font' -> FONT
'mode' -> MODENAME
'floating_minimum_size' -> FLOATING_MINIMUM_SIZE_WIDTH
'floating_maximum_size' -> FLOATING_MAXIMUM_SIZE_WIDTH
'floating_modifier' -> FLOATING_MODIFIER
'default_orientation' -> DEFAULT_ORIENTATION
'workspace_layout' -> WORKSPACE_LAYOUT
windowtype = 'new_window', 'new_float' -> NEW_WINDOW
'hide_edge_borders' -> HIDE_EDGE_BORDERS
'for_window' -> FOR_WINDOW
'assign' -> ASSIGN
'focus_follows_mouse' -> FOCUS_FOLLOWS_MOUSE
'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING
'force_xinerama', 'force-xinerama' -> FORCE_XINERAMA
'workspace_auto_back_and_forth' -> WORKSPACE_BACK_AND_FORTH
'fake_outputs', 'fake-outputs' -> FAKE_OUTPUTS
'force_display_urgency_hint' -> FORCE_DISPLAY_URGENCY_HINT
'workspace' -> WORKSPACE
'ipc_socket', 'ipc-socket' -> IPC_SOCKET
'restart_state' -> RESTART_STATE
'popup_during_fullscreen' -> POPUP_DURING_FULLSCREEN
exectype = 'exec_always', 'exec' -> EXEC
colorclass = 'client.background'
-> COLOR_SINGLE
colorclass = 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
-> COLOR_BORDER
# We ignore comments and 'set' lines (variables).
state IGNORE_LINE:
end, string
-> INITIAL
# floating_minimum_size <width> x <height>
state FLOATING_MINIMUM_SIZE_WIDTH:
width = number
-> FLOATING_MINIMUM_SIZE_X
state FLOATING_MINIMUM_SIZE_X:
'x'
-> FLOATING_MINIMUM_SIZE_HEIGHT
state FLOATING_MINIMUM_SIZE_HEIGHT:
height = number
-> call cfg_floating_minimum_size(&width, &height)
# floating_maximum_size <width> x <height>
state FLOATING_MAXIMUM_SIZE_WIDTH:
width = number
-> FLOATING_MAXIMUM_SIZE_X
state FLOATING_MAXIMUM_SIZE_X:
'x'
-> FLOATING_MAXIMUM_SIZE_HEIGHT
state FLOATING_MAXIMUM_SIZE_HEIGHT:
height = number
-> call cfg_floating_maximum_size(&width, &height)
# floating_modifier <modifier>
state FLOATING_MODIFIER:
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl'
->
'+'
->
end
-> call cfg_floating_modifier($modifiers)
# default_orientation <horizontal|vertical|auto>
state DEFAULT_ORIENTATION:
orientation = 'horizontal', 'vertical', 'auto'
-> call cfg_default_orientation($orientation)
# workspace_layout <default|stacking|tabbed>
state WORKSPACE_LAYOUT:
layout = 'default', 'stacking', 'stacked', 'tabbed'
-> call cfg_workspace_layout($layout)
# new_window <normal|1pixel|none>
# new_float <normal|1pixel|none>
# TODO: new_float is not in the userguide yet
# TODO: pixel is not in the userguide yet
state NEW_WINDOW:
border = 'normal', 'pixel'
-> NEW_WINDOW_PIXELS
border = '1pixel', 'none'
-> call cfg_new_window($windowtype, $border, -1)
state NEW_WINDOW_PIXELS:
end
-> call cfg_new_window($windowtype, $border, 2)
width = number
-> NEW_WINDOW_PIXELS_PX
state NEW_WINDOW_PIXELS_PX:
'px'
->
end
-> call cfg_new_window($windowtype, $border, &width)
# hide_edge_borders <none|vertical|horizontal|both>
# also hide_edge_borders <bool> for compatibility
state HIDE_EDGE_BORDERS:
hide_borders = 'none', 'vertical', 'horizontal', 'both'
-> call cfg_hide_edge_borders($hide_borders)
hide_borders = '1', 'yes', 'true', 'on', 'enable', 'active'
-> call cfg_hide_edge_borders($hide_borders)
# for_window <criteria> command
state FOR_WINDOW:
'['
-> call cfg_criteria_init(FOR_WINDOW_COMMAND); CRITERIA
state FOR_WINDOW_COMMAND:
command = string
-> call cfg_for_window($command)
# assign <criteria> [→] workspace
state ASSIGN:
'['
-> call cfg_criteria_init(ASSIGN_WORKSPACE); CRITERIA
state ASSIGN_WORKSPACE:
'→'
->
workspace = string
-> call cfg_assign($workspace)
# Criteria: Used by for_window and assign.
state CRITERIA:
ctype = 'class' -> CRITERION
ctype = 'instance' -> CRITERION
ctype = 'window_role' -> CRITERION
ctype = 'con_id' -> CRITERION
ctype = 'id' -> CRITERION
ctype = 'con_mark' -> CRITERION
ctype = 'title' -> CRITERION
ctype = 'urgent' -> CRITERION
']'
-> call cfg_criteria_pop_state()
state CRITERION:
'=' -> CRITERION_STR
state CRITERION_STR:
cvalue = word
-> call cfg_criteria_add($ctype, $cvalue); CRITERIA
# focus_follows_mouse bool
state FOCUS_FOLLOWS_MOUSE:
value = word
-> call cfg_focus_follows_mouse($value)
# force_focus_wrapping
state FORCE_FOCUS_WRAPPING:
value = word
-> call cfg_force_focus_wrapping($value)
# force_xinerama
state FORCE_XINERAMA:
value = word
-> call cfg_force_xinerama($value)
# workspace_back_and_forth
state WORKSPACE_BACK_AND_FORTH:
value = word
-> call cfg_workspace_back_and_forth($value)
# fake_outputs (for testcases)
state FAKE_OUTPUTS:
outputs = string
-> call cfg_fake_outputs($outputs)
# force_display_urgency_hint <timeout> ms
state FORCE_DISPLAY_URGENCY_HINT:
duration_ms = number
-> FORCE_DISPLAY_URGENCY_HINT_MS
state FORCE_DISPLAY_URGENCY_HINT_MS:
'ms'
->
end
-> call cfg_force_display_urgency_hint(&duration_ms)
# workspace <workspace> output <output>
state WORKSPACE:
workspace = word
-> WORKSPACE_OUTPUT
state WORKSPACE_OUTPUT:
'output'
-> WORKSPACE_OUTPUT_STR
state WORKSPACE_OUTPUT_STR:
output = string
-> call cfg_workspace($workspace, $output)
# ipc-socket <path>
state IPC_SOCKET:
path = string
-> call cfg_ipc_socket($path)
# restart_state <path> (for testcases)
state RESTART_STATE:
path = string
-> call cfg_restart_state($path)
# popup_during_fullscreen
state POPUP_DURING_FULLSCREEN:
value = 'ignore', 'leave_fullscreen'
-> call cfg_popup_during_fullscreen($value)
# client.background <hexcolor>
state COLOR_SINGLE:
color = word
-> call cfg_color_single($colorclass, $color)
# colorclass border background text indicator
state COLOR_BORDER:
border = word
-> COLOR_BACKGROUND
state COLOR_BACKGROUND:
background = word
-> COLOR_TEXT
state COLOR_TEXT:
text = word
-> COLOR_INDICATOR
state COLOR_INDICATOR:
indicator = word
-> call cfg_color($colorclass, $border, $background, $text, $indicator)
end
-> call cfg_color($colorclass, $border, $background, $text, NULL)
# <exec|exec_always> [--no-startup-id] command
state EXEC:
no_startup_id = '--no-startup-id'
->
command = string
-> call cfg_exec($exectype, $no_startup_id, $command)
# font font
state FONT:
font = string
-> call cfg_font($font)
# bindsym/bindcode
state BINDING:
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch'
->
'+'
->
key = word
-> BINDCOMMAND
state BINDCOMMAND:
release = '--release'
->
command = string
-> call cfg_binding($bindtype, $modifiers, $key, $release, $command)
################################################################################
# Mode configuration
################################################################################
state MODENAME:
modename = word
-> call cfg_enter_mode($modename); MODEBRACE
state MODEBRACE:
end
->
'{'
-> MODE
state MODE:
end ->
error ->
'#' -> MODE_IGNORE_LINE
'set' -> MODE_IGNORE_LINE
bindtype = 'bindsym', 'bindcode', 'bind'
-> MODE_BINDING
'}'
-> INITIAL
# We ignore comments and 'set' lines (variables).
state MODE_IGNORE_LINE:
end, string
-> MODE
state MODE_BINDING:
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch'
->
'+'
->
key = word
-> MODE_BINDCOMMAND
state MODE_BINDCOMMAND:
release = '--release'
->
command = string
-> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $command); MODE
################################################################################
# Bar configuration (i3bar)
################################################################################
state BARBRACE:
end
->
'{'
-> BAR
state BAR:
end ->
error ->
'#' -> BAR_IGNORE_LINE
'set' -> BAR_IGNORE_LINE
'i3bar_command' -> BAR_BAR_COMMAND
'status_command' -> BAR_STATUS_COMMAND
'socket_path' -> BAR_SOCKET_PATH
'mode' -> BAR_MODE
'modifier' -> BAR_MODIFIER
'position' -> BAR_POSITION
'output' -> BAR_OUTPUT
'tray_output' -> BAR_TRAY_OUTPUT
'font' -> BAR_FONT
'workspace_buttons' -> BAR_WORKSPACE_BUTTONS
'verbose' -> BAR_VERBOSE
'colors' -> BAR_COLORS_BRACE
'}'
-> call cfg_bar_finish(); INITIAL
# We ignore comments and 'set' lines (variables).
state BAR_IGNORE_LINE:
end, string
-> BAR
state BAR_BAR_COMMAND:
command = string
-> call cfg_bar_i3bar_command($command); BAR
state BAR_STATUS_COMMAND:
command = string
-> call cfg_bar_status_command($command); BAR
state BAR_SOCKET_PATH:
path = string
-> call cfg_bar_socket_path($path); BAR
state BAR_MODE:
mode = 'dock', 'hide'
-> call cfg_bar_mode($mode); BAR
state BAR_MODIFIER:
modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift'
-> call cfg_bar_modifier($modifier); BAR
state BAR_POSITION:
position = 'top', 'bottom'
-> call cfg_bar_position($position); BAR
state BAR_OUTPUT:
output = string
-> call cfg_bar_output($output); BAR
state BAR_TRAY_OUTPUT:
output = string
-> call cfg_bar_tray_output($output); BAR
state BAR_FONT:
font = string
-> call cfg_bar_font($font); BAR
state BAR_WORKSPACE_BUTTONS:
value = word
-> call cfg_bar_workspace_buttons($value); BAR
state BAR_VERBOSE:
value = word
-> call cfg_bar_verbose($value); BAR
state BAR_COLORS_BRACE:
end
->
'{'
-> BAR_COLORS
state BAR_COLORS:
end ->
'#' -> BAR_COLORS_IGNORE_LINE
'set' -> BAR_COLORS_IGNORE_LINE
colorclass = 'background', 'statusline'
-> BAR_COLORS_SINGLE
colorclass = 'focused_workspace', 'active_workspace', 'inactive_workspace', 'urgent_workspace'
-> BAR_COLORS_BORDER
'}'
-> BAR
# We ignore comments and 'set' lines (variables).
state BAR_COLORS_IGNORE_LINE:
end, string
-> BAR_COLORS
state BAR_COLORS_SINGLE:
color = word
-> call cfg_bar_color_single($colorclass, $color); BAR_COLORS
state BAR_COLORS_BORDER:
border = word
-> BAR_COLORS_BACKGROUND
state BAR_COLORS_BACKGROUND:
background = word
-> BAR_COLORS_TEXT
state BAR_COLORS_TEXT:
end
-> call cfg_bar_color($colorclass, $border, $background, NULL); BAR_COLORS
text = word
-> call cfg_bar_color($colorclass, $border, $background, $text); BAR_COLORS

View File

@ -9,7 +9,7 @@ syntax match i3specComment /#.*/
highlight link i3specComment Comment
syntax region i3specLiteral start=/'/ end=/'/
syntax keyword i3specToken string word end
syntax keyword i3specToken string word number end
highlight link i3specLiteral String
highlight link i3specToken String

View File

@ -56,6 +56,7 @@ EOL (\r?\n)
%s OUTPUT_COND
%s FOR_WINDOW_COND
%s EAT_WHITESPACE
%s BORDER_WIDTH
%x BUFFER_LINE
%x BAR
@ -171,6 +172,7 @@ EOL (\r?\n)
}
<ASSIGN_TARGET_COND>[ \t]*→[ \t]* { BEGIN(WANT_STRING); }
<ASSIGN_TARGET_COND>[ \t]+ { BEGIN(WANT_STRING); }
<BORDER_WIDTH>[^\n][0-9]+ { printf("Border width set to: %s\n", yytext); yylval.number = atoi(yytext); return NUMBER;}
<EXEC>--no-startup-id { printf("no startup id\n"); yy_pop_state(); return TOK_NO_STARTUP_ID; }
<EXEC>. { printf("anything else: *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); }
<OPTRELEASE>--release { printf("--release\n"); yy_pop_state(); return TOK_RELEASE; }
@ -200,9 +202,10 @@ auto { return TOK_AUTO; }
workspace_layout { return TOK_WORKSPACE_LAYOUT; }
new_window { return TOKNEWWINDOW; }
new_float { return TOKNEWFLOAT; }
normal { return TOK_NORMAL; }
normal { yy_push_state(BORDER_WIDTH); return TOK_NORMAL; }
none { return TOK_NONE; }
1pixel { return TOK_1PIXEL; }
pixel { yy_push_state(BORDER_WIDTH); return TOK_PIXEL; }
hide_edge_borders { return TOK_HIDE_EDGE_BORDERS; }
both { return TOK_BOTH; }
focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; }
@ -212,6 +215,8 @@ force-xinerama { return TOK_FORCE_XINERAMA; }
fake_outputs { WS_STRING; return TOK_FAKE_OUTPUTS; }
fake-outputs { WS_STRING; return TOK_FAKE_OUTPUTS; }
workspace_auto_back_and_forth { return TOK_WORKSPACE_AUTO_BAF; }
force_display_urgency_hint { return TOK_WORKSPACE_URGENCY_TIMER; }
ms { return TOK_TIME_MS; }
workspace_bar { return TOKWORKSPACEBAR; }
popup_during_fullscreen { return TOK_POPUP_DURING_FULLSCREEN; }
ignore { return TOK_IGNORE; }

View File

@ -13,6 +13,8 @@
#include "all.h"
bool force_old_config_parser = false;
static pid_t configerror_pid = -1;
static Match current_match;
@ -108,6 +110,7 @@ static int detect_version(char *buf) {
strncasecmp(bind, "focus down", strlen("focus down")) == 0 ||
strncasecmp(bind, "border normal", strlen("border normal")) == 0 ||
strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 ||
strncasecmp(bind, "border pixel", strlen("border pixel")) == 0 ||
strncasecmp(bind, "border borderless", strlen("border borderless")) == 0 ||
strncasecmp(bind, "--no-startup-id", strlen("--no-startup-id")) == 0 ||
strncasecmp(bind, "bar", strlen("bar")) == 0) {
@ -624,15 +627,20 @@ void parse_file(const char *f) {
}
}
/* now lex/parse it */
yy_scan_string(new);
context = scalloc(sizeof(struct context));
context->filename = f;
if (yyparse() != 0) {
fprintf(stderr, "Could not parse configfile\n");
exit(1);
if (force_old_config_parser) {
/* now lex/parse it */
yy_scan_string(new);
if (yyparse() != 0) {
fprintf(stderr, "Could not parse configfile\n");
exit(1);
}
} else {
struct ConfigResult *config_output = parse_config(new, context);
yajl_gen_free(config_output->json_gen);
}
check_for_duplicate_bindings(context);
@ -668,7 +676,8 @@ void parse_file(const char *f) {
start_configerror_nagbar(f);
}
yylex_destroy();
if (force_old_config_parser)
yylex_destroy();
FREE(context->line_copy);
free(context);
FREE(font_pattern);
@ -728,6 +737,7 @@ void parse_file(const char *f) {
%token <color> TOKCOLOR
%token TOKARROW "→"
%token TOKMODE "mode"
%token TOK_TIME_MS "ms"
%token TOK_BAR "bar"
%token TOK_ORIENTATION "default_orientation"
%token TOK_HORIZ "horizontal"
@ -738,6 +748,7 @@ void parse_file(const char *f) {
%token TOKNEWFLOAT "new_float"
%token TOK_NORMAL "normal"
%token TOK_NONE "none"
%token TOK_PIXEL "pixel"
%token TOK_1PIXEL "1pixel"
%token TOK_HIDE_EDGE_BORDERS "hide_edge_borders"
%token TOK_BOTH "both"
@ -746,6 +757,7 @@ void parse_file(const char *f) {
%token TOK_FORCE_XINERAMA "force_xinerama"
%token TOK_FAKE_OUTPUTS "fake_outputs"
%token TOK_WORKSPACE_AUTO_BAF "workspace_auto_back_and_forth"
%token TOK_WORKSPACE_URGENCY_TIMER "force_display_urgency_hint"
%token TOKWORKSPACEBAR "workspace_bar"
%token TOK_DEFAULT "default"
%token TOK_STACKING "stacking"
@ -816,9 +828,11 @@ void parse_file(const char *f) {
%type <number> bar_mode_mode
%type <number> bar_modifier_modifier
%type <number> optional_no_startup_id
%type <number> optional_border_width
%type <number> optional_release
%type <string> command
%type <string> word_or_number
%type <string> duration
%type <string> qstring_or_number
%type <string> optional_workspace_name
%type <string> workspace_name
@ -848,6 +862,7 @@ line:
| force_focus_wrapping
| force_xinerama
| fake_outputs
| force_display_urgency_hint
| workspace_back_and_forth
| workspace_bar
| workspace
@ -1052,6 +1067,11 @@ word_or_number:
}
;
duration:
NUMBER { sasprintf(&$$, "%d", $1); }
| NUMBER TOK_TIME_MS { sasprintf(&$$, "%d", $1); }
;
mode:
TOKMODE QUOTEDSTRING '{' modelines '}'
{
@ -1471,9 +1491,27 @@ new_float:
;
border_style:
TOK_NORMAL { $$ = BS_NORMAL; }
| TOK_NONE { $$ = BS_NONE; }
| TOK_1PIXEL { $$ = BS_1PIXEL; }
TOK_NORMAL optional_border_width
{
/* FIXME: the whole border_style thing actually screws up when new_float is used because it overwrites earlier values :-/ */
config.default_border_width = $2;
$$ = BS_NORMAL;
}
| TOK_1PIXEL
{
config.default_border_width = 1;
$$ = BS_PIXEL;
}
| TOK_NONE
{
config.default_border_width = 0;
$$ = BS_NONE;
}
| TOK_PIXEL optional_border_width
{
config.default_border_width = $2;
$$ = BS_PIXEL;
}
;
bool:
@ -1548,6 +1586,14 @@ workspace_back_and_forth:
}
;
force_display_urgency_hint:
TOK_WORKSPACE_URGENCY_TIMER duration
{
DLOG("workspace urgency_timer = %f\n", atoi($2) / 1000.0);
config.workspace_urgency_timer = atoi($2) / 1000.0;
}
;
workspace_bar:
TOKWORKSPACEBAR bool
{
@ -1736,6 +1782,11 @@ exec_always:
}
;
optional_border_width:
/* empty */ { $$ = 2; } // 2 pixels is the default value for any type of border
| NUMBER { $$ = $1; }
;
optional_no_startup_id:
/* empty */ { $$ = false; }
| TOK_NO_STARTUP_ID { $$ = true; }

View File

@ -179,6 +179,22 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
DLOG("--> OUTCOME = %p\n", con);
DLOG("type = %d, name = %s\n", con->type, con->name);
/* Any click in a workspace should focus that workspace. If the
* workspace is on another output we need to do a workspace_show in
* order for i3bar (and others) to notice the change in workspace. */
Con *ws = con_get_workspace(con);
Con *focused_workspace = con_get_workspace(focused);
if (!ws) {
ws = TAILQ_FIRST(&(output_get_content(con_get_output(con))->focus_head));
if (!ws)
goto done;
}
if (ws != focused_workspace)
workspace_show(ws);
focused_id = XCB_NONE;
/* dont handle dockarea cons, they must not be focused */
if (con->parent->type == CT_DOCKAREA)
goto done;
@ -207,21 +223,13 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
goto done;
}
/* 2: focus this con. If the workspace is on another output we need to
* do a workspace_show in order for i3bar (and others) to notice the
* change in workspace. */
Con *ws = con_get_workspace(con);
Con *focused_workspace = con_get_workspace(focused);
if (ws != focused_workspace)
workspace_show(ws);
focused_id = XCB_NONE;
/* 2: focus this con. */
con_focus(con);
/* 3: For floating containers, we also want to raise them on click.
* We will skip handling events on floating cons in fullscreen mode */
Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL);
if (floatingcon != NULL && fs == NULL) {
if (floatingcon != NULL && fs != con) {
floating_raise_con(floatingcon);
/* 4: floating_modifier plus left mouse button drags */
@ -309,6 +317,25 @@ int handle_button_press(xcb_button_press_event_t *event) {
return route_click(con, event, mod_pressed, CLICK_INSIDE);
if (!(con = con_by_frame_id(event->event))) {
/* If the root window is clicked, find the relevant output from the
* click coordinates and focus the output's active workspace. */
if (event->event == root) {
Con *output, *ws;
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
if (con_is_internal(output) ||
!rect_contains(output->rect, event->event_x, event->event_y))
continue;
ws = TAILQ_FIRST(&(output_get_content(output)->focus_head));
if (ws != con_get_workspace(focused)) {
workspace_show(ws);
tree_render();
}
return 1;
}
return 0;
}
ELOG("Clicked into unknown window?!\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);

View File

@ -38,7 +38,6 @@
} \
} while (0)
static owindows_head owindows;
/*
* Returns true if a is definitely greater than b (using the given epsilon)
@ -57,19 +56,19 @@ static Output *get_output_from_string(Output *current_output, const char *output
Output *output;
if (strcasecmp(output_str, "left") == 0) {
output = get_output_next(D_LEFT, current_output);
output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
if (!output)
output = get_output_most(D_RIGHT, current_output);
} else if (strcasecmp(output_str, "right") == 0) {
output = get_output_next(D_RIGHT, current_output);
output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
if (!output)
output = get_output_most(D_LEFT, current_output);
} else if (strcasecmp(output_str, "up") == 0) {
output = get_output_next(D_UP, current_output);
output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
if (!output)
output = get_output_most(D_DOWN, current_output);
} else if (strcasecmp(output_str, "down") == 0) {
output = get_output_next(D_DOWN, current_output);
output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
if (!output)
output = get_output_most(D_UP, current_output);
} else output = get_output_by_name(output_str);
@ -99,6 +98,29 @@ static bool maybe_back_and_forth(struct CommandResult *cmd_output, char *name) {
return true;
}
/*
* Return the passed workspace unless it is the current one and auto back and
* forth is enabled, in which case the back_and_forth workspace is returned.
*/
static Con *maybe_auto_back_and_forth_workspace(Con *workspace) {
Con *current, *baf;
if (!config.workspace_auto_back_and_forth)
return workspace;
current = con_get_workspace(focused);
if (current == workspace) {
baf = workspace_back_and_forth_get();
if (baf != NULL) {
DLOG("Substituting workspace with back_and_forth, as it is focused.\n");
return baf;
}
}
return workspace;
}
// This code is commented out because we might recycle it for popping up error
// messages on parser errors.
#if 0
@ -199,6 +221,20 @@ void cmd_MIGRATION_start_nagbar(void) {
* Criteria functions.
******************************************************************************/
/*
* Helper data structure for an operation window (window on which the operation
* will be performed). Used to build the TAILQ owindows.
*
*/
typedef struct owindow {
Con *con;
TAILQ_ENTRY(owindow) owindows;
} owindow;
typedef TAILQ_HEAD(owindows_head, owindow) owindows_head;
static owindows_head owindows;
/*
* Initializes the specified 'Match' data structure and the initial state of
* commands.c for matching target windows of a command.
@ -365,7 +401,8 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) {
* when criteria was specified but didn't match any window or
* when criteria wasn't specified and we don't have any window focused. */
if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
(match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
(match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
!con_has_children(focused))) {
ysuccess(false);
return;
}
@ -400,6 +437,38 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) {
ysuccess(true);
}
/**
* Implementation of 'move [window|container] [to] workspace back_and_forth'.
*
*/
void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
owindow *current;
Con *ws;
ws = workspace_back_and_forth_get();
if (ws == NULL) {
y(map_open);
ystr("success");
y(bool, false);
ystr("error");
ystr("No workspace was previously active.");
y(map_close);
return;
}
HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name);
con_move_to_workspace(current->con, ws, true, false);
}
cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply
ysuccess(true);
}
/*
* Implementation of 'move [window|container] [to] workspace <name>'.
*
@ -421,9 +490,8 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
ysuccess(false);
return;
}
if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) {
ELOG("No window to move, you have focused a workspace.\n");
else if (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
!con_has_children(focused)) {
ysuccess(false);
return;
}
@ -432,6 +500,8 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
/* get the workspace */
Con *ws = workspace_get(name, NULL);
ws = maybe_auto_back_and_forth_workspace(ws);
HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) {
@ -445,7 +515,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
}
/*
* Implementation of 'move [window|container] [to] workspace number <number>'.
* Implementation of 'move [window|container] [to] workspace number <name>'.
*
*/
void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
@ -455,7 +525,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
* when criteria was specified but didn't match any window or
* when criteria wasn't specified and we don't have any window focused. */
if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
(match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
(match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
!con_has_children(focused))) {
ysuccess(false);
return;
}
@ -469,8 +540,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
if (parsed_num == LONG_MIN ||
parsed_num == LONG_MAX ||
parsed_num < 0 ||
*endptr != '\0') {
LOG("Could not parse \"%s\" as a number.\n", which);
endptr == which) {
LOG("Could not parse initial part of \"%s\" as a number.\n", which);
y(map_open);
ystr("success");
y(bool, false);
@ -489,6 +560,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
workspace = workspace_get(which, NULL);
}
workspace = maybe_auto_back_and_forth_workspace(workspace);
HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) {
@ -503,23 +576,35 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floating_con, int px) {
LOG("floating resize\n");
Rect old_rect = floating_con->rect;
if (strcmp(direction, "up") == 0) {
floating_con->rect.y -= px;
floating_con->rect.height += px;
} else if (strcmp(direction, "down") == 0 || strcmp(direction, "height") == 0) {
floating_con->rect.height += px;
} else if (strcmp(direction, "left") == 0) {
floating_con->rect.x -= px;
floating_con->rect.width += px;
} else {
floating_con->rect.width += px;
}
floating_check_size(floating_con);
/* Did we actually resize anything or did the size constraints prevent us?
* If we could not resize, exit now to not move the window. */
if (memcmp(&old_rect, &(floating_con->rect), sizeof(Rect)) == 0)
return;
if (strcmp(direction, "up") == 0) {
floating_con->rect.y -= px;
} else if (strcmp(direction, "left") == 0) {
floating_con->rect.x -= px;
}
}
static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int ppt) {
static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) {
LOG("tiling resize\n");
/* get the appropriate current container (skip stacked/tabbed cons) */
Con *current = focused;
Con *other = NULL;
double percentage = 0;
while (current->parent->layout == L_STACKED ||
@ -599,10 +684,9 @@ static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int
return true;
}
static bool cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, int ppt) {
static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, char *way, char *direction, int ppt) {
LOG("width/height resize\n");
/* get the appropriate current container (skip stacked/tabbed cons) */
Con *current = focused;
while (current->parent->layout == L_STACKED ||
current->parent->layout == L_TABBED)
current = current->parent;
@ -697,17 +781,22 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz
ppt *= -1;
}
Con *floating_con;
if ((floating_con = con_inside_floating(focused))) {
cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, px);
} else {
if (strcmp(direction, "width") == 0 ||
strcmp(direction, "height") == 0) {
if (!cmd_resize_tiling_width_height(current_match, cmd_output, way, direction, ppt))
return;
HANDLE_EMPTY_MATCH;
owindow *current;
TAILQ_FOREACH(current, &owindows, owindows) {
Con *floating_con;
if ((floating_con = con_inside_floating(current->con))) {
cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, px);
} else {
if (!cmd_resize_tiling_direction(current_match, cmd_output, way, direction, ppt))
return;
if (strcmp(direction, "width") == 0 ||
strcmp(direction, "height") == 0) {
if (!cmd_resize_tiling_width_height(current_match, cmd_output, current->con, way, direction, ppt))
return;
} else {
if (!cmd_resize_tiling_direction(current_match, cmd_output, current->con, way, direction, ppt))
return;
}
}
}
@ -717,11 +806,11 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz
}
/*
* Implementation of 'border normal|none|1pixel|toggle'.
* Implementation of 'border normal|none|1pixel|toggle|pixel'.
*
*/
void cmd_border(I3_CMD, char *border_style_str) {
DLOG("border style should be changed to %s\n", border_style_str);
void cmd_border(I3_CMD, char *border_style_str, char *border_width ) {
DLOG("border style should be changed to %s with border width %s\n", border_style_str, border_width);
owindow *current;
HANDLE_EMPTY_MATCH;
@ -729,23 +818,39 @@ void cmd_border(I3_CMD, char *border_style_str) {
TAILQ_FOREACH(current, &owindows, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name);
int border_style = current->con->border_style;
char *end;
int tmp_border_width = -1;
tmp_border_width = strtol(border_width, &end, 10);
if (end == border_width) {
/* no valid digits found */
tmp_border_width = -1;
}
if (strcmp(border_style_str, "toggle") == 0) {
border_style++;
border_style %= 3;
if (border_style == BS_NORMAL)
tmp_border_width = 2;
else if (border_style == BS_NONE)
tmp_border_width = 0;
else if (border_style == BS_PIXEL)
tmp_border_width = 1;
} else {
if (strcmp(border_style_str, "normal") == 0)
border_style = BS_NORMAL;
else if (strcmp(border_style_str, "none") == 0)
else if (strcmp(border_style_str, "pixel") == 0)
border_style = BS_PIXEL;
else if (strcmp(border_style_str, "1pixel") == 0){
border_style = BS_PIXEL;
tmp_border_width = 1;
} else if (strcmp(border_style_str, "none") == 0)
border_style = BS_NONE;
else if (strcmp(border_style_str, "1pixel") == 0)
border_style = BS_1PIXEL;
else {
ELOG("BUG: called with border_style=%s\n", border_style_str);
ysuccess(false);
return;
}
}
con_set_border_style(current->con, border_style);
con_set_border_style(current->con, border_style, tmp_border_width);
}
cmd_output->needs_tree_render = true;
@ -807,7 +912,7 @@ void cmd_workspace(I3_CMD, char *which) {
}
/*
* Implementation of 'workspace number <number>'
* Implementation of 'workspace number <name>'
*
*/
void cmd_workspace_number(I3_CMD, char *which) {
@ -818,8 +923,8 @@ void cmd_workspace_number(I3_CMD, char *which) {
if (parsed_num == LONG_MIN ||
parsed_num == LONG_MAX ||
parsed_num < 0 ||
*endptr != '\0') {
LOG("Could not parse \"%s\" as a number.\n", which);
endptr == which) {
LOG("Could not parse initial part of \"%s\" as a number.\n", which);
y(map_open);
ystr("success");
y(bool, false);
@ -838,8 +943,6 @@ void cmd_workspace_number(I3_CMD, char *which) {
if (!workspace) {
LOG("There is no workspace with number %ld, creating a new one.\n", parsed_num);
ysuccess(true);
/* terminate the which string after the endposition of the number */
*endptr = '\0';
workspace_show_by_name(which);
cmd_output->needs_tree_render = true;
return;
@ -949,13 +1052,13 @@ void cmd_move_con_to_output(I3_CMD, char *name) {
// TODO: clean this up with commands.spec as soon as we switched away from the lex/yacc command parser
if (strcasecmp(name, "up") == 0)
output = get_output_next(D_UP, current_output);
output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
else if (strcasecmp(name, "down") == 0)
output = get_output_next(D_DOWN, current_output);
output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
else if (strcasecmp(name, "left") == 0)
output = get_output_next(D_LEFT, current_output);
output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
else if (strcasecmp(name, "right") == 0)
output = get_output_next(D_RIGHT, current_output);
output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
else
output = get_output_by_name(name);
@ -1042,8 +1145,12 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
Con *content = output_get_content(output->con);
LOG("got output %p with content %p\n", output, content);
Con *previously_visible_ws = TAILQ_FIRST(&(content->nodes_head));
LOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name);
Con *ws = con_get_workspace(current->con);
LOG("should move workspace %p / %s\n", ws, ws->name);
bool workspace_was_visible = workspace_is_visible(ws);
if (con_num_children(ws->parent) == 1) {
LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name);
@ -1078,9 +1185,9 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
/* notify the IPC listeners */
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
}
DLOG("Detaching\n");
/* detach from the old output and attach to the new output */
bool workspace_was_visible = workspace_is_visible(ws);
Con *old_content = ws->parent;
con_detach(ws);
if (workspace_was_visible) {
@ -1102,6 +1209,22 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
/* Focus the moved workspace on the destination output. */
workspace_show(ws);
}
/* NB: We cannot simply work with previously_visible_ws since it might
* have been cleaned up by workspace_show() already, depending on the
* focus order/number of other workspaces on the output.
* Instead, we loop through the available workspaces and only work with
* previously_visible_ws if we still find it. */
TAILQ_FOREACH(ws, &(content->nodes_head), nodes) {
if (ws != previously_visible_ws)
continue;
/* Call the on_remove_child callback of the workspace which previously
* was visible on the destination output. Since it is no longer
* visible, it might need to get cleaned up. */
CALL(previously_visible_ws, on_remove_child);
break;
}
}
cmd_output->needs_tree_render = true;
@ -1132,7 +1255,7 @@ void cmd_split(I3_CMD, char *direction) {
}
/*
* Implementaiton of 'kill [window|client]'.
* Implementation of 'kill [window|client]'.
*
*/
void cmd_kill(I3_CMD, char *kill_mode_str) {
@ -1187,14 +1310,6 @@ void cmd_exec(I3_CMD, char *nosn, char *command) {
*
*/
void cmd_focus_direction(I3_CMD, char *direction) {
if (focused &&
focused->type != CT_WORKSPACE &&
focused->fullscreen_mode != CF_NONE) {
LOG("Cannot change focus while in fullscreen mode.\n");
ysuccess(false);
return;
}
DLOG("direction = *%s*\n", direction);
if (strcmp(direction, "left") == 0)
@ -1221,14 +1336,6 @@ void cmd_focus_direction(I3_CMD, char *direction) {
*
*/
void cmd_focus_window_mode(I3_CMD, char *window_mode) {
if (focused &&
focused->type != CT_WORKSPACE &&
focused->fullscreen_mode != CF_NONE) {
LOG("Cannot change focus while in fullscreen mode.\n");
ysuccess(false);
return;
}
DLOG("window_mode = %s\n", window_mode);
Con *ws = con_get_workspace(focused);
@ -1478,7 +1585,7 @@ void cmd_layout_toggle(I3_CMD, char *toggle_mode) {
}
/*
* Implementaiton of 'exit'.
* Implementation of 'exit'.
*
*/
void cmd_exit(I3_CMD) {
@ -1490,7 +1597,7 @@ void cmd_exit(I3_CMD) {
}
/*
* Implementaiton of 'reload'.
* Implementation of 'reload'.
*
*/
void cmd_reload(I3_CMD) {
@ -1507,7 +1614,7 @@ void cmd_reload(I3_CMD) {
}
/*
* Implementaiton of 'restart'.
* Implementation of 'restart'.
*
*/
void cmd_restart(I3_CMD) {
@ -1519,7 +1626,7 @@ void cmd_restart(I3_CMD) {
}
/*
* Implementaiton of 'open'.
* Implementation of 'open'.
*
*/
void cmd_open(I3_CMD) {
@ -1708,16 +1815,24 @@ void cmd_scratchpad_show(I3_CMD) {
}
/*
* Implementation of 'rename workspace <name> to <name>'
* Implementation of 'rename workspace [<name>] to <name>'
*
*/
void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
LOG("Renaming workspace \"%s\" to \"%s\"\n", old_name, new_name);
if (old_name) {
LOG("Renaming workspace \"%s\" to \"%s\"\n", old_name, new_name);
} else {
LOG("Renaming current workspace to \"%s\"\n", new_name);
}
Con *output, *workspace = NULL;
TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
GREP_FIRST(workspace, output_get_content(output),
!strcasecmp(child->name, old_name));
if (old_name) {
TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
GREP_FIRST(workspace, output_get_content(output),
!strcasecmp(child->name, old_name));
} else {
workspace = con_get_workspace(focused);
}
if (!workspace) {
// TODO: we should include the old workspace name here and use yajl for

View File

@ -46,7 +46,7 @@
* input parser-specs/commands.spec.
******************************************************************************/
#include "GENERATED_enums.h"
#include "GENERATED_command_enums.h"
typedef struct token {
char *name;
@ -63,7 +63,7 @@ typedef struct tokenptr {
int n;
} cmdp_token_ptr;
#include "GENERATED_tokens.h"
#include "GENERATED_command_tokens.h"
/*******************************************************************************
* The (small) stack where identified literals are stored during the parsing
@ -182,7 +182,7 @@ static Match current_match;
static struct CommandResult subcommand_output;
static struct CommandResult command_output;
#include "GENERATED_call.h"
#include "GENERATED_command_call.h"
static void next_state(const cmdp_token *token) {
@ -190,6 +190,7 @@ static void next_state(const cmdp_token *token) {
subcommand_output.json_gen = command_output.json_gen;
subcommand_output.needs_tree_render = false;
GENERATED_call(token->extra.call_identifier, &subcommand_output);
state = subcommand_output.next_state;
/* If any subcommand requires a tree_render(), we need to make the
* whole parser result request a tree_render(). */
if (subcommand_output.needs_tree_render)

335
src/con.c
View File

@ -28,6 +28,20 @@ char *colors[] = {
static void con_on_remove_child(Con *con);
/*
* force parent split containers to be redrawn
*
*/
static void con_force_split_parents_redraw(Con *con) {
Con *parent = con;
while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) {
if (!con_is_leaf(parent))
FREE(parent->deco_render_params);
parent = parent->parent;
}
}
/*
* Create a new container (and attach it to the given parent, if not NULL).
* This function initializes the data structures and creates the appropriate
@ -41,6 +55,7 @@ Con *con_new(Con *parent, i3Window *window) {
new->type = CT_CON;
new->window = window;
new->border_style = config.default_border;
new->current_border_width = -1;
static int cnt = 0;
DLOG("opening window %d\n", cnt);
@ -162,6 +177,7 @@ add_to_focus_head:
* This way, we have the option to insert Cons without having
* to focus them. */
TAILQ_INSERT_TAIL(focus_head, con, focused);
con_force_split_parents_redraw(con);
}
/*
@ -169,6 +185,7 @@ add_to_focus_head:
*
*/
void con_detach(Con *con) {
con_force_split_parents_redraw(con);
if (con->type == CT_FLOATING_CON) {
TAILQ_REMOVE(&(con->parent->floating_head), con, floating_windows);
TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
@ -195,8 +212,14 @@ void con_focus(Con *con) {
con_focus(con->parent);
focused = con;
if (con->urgent) {
/* We can't blindly reset non-leaf containers since they might have
* other urgent children. Therefore we only reset leafs and propagate
* the changes upwards via con_update_parents_urgency() which does proper
* checks before resetting the urgency.
*/
if (con->urgent && con_is_leaf(con)) {
con->urgent = false;
con_update_parents_urgency(con);
workspace_update_urgent_flag(con_get_workspace(con));
}
}
@ -209,6 +232,32 @@ bool con_is_leaf(Con *con) {
return TAILQ_EMPTY(&(con->nodes_head));
}
/**
* Returns true if this node has regular or floating children.
*
*/
bool con_has_children(Con *con) {
return (!con_is_leaf(con) || !TAILQ_EMPTY(&(con->floating_head)));
}
/*
* Returns true if a container should be considered split.
*
*/
bool con_is_split(Con *con) {
if (con_is_leaf(con))
return false;
switch (con->layout) {
case L_DOCKAREA:
case L_OUTPUT:
return false;
default:
return true;
}
}
/*
* Returns true if this node accepts a window (if the node swallows windows,
* it might already have swallowed enough and cannot hold any more).
@ -219,7 +268,7 @@ bool con_accepts_window(Con *con) {
if (con->type == CT_WORKSPACE)
return false;
if (con->split) {
if (con_is_split(con)) {
DLOG("container %p does not accept windows, it is a split container.\n", con);
return false;
}
@ -337,6 +386,14 @@ Con *con_get_fullscreen_con(Con *con, int fullscreen_mode) {
return NULL;
}
/**
* Returns true if the container is internal, such as __i3_scratch
*
*/
bool con_is_internal(Con *con) {
return (con->name[0] == '_' && con->name[1] == '_');
}
/*
* Returns true if the node is floating.
*
@ -576,11 +633,6 @@ void con_toggle_fullscreen(Con *con, int fullscreen_mode) {
*
*/
void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp) {
if (con->type == CT_WORKSPACE) {
DLOG("Moving workspaces is not yet implemented.\n");
return;
}
/* Prevent moving if this would violate the fullscreen focus restrictions. */
if (!con_fullscreen_permits_focusing(workspace)) {
LOG("Cannot move out of a fullscreen container");
@ -598,6 +650,25 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
return;
}
if (con->type == CT_WORKSPACE) {
/* Re-parent all of the old workspace's floating windows. */
Con *child;
while (!TAILQ_EMPTY(&(source_ws->floating_head))) {
child = TAILQ_FIRST(&(source_ws->floating_head));
con_move_to_workspace(child, workspace, true, true);
}
/* If there are no non-floating children, ignore the workspace. */
if (con_is_leaf(con))
return;
con = workspace_encapsulate(con);
if (con == NULL) {
ELOG("Workspace failed to move its contents into a container!\n");
return;
}
}
/* Save the current workspace. So we can call workspace_show() by the end
* of this function. */
Con *current_ws = con_get_workspace(focused);
@ -655,6 +726,14 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
}
}
/* If moving a fullscreen container and the destination already has a
* fullscreen window on it, un-fullscreen the target's fullscreen con. */
Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
if (con->fullscreen_mode != CF_NONE && fullscreen != NULL) {
con_toggle_fullscreen(fullscreen, CF_OUTPUT);
fullscreen = NULL;
}
DLOG("Re-attaching container to %p / %s\n", next, next->name);
/* 5: re-attach the con to the parent of this focused container */
Con *parent = con->parent;
@ -671,14 +750,16 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
* invisible.
* We dont focus the con for i3 pseudo workspaces like __i3_scratch and
* we dont focus when there is a fullscreen con on that workspace. */
if ((workspace->name[0] != '_' || workspace->name[1] != '_') &&
con_get_fullscreen_con(workspace, CF_OUTPUT) == NULL) {
/* We need to save focus on workspace level and restore it afterwards.
* Otherwise, we might focus a different workspace without actually
* switching workspaces. */
if (!con_is_internal(workspace) && !fullscreen) {
/* We need to save the focused workspace on the output in case the
* new workspace is hidden and it's necessary to immediately switch
* back to the originally-focused workspace. */
Con *old_focus = TAILQ_FIRST(&(output_get_content(dest_output)->focus_head));
con_focus(con_descend_focused(con));
con_focus(old_focus);
/* Restore focus if the output's focused workspace has changed. */
if (con_get_workspace(focused) != old_focus)
con_focus(old_focus);
}
/* 8: when moving to a visible workspace on a different output, we keep the
@ -686,8 +767,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
* dont want to focus invisible workspaces */
if (source_output != dest_output &&
workspace_is_visible(workspace) &&
workspace->name[0] != '_' &&
workspace->name[1] != '_') {
!con_is_internal(workspace)) {
DLOG("Moved to a different output, focusing target\n");
} else {
/* Descend focus stack in case focus_next is a workspace which can
@ -701,6 +781,38 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
con_focus(con_descend_focused(focus_next));
}
/* If anything within the container is associated with a startup sequence,
* delete it so child windows won't be created on the old workspace. */
struct Startup_Sequence *sequence;
xcb_get_property_cookie_t cookie;
xcb_get_property_reply_t *startup_id_reply;
if (!con_is_leaf(con)) {
Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
if (!child->window)
continue;
cookie = xcb_get_property(conn, false, child->window->id,
A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
sequence = startup_sequence_get(child->window, startup_id_reply, true);
if (sequence != NULL)
startup_sequence_delete(sequence);
}
}
if (con->window) {
cookie = xcb_get_property(conn, false, con->window->id,
A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
sequence = startup_sequence_get(con->window, startup_id_reply, true);
if (sequence != NULL)
startup_sequence_delete(sequence);
}
CALL(parent, on_remove_child);
}
@ -893,6 +1005,7 @@ Con *con_descend_tiling_focused(Con *con) {
*/
Con *con_descend_direction(Con *con, direction_t direction) {
Con *most = NULL;
Con *current;
int orientation = con_orientation(con);
DLOG("con_descend_direction(%p, orientation %d, direction %d)\n", con, orientation, direction);
if (direction == D_LEFT || direction == D_RIGHT) {
@ -906,7 +1019,12 @@ Con *con_descend_direction(Con *con, direction_t direction) {
/* Wrong orientation. We use the last focused con. Within that con,
* we recurse to chose the left/right con or at least the last
* focused one. */
most = TAILQ_FIRST(&(con->focus_head));
TAILQ_FOREACH(current, &(con->focus_head), focused) {
if (current->type != CT_FLOATING_CON) {
most = current;
break;
}
}
} else {
/* If the con has no orientation set, its not a split container
* but a container with a client window, so stop recursing */
@ -925,7 +1043,12 @@ Con *con_descend_direction(Con *con, direction_t direction) {
/* Wrong orientation. We use the last focused con. Within that con,
* we recurse to chose the top/bottom con or at least the last
* focused one. */
most = TAILQ_FIRST(&(con->focus_head));
TAILQ_FOREACH(current, &(con->focus_head), focused) {
if (current->type != CT_FLOATING_CON) {
most = current;
break;
}
}
} else {
/* If the con has no orientation set, its not a split container
* but a container with a client window, so stop recursing */
@ -946,52 +1069,38 @@ Con *con_descend_direction(Con *con, direction_t direction) {
*/
Rect con_border_style_rect(Con *con) {
adjacent_t borders_to_hide = ADJ_NONE;
int border_width = con->current_border_width;
DLOG("The border width for con is set to: %d\n", con->current_border_width);
Rect result;
if (con->current_border_width < 0)
border_width = config.default_border_width;
DLOG("Effective border width is set to: %d\n", border_width);
/* Shortcut to avoid calling con_adjacent_borders() on dock containers. */
int border_style = con_border_style(con);
if (border_style == BS_NONE)
return (Rect){ 0, 0, 0, 0 };
borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
switch (border_style) {
case BS_NORMAL:
result = (Rect){2, 0, -(2 * 2), -2};
if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
result.x -= 2;
result.width += 2;
}
if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) {
result.width += 2;
}
/* With normal borders we never hide the upper border */
if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) {
result.height += 2;
}
return result;
case BS_1PIXEL:
result = (Rect){1, 1, -2, -2};
if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
result.x -= 1;
result.width += 1;
}
if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) {
result.width += 1;
}
if (borders_to_hide & ADJ_UPPER_SCREEN_EDGE) {
result.y -= 1;
result.height += 1;
}
if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) {
result.height += 1;
}
return result;
case BS_NONE:
return (Rect){0, 0, 0, 0};
default:
assert(false);
if (border_style == BS_NORMAL) {
result = (Rect){border_width, 0 , -(2 * border_width), -(border_width)};
} else {
result = (Rect){border_width, border_width, -(2 * border_width), -(2 * border_width)};
}
if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
result.x -= border_width;
result.width += border_width;
}
if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) {
result.width += border_width;
}
if (borders_to_hide & ADJ_UPPER_SCREEN_EDGE && (border_style != BS_NORMAL)) {
result.y -= border_width;
result.height += border_width;
}
if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) {
result.height += border_width;
}
return result;
}
/*
@ -1046,10 +1155,11 @@ int con_border_style(Con *con) {
* floating window.
*
*/
void con_set_border_style(Con *con, int border_style) {
void con_set_border_style(Con *con, int border_style, int border_width) {
/* Handle the simple case: non-floating containerns */
if (!con_is_floating(con)) {
con->border_style = border_style;
con->current_border_width = border_width;
return;
}
@ -1068,6 +1178,7 @@ void con_set_border_style(Con *con, int border_style) {
/* Change the border style, get new border/decoration values. */
con->border_style = border_style;
con->current_border_width = border_width;
bsr = con_border_style_rect(con);
int deco_height =
(con->border_style == BS_NORMAL ? config.font.height + 5 : 0);
@ -1125,7 +1236,6 @@ void con_set_layout(Con *con, int layout) {
* split. */
new->layout = layout;
new->last_split_layout = con->last_split_layout;
new->split = true;
Con *old_focused = TAILQ_FIRST(&(con->focus_head));
if (old_focused == TAILQ_END(&(con->focus_head)))
@ -1149,6 +1259,7 @@ void con_set_layout(Con *con, int layout) {
tree_flatten(croot);
}
con_force_split_parents_redraw(con);
return;
}
@ -1169,6 +1280,7 @@ void con_set_layout(Con *con, int layout) {
} else {
con->layout = layout;
}
con_force_split_parents_redraw(con);
}
/*
@ -1248,6 +1360,8 @@ static void con_on_remove_child(Con *con) {
return;
}
con_force_split_parents_redraw(con);
/* TODO: check if this container would swallow any other client and
* dont close it automatically. */
int children = con_num_children(con);
@ -1294,7 +1408,7 @@ Rect con_minimum_size(Con *con) {
/* For horizontal/vertical split containers we sum up the width (h-split)
* or height (v-split) and use the maximum of the height (h-split) or width
* (v-split) as minimum size. */
if (con->split) {
if (con_is_split(con)) {
uint32_t width = 0, height = 0;
Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
@ -1312,7 +1426,7 @@ Rect con_minimum_size(Con *con) {
}
ELOG("Unhandled case, type = %d, layout = %d, split = %d\n",
con->type, con->layout, con->split);
con->type, con->layout, con_is_split(con));
assert(false);
}
@ -1378,3 +1492,104 @@ bool con_fullscreen_permits_focusing(Con *con) {
/* Focusing con would hide it behind a fullscreen window, disallow it. */
return false;
}
/*
*
* Checks if the given container has an urgent child.
*
*/
bool con_has_urgent_child(Con *con) {
Con *child;
if (con_is_leaf(con))
return con->urgent;
/* We are not interested in floating windows since they can only be
* attached to a workspace nodes_head instead of focus_head */
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
if (con_has_urgent_child(child))
return true;
}
return false;
}
/*
* Make all parent containers urgent if con is urgent or clear the urgent flag
* of all parent containers if there are no more urgent children left.
*
*/
void con_update_parents_urgency(Con *con) {
Con *parent = con->parent;
bool new_urgency_value = con->urgent;
while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) {
if (new_urgency_value) {
parent->urgent = true;
} else {
/* We can only reset the urgency when the parent
* has no other urgent children */
if (!con_has_urgent_child(parent))
parent->urgent = false;
}
parent = parent->parent;
}
}
/*
* Create a string representing the subtree under con.
*
*/
char *con_get_tree_representation(Con *con) {
/* this code works as follows:
* 1) create a string with the layout type (D/V/H/T/S) and an opening bracket
* 2) append the tree representation of the children to the string
* 3) add closing bracket
*
* The recursion ends when we hit a leaf, in which case we return the
* class_instance of the contained window.
*/
/* end of recursion */
if (con_is_leaf(con)) {
if (!con->window)
return sstrdup("nowin");
if (!con->window->class_instance)
return sstrdup("noinstance");
return sstrdup(con->window->class_instance);
}
char *buf;
/* 1) add the Layout type to buf */
if (con->layout == L_DEFAULT)
buf = sstrdup("D[");
else if (con->layout == L_SPLITV)
buf = sstrdup("V[");
else if (con->layout == L_SPLITH)
buf = sstrdup("H[");
else if (con->layout == L_TABBED)
buf = sstrdup("T[");
else if (con->layout == L_STACKED)
buf = sstrdup("S[");
/* 2) append representation of children */
Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
char *child_txt = con_get_tree_representation(child);
char *tmp_buf;
sasprintf(&tmp_buf, "%s%s%s", buf,
(TAILQ_FIRST(&(con->nodes_head)) == child ? "" : " "), child_txt);
free(buf);
buf = tmp_buf;
}
/* 3) close the brackets */
char *complete_buf;
sasprintf(&complete_buf, "%s]", buf);
free(buf);
return complete_buf;
}

View File

@ -46,6 +46,7 @@ static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint
}
GRAB_KEY(mods);
GRAB_KEY(mods | xcb_numlock_mask);
GRAB_KEY(mods | XCB_MOD_MASK_LOCK);
GRAB_KEY(mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
}
@ -194,6 +195,13 @@ void switch_mode(const char *new_mode) {
bindings = mode->bindings;
translate_keysyms();
grab_all_keys(conn, false);
char *event_msg;
sasprintf(&event_msg, "{\"change\":\"%s\"}", mode->name);
ipc_send_event("mode", I3_IPC_EVENT_MODE, event_msg);
FREE(event_msg);
return;
}
@ -410,9 +418,14 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
config.default_border = BS_NORMAL;
config.default_floating_border = BS_NORMAL;
config.default_border_width = 2;
/* Set default_orientation to NO_ORIENTATION for auto orientation. */
config.default_orientation = NO_ORIENTATION;
/* Set default urgency reset delay to 500ms */
if (config.workspace_urgency_timer == 0)
config.workspace_urgency_timer = 0.5;
parse_configuration(override_configpath);
if (reload) {

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