]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'next' (3.β is stable now)
authorMichael Stapelberg <michael@stapelberg.de>
Fri, 26 Jun 2009 11:27:06 +0000 (13:27 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Fri, 26 Jun 2009 11:27:06 +0000 (13:27 +0200)
43 files changed:
CMDMODE
DEPENDS
Makefile
RELEASE-NOTES-3.b [new file with mode: 0644]
debian/changelog
debian/control
debian/i3-wm.docs [new file with mode: 0644]
debian/rules
docs/Makefile
docs/debugging
docs/hacking-howto
docs/single_terminal.png [new file with mode: 0644]
docs/snapping.png [new file with mode: 0644]
docs/two_columns.png [new file with mode: 0644]
docs/two_terminals.png [new file with mode: 0644]
docs/userguide [new file with mode: 0644]
i3.config
include/client.h [new file with mode: 0644]
include/config.h
include/data.h
include/floating.h [new file with mode: 0644]
include/handlers.h
include/i3.h
include/layout.h
include/manage.h [new file with mode: 0644]
include/util.h
include/xcb.h
man/asciidoc.conf
man/i3.man
src/client.c [new file with mode: 0644]
src/commands.c
src/config.c
src/floating.c [new file with mode: 0644]
src/handlers.c
src/layout.c
src/mainx.c
src/manage.c [new file with mode: 0644]
src/resize.c
src/table.c
src/util.c
src/xcb.c
src/xinerama.c
website/screenshots/index.html

diff --git a/CMDMODE b/CMDMODE
index 6b8315e2fa027e30f5e62ee2464ff54af026f51f..7d8f6f23a1426f2803d5e03e8d977ec2751dcb02 100644 (file)
--- a/CMDMODE
+++ b/CMDMODE
@@ -1,36 +1,44 @@
-left := <h> | <cursor-left>
-right := <l> | <cursor-right>
-up := <j> | <cursor-up>
-down := <k> | <cursor-down>
-
-where := <left|right|up|down> | <tag>
-move := <m>
-snap := <s>
-
-Eingabe ist entweder
-
-cmd := [ <times> ] [ <move> | <snap> ] <where>
+---------------------
+- Command mode
+---------------------
 
-oder
+This is the grammar for the command mode (your configuration file uses these commands, too).
 
-with := <w> { [ <times> ] <where> }+ <space> <cmd>
-
-oder
+left  := <h> | <cursor-left>
+right := <l> | <cursor-right>
+up    := <j> | <cursor-up>
+down  := <k> | <cursor-down>
 
+where := <left|right|up|down> | <tag>
+move  := <m>
+snap  := <s>
+
+cmd     := [ <times> ] [ <move> | <snap> ] <where>
+with    := <w> { [ <times> ] <where> }+ <space> <cmd>
+jump    := [ "<window class>[/<window title>]" | <workspace> [ <column> <row> ] ]
+focus   := focus [ <times> | floating | tiling | ft ]
+(travels the focus stack backwards the given amount of times (by default 1), so
+ it selects the window which had the focus before you focused the current one when
+ specifying "focus 1".
+ The special values 'floating' (select the next floating window), 'tiling'
+ (select the next tiling window), 'ft' (if the current window is floating,
+ select the next tiling window and vice-versa) are also valid)
 special := [ exec <path> | kill | exit | restart ]
 
-an jeder Stelle kann mit escape abgebrochen werden
+input := [ <cmd> | <with> | <jump> | <focus> | <special> ]
+
+you can cancel command mode by pressing escape anytime.
 
-Beispiele:
+Some examples:
 
-Fenster links neben dem aktuellen auswählen:
+Select the window on the left:
 h
 
-Fenster zwei links neben dem aktuellen auswählen:
+Select the window two places on the left:
 2h
 
-Fenster nach rechts verschieben:
+Move window to the right:
 ml
 
-Fenster und Fenster untendrunter nach rechts verschieben:
+Move window and window on the bottom to the right:
 wk ml
diff --git a/DEPENDS b/DEPENDS
index 6d56e1326317e5a39ace5e79a60768a56a6f0387..0e1de719fe3c116f71af67faf37501c232b9cedd 100644 (file)
--- a/DEPENDS
+++ b/DEPENDS
@@ -5,6 +5,7 @@ In that case, please try using the versions mentioned below until a fix is provi
  * xcb-proto-1.3 (2008-12-10)
  * libxcb-1.1.93 (2008-12-11)
  * xcb-util-0.3.3 (2009-01-31)
+ * libev
  * asciidoc >= 8.3.0 for docs/hacking-howto
  * asciidoc, xmlto, docbook-xml for man/i3.man
  * Xlib, the one that comes with your X-Server
@@ -14,9 +15,10 @@ Recommendations:
  * dmenu for launching applications
 
 Get the libraries from:
-http://xcb.freedesktop.org/dist/xcb-proto-1.3.tar.bz2
+http://xcb.freedesktop.org/dist/xcb-proto-1.5.tar.bz2
 http://xcb.freedesktop.org/dist/libxcb-1.1.93.tar.bz2
-http://xcb.freedesktop.org/dist/xcb-util-0.3.3.tar.bz2
+http://xcb.freedesktop.org/dist/xcb-util-0.3.5.tar.bz2
+http://libev.schmorp.de/
 
 http://i3.zekjur.net/i3lock/
 http://tools.suckless.org/dmenu
index 9f82b1a9ffb5815d9ee6dd0756fa3985730d5b6f..a42099d47eb01a02f3ee7a250045696789488c55 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -37,6 +37,7 @@ LDFLAGS += -lxcb-aux
 LDFLAGS += -lxcb-icccm
 LDFLAGS += -lxcb-xinerama
 LDFLAGS += -lX11
+LDFLAGS += -lev
 LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib
 
 ifeq ($(UNAME),NetBSD)
diff --git a/RELEASE-NOTES-3.b b/RELEASE-NOTES-3.b
new file mode 100644 (file)
index 0000000..43220a4
--- /dev/null
@@ -0,0 +1,51 @@
+Release notes for i3 v3.β
+-----------------------------
+
+This is the second version (3.β, transcribed 3.b) of i3. It is considered stable.
+
+The most important change probably is the implementation of floating clients,
+primarily useful for dialog/toolbar/popup/splash windows. When using i3 for
+managing floating windows other than the ones mentioned beforehand, please
+keep in mind that i3 is a tiling window manager in the first place and thus
+you might better use a "traditional" window manager when having to deal a
+lot with floating windows.
+
+Now that you’re warned, let’s have a quick glance at the other new features:
+  * jumping to other windows by specifying their position or window class/title
+  * assigning clients to specific workspaces by window class/title
+  * automatically starting programs (such as i3status + dzen2)
+  * configurable colors
+  * variables in configfile
+
+Furthermore, we now have a user’s guide which should be the first document
+you read when new to i3 (apart from the manpage).
+
+Thanks for this release go out to mist, Atsutane, ch3ka, urs, Moredread,
+badboy and all other people who reported bugs/made suggestions.
+
+A list of changes follows:
+
+  * Bugfix: Correctly handle col-/rowspanned containers when setting focus.
+  * Bugfix: Correctly handle col-/rowspanned containers when snapping.
+  * Bugfix: Force reconfiguration of all windows on workspaces which are
+    re-assigned because a screen was detached.
+  * Bugfix: Several bugs in resizing table columns fixed.
+  * Bugfix: Resizing should now work correctly in all cases.
+  * Bugfix: Correctly re-assign dock windows when workspace is destroyed.
+  * Bugfix: Correctly handle Mode_switch modifier.
+  * Bugfix: Don't raise clients in fullscreen mode.
+  * Bugfix: Re-assign dock windows to different workspaces when a workspace
+    is detached.
+  * Bugfix: Fix crash because of workspace-pointer which did not get updated
+  * Bugfix: Correctly initialize screen when Xinerama is disabled.
+  * Bugfix: Fullscreen window movement and focus problems fixed
+  * Implement jumping to other windows by specifying their position or
+    window class/title.
+  * Implement jumping back by using the focus stack.
+  * Implement autostart (exec-command in configuration file).
+  * Implement floating.
+  * Implement automatically assigning clients on specific workspaces.
+  * Implement variables in configfile.
+  * Colors are now configurable.
+
+-- Michael Stapelberg, 2009-06-21
index f698b32cbb8ab45186217171c6149ba3e4ba2c61..17ece2ba5c3172f1aa1b88a89a2030646cfa332b 100644 (file)
@@ -1,3 +1,30 @@
+i3-wm (3.b-1) unstable; urgency=low
+
+  * Bugfix: Correctly handle col-/rowspanned containers when setting focus.
+  * Bugfix: Correctly handle col-/rowspanned containers when snapping.
+  * Bugfix: Force reconfiguration of all windows on workspaces which are
+    re-assigned because a screen was detached.
+  * Bugfix: Several bugs in resizing table columns fixed.
+  * Bugfix: Resizing should now work correctly in all cases.
+  * Bugfix: Correctly re-assign dock windows when workspace is destroyed.
+  * Bugfix: Correctly handle Mode_switch modifier.
+  * Bugfix: Don't raise clients in fullscreen mode.
+  * Bugfix: Re-assign dock windows to different workspaces when a workspace
+    is detached.
+  * Bugfix: Fix crash because of workspace-pointer which did not get updated
+  * Bugfix: Correctly initialize screen when Xinerama is disabled.
+  * Bugfix: Fullscreen window movement and focus problems fixed
+  * Implement jumping to other windows by specifying their position or
+    window class/title.
+  * Implement jumping back by using the focus stack.
+  * Implement autostart (exec-command in configuration file).
+  * Implement floating.
+  * Implement automatically assigning clients on specific workspaces.
+  * Implement variables in configfile.
+  * Colors are now configurable.
+
+ -- Michael Stapelberg <michael@stapelberg.de>  Fri, 26 Jun 2009 04:42:23 +0200
+
 i3-wm (3.a-bf2-1) unstable; urgency=low
 
   * Bugfix: Don't crash when setting focus
index e0807987802586ece877b2780ff1be0bc7e659eb..c8834deebe38d830adc0a84323b75fd2136aa9d2 100644 (file)
@@ -1,26 +1,28 @@
 Source: i3-wm
 Section: utils
-Priority: optional
+Priority: extra
 Maintainer: Michael Stapelberg <michael@stapelberg.de>
 DM-Upload-Allowed: yes
-Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4-1), xmlto, docbook-xml, pkg-config
-Standards-Version: 3.8.0
+Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4-1), xmlto, docbook-xml, pkg-config, libev-dev
+Standards-Version: 3.8.2
 Homepage: http://i3.zekjur.net/
 
 Package: i3
 Architecture: any
-Priority: optional
+Priority: extra
 Section: x11
 Depends: i3-wm, ${misc:Depends}
-Recommends: i3lock, dwm-tools
-Description: metapackage (i3 window manager, i3lock (screen locker), dwm-tools)
+Recommends: i3lock, dwm-tools, i3status
+Description: metapackage (i3 window manager, screen locker, menu, statusbar)
  This metapackage installs the i3 window manager (i3-wm), the i3lock screen
- locker (slightly improved version of slock) and dwm-tools which contains dmenu.
- These are all the tools you need to use the i3 window manager efficiently.
+ locker (slightly improved version of slock), dwm-tools which contains dmenu
+ and i3status, which displays useful information about your system in
+ combination with dzen2. These are all the tools you need to use the i3 window
+ manager efficiently.
 
 Package: i3-wm
 Architecture: any
-Priority: optional
+Priority: extra
 Section: x11
 Depends: ${shlibs:Depends}, ${misc:Depends}
 Provides: x-window-manager
diff --git a/debian/i3-wm.docs b/debian/i3-wm.docs
new file mode 100644 (file)
index 0000000..6372ffe
--- /dev/null
@@ -0,0 +1,8 @@
+docs/debugging.html
+docs/hacking-howto.html
+docs/userguide.html
+docs/bigpicture.png
+docs/single_terminal.png
+docs/snapping.png
+docs/two_columns.png
+docs/two_terminals.png
index 78eba656df8432ff8d9225c6632ad5522f64b25d..1fee800a3a6bed92f4d38d1ba08b1127d1d0c1d8 100755 (executable)
@@ -19,6 +19,7 @@ build:
        # Add here commands to compile the package.
        $(MAKE)
        $(MAKE) -C man
+       $(MAKE) -C docs
 
        touch $@
 
index e69aefc56ddb513a1da8bccd077754f548f13026..123f839f2a7682f05da9bf00010a170b4b3f6c62 100644 (file)
@@ -1,5 +1,5 @@
 
-all: hacking-howto.html debugging.html
+all: hacking-howto.html debugging.html userguide.html
 
 hacking-howto.html: hacking-howto
        asciidoc -a toc -n $<
@@ -7,6 +7,8 @@ hacking-howto.html: hacking-howto
 debugging.html: debugging
        asciidoc -n $<
 
+userguide.html: userguide
+       asciidoc -a toc -n $<
 
 clean:
        rm -f */*.{aux,log,toc,bm,pdf,dvi}
index d33f64675dc238987be2f95e58fba4723abb14cf..d32329d4e2d635a849a390f2ce8db6c51a340b44 100644 (file)
@@ -84,35 +84,9 @@ gdb $(which i3) core.i3.3849
 
 Then, generate a backtrace using:
 
----------
-backtrace
----------
-
-Also, getting an overview of the local variables might help:
------------
-info locals
------------
-
-If your backtrace looks like this:
----------------------------------------------------------------------------------------------------
-(gdb) backtrace
-#0  0x041b1a01 in vfprintf () from /lib/libc.so.6
-#1  0x041b2f80 in vprintf () from /lib/libc.so.6
-#2  0x080555de in slog (fmt=0x8059ba0 "%s:%s:%d - Name should change to \"%s\"\n") at src/util.c:60
-#3  0x0804fa73 in handle_windowname_change_legacy (data=0x0, conn=0x42da908,
-                  state=0 '\0', window=8389918, atom=39, prop=0x4303f90) at src/handlers.c:752
-#4  0x0406cace in ?? () from /usr/lib/libxcb-property.so.1
-#5  0x00000000 in ?? ()
----------------------------------------------------------------------------------------------------
-
-you need to find the first frame which actually belongs to i3 code. You can easily spot them, as
-their filename starts with src/ and has a line number. In this case, frame 2 would be the correct
-frame, so before getting the local variables, switch to frame 2:
-
------------
-frame 2
-info locals
------------
+--------------
+backtrace full
+--------------
 
 == Sending bugreports/debugging on IRC
 
index 72796b0ca24687fe480cbfa9d30e726bea71bae4..22649c38b9faea189263494dc1bf11d4a7af0c84 100644 (file)
@@ -107,23 +107,38 @@ Contains forward definitions for all public functions, aswell as doxygen-compati
 comments (so if you want to get a bit more of the big picture, either browse all
 header files or use doxygen if you prefer that).
 
+src/client.c::
+Contains all functions which are specific to a certain client (make it
+fullscreen, see if its class/name matches a pattern, kill it, …).
+
 src/commands.c::
-Parsing commands
+Parsing commands and actually execute them (focussing, moving, …).
 
 src/config.c::
-Parses the configuration file
+Parses the configuration file.
 
 src/debug.c::
-Contains debugging functions to print unhandled X events
+Contains debugging functions to print unhandled X events.
+
+src/floating.c::
+Contains functions for floating mode (mostly resizing/dragging).
 
 src/handlers.c::
-Contains all handlers for all kind of X events
+Contains all handlers for all kind of X events (new window title, new hints,
+unmapping, key presses, button presses, …).
 
 src/layout.c::
-Renders your layout (screens, workspaces, containers)
+Renders your layout (screens, workspaces, containers).
 
 src/mainx.c::
-Initializes the window manager
+Initializes the window manager.
+
+src/manage.c::
+Looks at existing or new windows and decides whether to manage them. If so, it
+reparents the window and inserts it into our data structures.
+
+src/resize.c::
+Contains the functions to resize columns/rows in the table.
 
 src/resize.c::
 Contains the functions to resize columns/rows in the table.
@@ -213,7 +228,7 @@ chosen for those:
  * ``conn'' is the xcb_connection_t
  * ``event'' is the event of the particular type
  * ``container'' names a container
- * ``client'' names a client, for example when using a `CIRCLEQ_FOREACH`
+ * ``client'' names a client, for example when using a +CIRCLEQ_FOREACH+
 
 == Startup (src/mainx.c, main())
 
@@ -361,9 +376,26 @@ when rendering.
 
 === Resizing containers
 
-By clicking and dragging the border of a container, you can resize it freely.
+By clicking and dragging the border of a container, you can resize the whole column
+(respectively row) which this container is in. This is necessary to keep the table
+layout working and consistent.
+
+Currently, only vertical resizing is implemented.
+
+The resizing works similarly to the resizing of floating windows or movement of floating
+windows:
 
-TODO
+* A new, invisible window with the size of the root window is created (+grabwin+)
+* Another window, 2px width and as high as your screen (or vice versa for horizontal
+  resizing) is created. Its background color is the border color and it is only
+  there to signalize the user how big the container will be (it creates the impression
+  of dragging the border out of the container).
+* The +drag_pointer+ function of +src/floating.c+ is called to grab the pointer and
+  enter an own event loop which will pass all events (expose events) but motion notify
+  events. This function then calls the specified callback (+resize_callback+) which
+  does some boundary checking and moves the helper window. As soon as the mouse
+  button is released, this loop will be terminated.
+* The new width_factor for each involved column (respectively row) will be calculated.
 
 == User commands / commandmode (src/commands.c)
 
@@ -396,7 +428,8 @@ direction to move a window respectively or snap.
 
 == Using git / sending patches
 
-For a short introduction into using git, see TODO.
+For a short introduction into using git, see http://www.spheredev.org/wiki/Git_for_the_lazy
+or, for more documentation, see http://git-scm.com/documentation
 
 When you want to send a patch because you fixed a bug or implemented a cool feature (please
 talk to us before working on features to see whether they are maybe already implemented, not
diff --git a/docs/single_terminal.png b/docs/single_terminal.png
new file mode 100644 (file)
index 0000000..4fe918c
Binary files /dev/null and b/docs/single_terminal.png differ
diff --git a/docs/snapping.png b/docs/snapping.png
new file mode 100644 (file)
index 0000000..65fe6e4
Binary files /dev/null and b/docs/snapping.png differ
diff --git a/docs/two_columns.png b/docs/two_columns.png
new file mode 100644 (file)
index 0000000..6dc8c40
Binary files /dev/null and b/docs/two_columns.png differ
diff --git a/docs/two_terminals.png b/docs/two_terminals.png
new file mode 100644 (file)
index 0000000..20b45ac
Binary files /dev/null and b/docs/two_terminals.png differ
diff --git a/docs/userguide b/docs/userguide
new file mode 100644 (file)
index 0000000..36c30d6
--- /dev/null
@@ -0,0 +1,371 @@
+i3 User’s Guide
+===============
+Michael Stapelberg <michael+i3@stapelberg.de>
+June 2009
+
+This document contains all information you need to configuring and using the i3
+window manager. If it does not, please contact me on IRC, Jabber or E-Mail and
+I’ll help you out.
+
+For a complete listing of the default keybindings, please see the manpage.
+
+== Using i3
+
+=== Creating terminals and moving around
+
+A very basic operation is to create a new terminal. By default, the keybinding
+for that is Mod1+Enter, that is Alt+Enter in the default configuration. By
+pressing Mod1+Enter, a new terminal will be created and it will fill the whole
+space which is available on your screen.
+
+image:single_terminal.png[Single terminal]
+
+It is important to keep in mind that i3 uses a table to manage your windows. At
+the moment, you have exactly one column and one row which leaves you with one
+cell. In this cell, there is a container in which your newly opened terminal is.
+
+If you now open another terminal, you still have only one cell. However, the
+container has both of your terminals. So, a container is just a group of clients
+with a specific layout. You can resize containers as they directly resemble
+columns/rows of the layout table.
+
+image:two_terminals.png[Two terminals]
+
+To move the focus between the two terminals, you use the direction keys which
+you may know from the editor +vi+. However, in i3, your homerow is used for
+these keys (in +vi+, the keys are shifted to the left by one for compatibility
+with most keyboard layouts). Therefore, +Mod1+J+ is left, +Mod1+K+ is down, +Mod1+L+
+is up and `Mod1+;` is right. So, to switch between the terminals, use +Mod1+K+ or
++Mod1+L+.
+
+To create a new row/column, you can simply move a terminal (or any other window)
+to the direction you want to expand your table. So, let’s expand the table to
+the right by pressing `Mod1+Shift+;`.
+
+image:two_columns.png[Two columns]
+
+=== Changing mode of containers
+
+A container can be in two modes at the moment (more to be implemented later):
++default+ or +stacking+. In default mode, clients are sized so that every client
+gets an equal amount of space of the container. In stacking mode, only one
+focused client of the container is displayed and you get a list of windows
+at the top of the container.
+
+To switch the mode, press +Mod1+h+ for stacking and +Mod1+e+ for default.
+
+=== Toggling fullscreen mode for a window
+
+To display a window fullscreen or to go out of fullscreen mode again, press
++Mod1+f+.
+
+=== Opening other applications
+
+Aside from opening applicatios from a terminal, you can also use the handy
++dmenu+ which is opened by pressing +Mod1+v+ by default. Just type the name
+(or a part of it) of the application which you want to open. It has to be in
+your +$PATH+ for that to work.
+
+Furthermore, if you have applications you open very frequently, you can also
+create a keybinding for it. See the section "Configuring i3" for details.
+
+=== Closing windows
+
+If an application does not provide a mechanism to close (most applications
+provide a menu, the escape key or a shortcut like +Control+W+ to close), you
+can press +Mod1+Shift+q+ to kill a window. For applications which support
+the WM_DELETE protocol, this will correctly close the application (saving
+any modifications or doing other cleanup). If the application doesn’t support
+it, your X server will kill the window and the behaviour depends on the
+application.
+
+=== Using workspaces
+
+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 +Mod1+num+ where +num+ is the number of the workspace
+you want to use. If the workspace does not exist yet, it will be created.
+
+A common paradigm is to put the web browser on one workspace, communication
+applications (+mutt+, +irssi+, ...) on another one and the ones with which you
+work on the third one. Of course, there is no need to follow this approach.
+
+If you have multiple screens, a workspace will be created on each screen. If
+you open a new workspace, it will be bound to the screen you created it on.
+When you switch to a workspace on another screen, i3 will set focus to this
+screen.
+
+=== Moving windows to workspaces
+
+To move a window to another workspace, simply press +Mod1+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.
+
+=== Resizing columns
+
+To resize columns just grab the border between the two columns and move it to
+the wanted size.
+
+A command for doing this via keyboard will be implemented soon.
+
+=== Restarting i3 inplace
+
+To restart i3 inplace (and thus get it into a clean state if it has a bug, to
+reload your configuration or even to upgrade to a newer version of i3) you
+can use +Mod1+Shift+r+. Be aware, though, that this kills your current layout
+and all the windows you have opened will be put in a default container in only
+one cell. Saving the layout will be implemented in a later version.
+
+=== Exiting i3
+
+To cleanly exit i3 without killing your X server, you can use +Mod1+Shift+e+.
+
+=== Snapping
+
+Snapping is a mechanism to increase/decrease the colspan/rowspan of a container.
+Colspan/rowspan is the amount of columns/rows a specific cell of the table
+consumes. This is easier explained by giving an example, so take the following
+layout:
+
+image:snapping.png[Snapping example]
+
+To use the full size of your screen, you can now snap container 3 downwards
+by pressing +Mod1+Control+k+ (or snap container 2 rightwards).
+
+=== Floating
+
+Floating is the opposite of tiling mode. The position and size of a window
+are then not managed by i3, but by you. Using this mode violates the tiling
+paradigm but can be useful for some corner cases like "Save as" dialog
+windows or toolbar windows (GIMP or similar).
+
+You can enable floating for a window by pressing +Mod1+Shift+Space+. By
+dragging the window’s titlebar with your mouse, you can move the window
+around. By grabbing the borders and moving them you can resize the window.
+
+Bindings for doing this with your keyboard will follow.
+
+Floating clients are always on top of tiling clients.
+
+== Configuring i3
+
+This is where the real fun begins ;-). Most things are very dependant on your
+ideal working environment, so we can’t make reasonable defaults for them.
+
+While not using a programming language for the configuration, i3 stays
+quite flexible regarding to the things you usually want your window manager
+to do.
+
+For example, you can configure bindings to jump to specific windows,
+you can set specific applications to start on a specific workspace, you can
+automatically start applications, you can change the colors of i3 or bind
+your keys to do useful stuff.
+
+terminal::
+       Specifies the terminal emulator program you prefer. It will be started
+       by default when you press Mod1+Enter, but you can overwrite this. Refer
+       to it as +$terminal+ to keep things modular.
+font::
+       Specifies the default font you want i3 to use. Use an X core font
+       descriptor here, like
+       +-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1+. You can
+       use +xfontsel(1)+ to pick one.
+
+=== Keyboard bindings
+
+You can use each command (see below) using keyboard bindings. At the moment,
+keyboard bindings require you to specify the keycode (38) of the key, not its key
+symbol ("a"). This has some advantages (keybindings make sense regardless of
+the layout you type) and some disadvantages (hard to remember, you have to look
+them up every time).
+
+*Syntax*:
+--------------------------------
+bind [Modifiers+]keycode command
+--------------------------------
+
+*Examples*:
+--------------------------------
+# Fullscreen
+bind Mod1+41 f
+
+# Restart
+bind Mod1+Shift+27 restart
+--------------------------------
+
+Available Modifiers:
+
+Mod1-Mod5, Shift, Control::
+Standard modifiers, see +xmodmap(1)+
+
+Mode_switch::
+Unlike other window managers, i3 can use Mode_switch as a modifier. This allows
+you to remap capslock (for example) to Mode_switch and use it for both: typing
+umlauts or special characters 'and' having some comfortably reachable key
+bindings. For example, when typing, capslock+1 or capslock+2 for switching
+workspaces is totally convenient. Try it :-).
+
+=== The floating modifier
+
+To move floating windows with your mouse, you can either grab their titlebar
+or configure the so called floating modifier which you can then press and
+click anywhere in the window itself. The most common setup is to configure
+it as the same one you use for managing windows (Mod1 for example). Afterwards,
+you can press Mod1, click into a window using your left mouse button and drag
+it to the position you want it at.
+
+*Syntax*:
+--------------------------------
+floating_modifier <Modifiers>
+--------------------------------
+
+*Examples*:
+--------------------------------
+floating_modifier Mod1
+--------------------------------
+
+
+=== Variables
+
+As you learned in the previous section about keyboard bindings, you will have
+to configure lots of bindings containing modifier keys. If you want to save
+yourself some typing and have the possibility to change the modifier you want
+to use later, variables can be handy.
+
+*Syntax*:
+--------------
+set name value
+--------------
+
+*Examples*:
+------------------------
+set $m Mod1
+bind $m+Shift+27 restart
+------------------------
+
+Variables are directly replaced in the file when parsing, there is no fancy
+handling and there are absolutely no plans to change this. If you need a more
+dynamic configuration, you should create a little script, like when configuring
+wmii.
+
+=== Automatically putting clients on specific workspaces
+
+It is recommended that you match on window classes whereever possible because
+some applications first create their window and then care about setting the
+correct title. Firefox with Vimperator comes to mind, as the window starts up
+being named Firefox and only when Vimperator is loaded, the title changes. As
+i3 will get the title as soon as the application maps the window (mapping means
+actually displaying it on the screen), you’d need to have to match on Firefox
+in this case.
+
+You can use the special workspace +~+ to specify that matching clients should
+be put into floating mode.
+
+*Syntax*:
+----------------------------------------------------
+assign ["]window class[/window title]["] [→] workspace
+----------------------------------------------------
+
+*Examples*:
+----------------------
+assign urxvt 2
+assign urxvt → 2
+assign "urxvt" → 2
+assign "urxvt/VIM" → 3
+assign "gecko" → ~
+----------------------
+
+=== Automatically starting applications on startup
+
+By using the +exec+ keyword outside a keybinding, you can configure which
+commands will be performed by i3 on the first start (not when reloading inplace
+however). The commands will be run in order.
+
+*Syntax*:
+------------
+exec command
+------------
+
+*Examples*:
+--------------------------------
+exec sudo i3status | dzen2 -dock
+--------------------------------
+
+=== Jumping to specific windows
+
+Especially when in a multi-monitor environment, you want to quickly jump to a specific
+window, for example while currently working on workspace 3 you may want to jump to
+your mailclient to mail your boss that you’ve achieved some important goal. Instead
+of figuring out how to navigate to your mailclient, it would be more convenient to
+have a shortcut.
+
+*Syntax*:
+----------------------------------------------------
+jump ["]window class[/window title]["]
+jump workspace [ column row ]
+----------------------------------------------------
+
+You can either use the same matching algorithm as in the +assign+ command (see above)
+or you can specify the position of the client if you always use the same layout.
+
+*Examples*:
+--------------------------------------
+# Get me to the next open VIM instance
+bind Mod1+38 jump "urxvt/VIM"
+--------------------------------------
+
+=== Traveling the focus stack
+
+This mechanism can be thought of as the opposite of the +jump+ command. It travels
+the focus stack and jumps to the window you focused before.
+
+*Syntax*:
+--------------
+focus [number] | floating | tilling | ft
+--------------
+
+Where +number+ by default is 1 meaning that the next client in the focus stack will
+be selected.
+
+The special values have the following meaning:
+
+floating::
+       The next floating window is selected.
+tiling::
+       The next tiling window is selected.
+ft::
+       If the current window is floating, the next tiling window will be selected
+       and vice-versa.
+
+=== Changing colors
+
+You can change all colors which i3 uses to draw the window decorations and the
+bottom bar.
+
+*Syntax*:
+--------------------------------------------
+colorclass border background text
+--------------------------------------------
+
+Where colorclass can be one of:
+
+client.focused::
+       A client which currently has the focus.
+client.focused_inactive::
+       A client which is the focused one of its container, but it does not have
+       the focus at the moment.
+client.unfocused::
+       A client which is not the focused one of its container.
+bar.focused::
+       The current workspace in the bottom bar.
+bar.unfocused::
+       All other workspaces in the bottom bar.
+
+Colors are in HTML hex format, see below.
+
+*Examples*:
+--------------------------------------
+# class        border  backgr. text
+client.focused #2F343A #900000 #FFFFFF
+--------------------------------------
index d2cf13a09e8eca695b8c37e94f1b480a24bdb553..816b364cd7dfa892abd52821de58b888227ac12d 100644 (file)
--- a/i3.config
+++ b/i3.config
@@ -1,11 +1,17 @@
 # This configuration uses Mod1 and Mod3. Make sure they are mapped properly using xev(1)
 # and xmodmap(1). Usually, Mod1 is Alt (Alt_L) and Mod3 is Windows (Super_L)
 
+# Tell i3 about your preferred terminal. You can refer to this as $terminal
+# later. It is recommended to set this option to allow i3 to open a terminal
+# containing the introduction on first start.
 terminal /usr/bin/urxvt
 
 # ISO 10646 = Unicode
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 
+# Use Mouse+Mod1 to drag floating windows to their wanted position
+floating_modifier Mod1
+
 # Fullscreen (Mod1+f)
 bind Mod1+41 f
 
@@ -15,6 +21,13 @@ bind Mod1+43 s
 # Default (Mod1+e)
 bind Mod1+26 d
 
+# Toggle tiling/floating of the current window (Mod1+Shift+Space)
+bind Mod1+Shift+65 t
+
+# Go into the tiling layer / floating layer, depending on whether
+# the current window is tiling / floating (Mod1+t)
+bind Mod1+28 focus ft
+
 # Focus (Mod1+j/k/l/;)
 bind Mod1+44 h
 bind Mod1+45 j
diff --git a/include/client.h b/include/client.h
new file mode 100644 (file)
index 0000000..85a4153
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * vim:ts=8:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * (c) 2009 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ */
+#include <xcb/xcb.h>
+
+#include "data.h"
+
+#ifndef _CLIENT_H
+#define _CLIENT_H
+
+/**
+ * Removes the given client from the container, either because it will be inserted into another
+ * one or because it was unmapped
+ *
+ */
+void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container, bool remove_from_focusstack);
+
+/**
+ * Warps the pointer into the given client (in the middle of it, to be specific), therefore
+ * selecting it
+ *
+ */
+void client_warp_pointer_into(xcb_connection_t *conn, Client *client);
+
+/**
+ * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
+ *
+ */
+void client_kill(xcb_connection_t *conn, Client *window);
+
+/**
+ * Checks if the given window class and title match the given client
+ * Window title is passed as "normal" string and as UCS-2 converted string for
+ * matching _NET_WM_NAME capable clients as well as those using legacy hints.
+ *
+ */
+bool client_matches_class_name(Client *client, char *to_class, char *to_title,
+                               char *to_title_ucs, int to_title_ucs_len);
+
+/**
+ * Enters fullscreen mode for the given client. This is called by toggle_fullscreen
+ * and when moving a fullscreen client to another screen.
+ *
+ */
+void client_enter_fullscreen(xcb_connection_t *conn, Client *client);
+
+/**
+ * Toggles fullscreen mode for the given client. It updates the data structures and
+ * reconfigures (= resizes/moves) the client and its frame to the full size of the
+ * screen. When leaving fullscreen, re-rendering the layout is forced.
+ *
+ */
+void client_toggle_fullscreen(xcb_connection_t *conn, Client *client);
+
+/**
+ * Sets the position of the given client in the X stack to the highest (tiling layer is always
+ * on the same position, so this doesn’t matter) below the first floating client, so that
+ * floating windows are always on top.
+ *
+ */
+void client_set_below_floating(xcb_connection_t *conn, Client *client);
+
+/**
+ * Returns true if the client is floating. Makes the code more beatiful, as floating
+ * is not simply a boolean, but also saves whether the user selected the current state
+ * or whether it was automatically set.
+ *
+ */
+bool client_is_floating(Client *client);
+
+#endif
index 59c6866f62434e19d9f4fb37652c063eac17b4d6..1e85d4712dbea43b907d08adcd3bbd7a2b4ba22c 100644 (file)
@@ -1,12 +1,56 @@
+/*
+ * vim:ts=8:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * © 2009 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ * include/config.h: Contains all structs/variables for
+ * the configurable part of i3
+ *
+ */
+
 #ifndef _CONFIG_H
 #define _CONFIG_H
 
+#include "queue.h"
+
 typedef struct Config Config;
 extern Config config;
 
+struct Colortriple {
+        uint32_t border;
+        uint32_t background;
+        uint32_t text;
+};
+
+struct Variable {
+        char *key;
+        char *value;
+
+        SLIST_ENTRY(Variable) variables;
+};
+
 struct Config {
-       const char *terminal;
-       const char *font;
+        const char *terminal;
+        const char *font;
+
+        /** The modifier which needs to be pressed in combination with your mouse
+         * buttons to do things with floating windows (move, resize) */
+        uint32_t floating_modifier;
+
+        /* Color codes are stored here */
+        struct config_client {
+                struct Colortriple focused;
+                struct Colortriple focused_inactive;
+                struct Colortriple unfocused;
+        } client;
+        struct config_bar {
+                struct Colortriple focused;
+                struct Colortriple unfocused;
+        } bar;
 };
 
 /**
@@ -16,6 +60,6 @@ struct Config {
  * configuration file.
  *
  */
-void load_configuration(const char *override_configfile);
+void load_configuration(xcb_connection_t *conn, const char *override_configfile);
 
 #endif
index 2d2f0522363d2cee88115179df6bc58ab2d129f0..b468c1f7092aeac0d9bf8740e144c763ce2cdf9f 100644 (file)
@@ -3,7 +3,7 @@
  *
  * i3 - an improved dynamic tiling window manager
  *
- * (c) 2009 Michael Stapelberg and contributors
+ * © 2009 Michael Stapelberg and contributors
  *
  * See file LICENSE for license information.
  *
@@ -161,12 +161,21 @@ struct Workspace {
         int current_row;
         int current_col;
 
+        /* Should clients on this workspace be automatically floating? */
+        bool auto_float;
+        /* Are the floating clients on this workspace currently hidden? */
+        bool floating_hidden;
+
         Client *fullscreen_client;
 
         /* The focus stack contains the clients in the correct order of focus so that
            the focus can be reverted correctly when a client is closed */
         SLIST_HEAD(focus_stack_head, Client) focus_stack;
 
+        /* This tail queue contains the floating clients in order of when they were first
+         * set to floating (new floating clients are just appended) */
+        TAILQ_HEAD(floating_clients_head, Client) floating_clients;
+
         /* Backpointer to the screen this workspace is on */
         i3Screen *screen;
 
@@ -197,6 +206,30 @@ struct Binding {
         TAILQ_ENTRY(Binding) bindings;
 };
 
+/*
+ * Holds a command specified by an exec-line in the config (see src/config.c)
+ *
+ */
+struct Autostart {
+        /* Command, like in command mode */
+        char *command;
+        TAILQ_ENTRY(Autostart) autostarts;
+};
+
+/*
+ * Holds an assignment for a given window class/title to a specific workspace
+ * (see src/config.c)
+ *
+ */
+struct Assignment {
+        char *windowclass_title;
+        /* floating is true if this was an assignment to the special workspace "~".
+         * Matching clients will be put into floating mode automatically. */
+        bool floating;
+        int workspace;
+        TAILQ_ENTRY(Assignment) assignments;
+};
+
 /*
  * Data structure for cached font information:
  * - font id in X11 (load it once)
@@ -221,6 +254,10 @@ struct Font {
  *
  */
 struct Client {
+        /* initialized will be set to true if the client was fully initialized by
+         * manage_window() and all functions can be used normally */
+        bool initialized;
+
         /* if you set a client to floating and set it back to managed, it does remember its old
            position and *tries* to get back there */
         Cell old_position;
@@ -232,6 +269,8 @@ struct Client {
 
         /* x, y, width, height of the frame */
         Rect rect;
+        /* Position in floating mode and in tiling mode are saved separately */
+        Rect floating_rect;
         /* x, y, width, height of the child (relative to its frame) */
         Rect child_rect;
 
@@ -255,9 +294,18 @@ struct Client {
            legacy window names are ignored. */
         bool uses_net_wm_name;
 
+        /* Holds the WM_CLASS, useful for matching the client in commands */
+        char *window_class;
+
         /* fullscreen is pretty obvious */
         bool fullscreen;
 
+        /* floating? (= not in tiling layout) This cannot be simply a bool because we want to keep track
+         * of whether the status was set by the application (by setting WM_CLASS to tools for example) or
+         * by the user. The user’s choice overwrites automatic mode, of course. The order of the values
+         * is important because we check with >= FLOATING_AUTO_ON if a client is floating. */
+        enum { FLOATING_AUTO_OFF = 0, FLOATING_USER_OFF = 1, FLOATING_AUTO_ON = 2, FLOATING_USER_ON = 3 } floating;
+
         /* Ensure TITLEBAR_TOP maps to 0 because we use calloc for initialization later */
         enum { TITLEBAR_TOP = 0, TITLEBAR_LEFT, TITLEBAR_RIGHT, TITLEBAR_BOTTOM, TITLEBAR_OFF } titlebar_position;
 
@@ -283,6 +331,7 @@ struct Client {
         CIRCLEQ_ENTRY(Client) clients;
         SLIST_ENTRY(Client) dock_clients;
         SLIST_ENTRY(Client) focus_clients;
+        TAILQ_ENTRY(Client) floating_clients;
 };
 
 /*
diff --git a/include/floating.h b/include/floating.h
new file mode 100644 (file)
index 0000000..b0c0b7c
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * vim:ts=8:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * © 2009 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ */
+#ifndef _FLOATING_H
+#define _FLOATING_H
+
+/** Callback for dragging */
+typedef void(*callback_t)(Rect*, uint32_t, uint32_t);
+
+/** On which border was the dragging initiated? */
+typedef enum { BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM} border_t;
+
+/**
+ * Enters floating mode for the given client.
+ * Correctly takes care of the position/size (separately stored for tiling/floating mode)
+ * and repositions/resizes/redecorates the client.
+ *
+ * If the automatic flag is set to true, this was an automatic update by a change of the
+ * window class from the application which can be overwritten by the user.
+ *
+ */
+void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic);
+
+/**
+ * Removes the floating client from its workspace and attaches it to the new workspace.
+ * This is centralized here because it may happen if you move it via keyboard and
+ * if you move it using your mouse.
+ *
+ */
+void floating_assign_to_workspace(Client *client, Workspace *new_workspace);
+
+/**
+ * Called whenever the user clicks on a border (not the titlebar!) of a floating window.
+ * Determines on which border the user clicked and launches the drag_pointer function
+ * with the resize_callback.
+ *
+ */
+int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event);
+
+/**
+ * Called when the user clicked on the titlebar of a floating window.
+ * Calls the drag_pointer function with the drag_window callback
+ *
+ */
+void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event);
+
+/**
+ * Changes focus in the given direction for floating clients.
+ *
+ * Changing to the left/right means going to the previous/next floating client,
+ * changing to top/bottom means cycling through the Z-index.
+ *
+ */
+void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction);
+
+/**
+ * Moves the client 10px to the specified direction.
+ *
+ */
+void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction);
+
+/**
+ * Hides all floating clients (or show them if they are currently hidden) on
+ * the specified workspace.
+ *
+ */
+void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace);
+
+/**
+ * This function grabs your pointer and lets you drag stuff around (borders).
+ * Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
+ * and the given callback will be called with the parameters specified (client,
+ * border on which the click originally was), the original rect of the client,
+ * the event and the new coordinates (x, y).
+ *
+ */
+void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
+                  xcb_window_t confine_to, border_t border, callback_t callback);
+
+#endif
index b2c0de94cf843fe5180a8ca60f0beff677afd84a..b4b2c99aa7d803ce874d7ed5d2928f95a271be7b 100644 (file)
@@ -85,7 +85,15 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
  *
  */
 int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state,
-                                xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop);
+                                    xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop);
+
+/**
+ * Store the window classes for jumping to them later.
+ *
+ */
+int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
+                              xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop);
+
 
 /**
  * Expose event means we should redraw our windows (= title bar)
@@ -116,4 +124,14 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi
 int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
                         xcb_atom_t name, xcb_get_property_reply_t *reply);
 
+/**
+ * Handles the transient for hints set by a window, signalizing that this window is a popup window
+ * for some other window.
+ *
+ * See ICCCM 4.1.2.6 for more details
+ *
+ */
+int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
+                         xcb_atom_t name, xcb_get_property_reply_t *reply);
+
 #endif
index 02df999610fbd1efbc4b1e3d4291616aca3e59f2..ccf7a4951552612d00407bc86c429e5c435d9700 100644 (file)
@@ -3,7 +3,7 @@
  *
  * i3 - an improved dynamic tiling window manager
  *
- * (c) 2009 Michael Stapelberg and contributors
+ * © 2009 Michael Stapelberg and contributors
  *
  * See file LICENSE for license information.
  *
 #ifndef _I3_H
 #define _I3_H
 
-#define NUM_ATOMS 13
+#define NUM_ATOMS 17
 
 extern char **start_argv;
 extern Display *xkbdpy;
 extern TAILQ_HEAD(bindings_head, Binding) bindings;
+extern TAILQ_HEAD(autostarts_head, Autostart) autostarts;
+extern TAILQ_HEAD(assignments_head, Assignment) assignments;
 extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins;
 extern xcb_event_handlers_t evenths;
 extern int num_screens;
 extern xcb_atom_t atoms[NUM_ATOMS];
 
-void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa);
-void reparent_window(xcb_connection_t *conn, xcb_window_t child,
-                     xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
-                     int16_t x, int16_t y, uint16_t width, uint16_t height);
-
 #endif
index 19a40c5d157de4af771a23959b5647cb2f391b87..53dfbb9dead58fd1c7ef2115e257e13b2e6fd6d5 100644 (file)
@@ -36,6 +36,18 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
  */
 void redecorate_window(xcb_connection_t *conn, Client *client);
 
+/**
+ * Pushes the client’s x and y coordinates to X11
+ *
+ */
+void reposition_client(xcb_connection_t *conn, Client *client);
+
+/**
+ * Pushes the client’s width/height to X11 and resizes the child window
+ *
+ */
+void resize_client(xcb_connection_t *conn, Client *client);
+
 /**
  * Renders the given container. Is called by render_layout() or individually (for example
  * when focus changes in a stacking container)
diff --git a/include/manage.h b/include/manage.h
new file mode 100644 (file)
index 0000000..52816e3
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * vim:ts=8:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * (c) 2009 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ */
+#include <xcb/xcb.h>
+
+#include "data.h"
+
+#ifndef _MANAGE_H
+#define _MANAGE_H
+
+/**
+ * Go through all existing windows (if the window manager is restarted) and manage them
+ *
+ */
+void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root);
+
+/**
+ * Do some sanity checks and then reparent the window.
+ *
+ */
+void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
+                   xcb_window_t window, window_attributes_t wa);
+
+/**
+ * reparent_window() gets called when a new window was opened and becomes a child of the root
+ * window, or it gets called by us when we manage the already existing windows at startup.
+ *
+ * Essentially, this is the point where we take over control.
+ *
+ */
+void reparent_window(xcb_connection_t *conn, xcb_window_t child,
+                     xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
+                     int16_t x, int16_t y, uint16_t width, uint16_t height);
+
+#endif
index 889dcf122f175b53d2b10815b23d425ee81cbaa2..3fd6b18a7aa537895c39cd101159c875374d270a 100644 (file)
@@ -25,7 +25,7 @@
                         for (int cols = 0; cols < (workspace)->cols; cols++) \
                                 for (int rows = 0; rows < (workspace)->rows; rows++)
 #define FREE(pointer) do { \
-        if (pointer == NULL) { \
+        if (pointer != NULL) { \
                 free(pointer); \
                 pointer = NULL; \
         } \
@@ -54,7 +54,7 @@ void slog(char *fmt, ...);
  * Prints the message (see printf()) to stderr, then exits the program.
  *
  */
-void die(char *fmt, ...);
+void die(char *fmt, ...) __attribute__((__noreturn__));
 
 /**
  * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that there
@@ -124,18 +124,22 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes
 char *convert_utf8_to_ucs2(char *input, int *real_strlen);
 
 /**
- * Removes the given client from the container, either because it will be inserted into another
- * one or because it was unmapped
+ * Returns the client which comes next in focus stack (= was selected before) for
+ * the given container, optionally excluding the given client.
  *
  */
-void remove_client_from_container(xcb_connection_t *conn, Client *client, Container *container);
+Client *get_last_focused_client(xcb_connection_t *conn, Container *container, Client *exclude);
 
 /**
- * Returns the client which comes next in focus stack (= was selected before) for
- * the given container, optionally excluding the given client.
+ * Unmaps all clients (and stack windows) of the given workspace.
+ *
+ * This needs to be called separately when temporarily rendering
+ * a workspace which is not the active workspace to force
+ * reconfiguration of all clients, like in src/xinerama.c when
+ * re-assigning a workspace to another screen.
  *
  */
-Client *get_last_focused_client(xcb_connection_t *conn, Container *container, Client *exclude);
+void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws);
 
 /**
  * Unmaps all clients (and stack windows) of the given workspace.
@@ -170,24 +174,12 @@ void leave_stack_mode(xcb_connection_t *conn, Container *container);
 void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode);
 
 /**
- * Warps the pointer into the given client (in the middle of it, to be specific), therefore
- * selecting it
- *
- */
-void warp_pointer_into(xcb_connection_t *conn, Client *client);
-
-/**
- * Toggles fullscreen mode for the given client. It updates the data structures and
- * reconfigures (= resizes/moves) the client and its frame to the full size of the
- * screen. When leaving fullscreen, re-rendering the layout is forced.
- *
- */
-void toggle_fullscreen(xcb_connection_t *conn, Client *client);
-
-/**
- * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
+ * Gets the first matching client for the given window class/window title.
+ * If the paramater specific is set to a specific client, only this one
+ * will be checked.
  *
  */
-void kill_window(xcb_connection_t *conn, Client *window);
+Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle,
+                            Client *specific);
 
 #endif
index c6bb70b2de4b7f2201e39f10b525ccfbd780f045..a8a75876907053c2bbf6a1019ea6995965f40a19 100644 (file)
@@ -49,6 +49,10 @@ enum { _NET_SUPPORTED = 0,
         _NET_WM_STATE,
         _NET_WM_WINDOW_TYPE,
         _NET_WM_WINDOW_TYPE_DOCK,
+        _NET_WM_WINDOW_TYPE_DIALOG,
+        _NET_WM_WINDOW_TYPE_UTILITY,
+        _NET_WM_WINDOW_TYPE_TOOLBAR,
+        _NET_WM_WINDOW_TYPE_SPLASH,
         _NET_WM_DESKTOP,
         _NET_WM_STRUT_PARTIAL,
         WM_PROTOCOLS,
@@ -126,4 +130,10 @@ void fake_absolute_configure_notify(xcb_connection_t *conn, Client *client);
  */
 void xcb_get_numlock_mask(xcb_connection_t *conn);
 
+/**
+ * Raises the given window (typically client->frame) above all other windows
+ *
+ */
+void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window);
+
 #endif
index 3854debdc673468763aace364e3a066a1e56237b..cd411ec718f5dad67fc8017b7e4410f175837eb0 100644 (file)
@@ -7,7 +7,7 @@ template::[header-declarations]
 <refentrytitle>{mantitle}</refentrytitle>
 <manvolnum>{manvolnum}</manvolnum>
 <refmiscinfo class="source">i3</refmiscinfo>
-<refmiscinfo class="version">alpha</refmiscinfo>
+<refmiscinfo class="version">beta</refmiscinfo>
 <refmiscinfo class="manual">i3 Manual</refmiscinfo>
 </refmeta>
 <refnamediv>
index 4f0c3fe417d327b4df3d1168197d08dc74cba9ea..cf592c62f4b6b3e771cecd393a8c0d20d270f5fd 100644 (file)
@@ -1,7 +1,7 @@
 i3(1)
 =====
 Michael Stapelberg <michael+i3@stapelberg.de>
-v3.alpha-bf1, May 2009
+v3.beta, May 2009
 
 == NAME
 
@@ -9,7 +9,15 @@ i3 - an improved dynamic, tiling window manager
 
 == SYNOPSIS
 
-i3 [-c configfile]
+i3 [-c configfile] [-a]
+
+== OPTIONS
+
+-c::
+Specifies an alternate configuration file path
+
+-a::
+Disables autostart.
 
 == DESCRIPTION
 
@@ -98,8 +106,17 @@ Enable stacking layout for the current container.
 Mod1+e::
 Enable default layout for the current container.
 
+Mod1+Shift+Space::
+Toggle tiling/floating for the current window.
+
+Mod1+t::
+Select the first tiling window if the current window is floating and vice-versa.
+
 Mod1+Shift+q::
-Kills the current client.
+Kills the current window. This is equivalent to "clicking on the close button", meaning a polite
+request to the application to close this window. For example, Firefox will save its session
+upon such a request. If the application does not support that, the window will be killed and
+it depends on the application what happens.
 
 Mod1+Shift+r::
 Restarts i3 in place (without losing any windows, but the layout).
@@ -157,6 +174,13 @@ bind Mod1+43 s
 # Default (Mod1+e)
 bind Mod1+26 d
 
+# Toggle tiling/floating of the current window (Mod1+Shift+Space)
+bind Mod1+Shift+65 t
+
+# Go into the tiling layer / floating layer, depending on whether
+# the current window is tiling / floating (Mod1+t)
+bind Mod1+28 focus ft
+
 # Focus (Mod1+j/k/l/;)
 bind Mod1+44 h
 bind Mod1+45 j
@@ -225,6 +249,9 @@ export LC_TELEPHONE=de_DE.UTF-8
 export LC_MEASUREMENT=de_DE.UTF-8
 export LC_IDENTIFICATION=de_DE.UTF-8
 
+# Set background color
+xsetroot -solid "#333333"
+
 # Enable core dumps in case something goes wrong
 ulimit -c unlimited
 
@@ -238,6 +265,14 @@ exec /usr/bin/i3 >> ~/.i3/logfile
 There is still lot of work to do. Please check our bugtracker for up-to-date information
 about tasks which are still not finished.
 
+== SEE ALSO
+
+You should have a copy of the userguide (featuring nice screenshots/graphics which is why this
+is not integrated into this manpage), the debugging guide and the "how to hack" guide. If you
+are building from source, run +make -C docs+.
+
+You can also access these documents online at http://i3.zekjur.net/
+
 == AUTHOR
 
 Michael Stapelberg and contributors
diff --git a/src/client.c b/src/client.c
new file mode 100644 (file)
index 0000000..c3a80c3
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * vim:ts=8:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * © 2009 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ * client.c: holds all client-specific functions
+ *
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_icccm.h>
+
+#include "data.h"
+#include "i3.h"
+#include "xcb.h"
+#include "util.h"
+#include "queue.h"
+#include "layout.h"
+#include "client.h"
+
+/*
+ * Removes the given client from the container, either because it will be inserted into another
+ * one or because it was unmapped
+ *
+ */
+void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container, bool remove_from_focusstack) {
+        CIRCLEQ_REMOVE(&(container->clients), client, clients);
+
+        if (remove_from_focusstack)
+                SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients);
+
+        /* If the container will be empty now and is in stacking mode, we need to
+           unmap the stack_win */
+        if (CIRCLEQ_EMPTY(&(container->clients)) && container->mode == MODE_STACK) {
+                struct Stack_Window *stack_win = &(container->stack_win);
+                stack_win->rect.height = 0;
+                xcb_unmap_window(conn, stack_win->window);
+        }
+}
+
+/*
+ * Warps the pointer into the given client (in the middle of it, to be specific), therefore
+ * selecting it
+ *
+ */
+void client_warp_pointer_into(xcb_connection_t *conn, Client *client) {
+        int mid_x = client->rect.width / 2,
+            mid_y = client->rect.height / 2;
+        xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
+}
+
+/*
+ * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
+ *
+ */
+static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
+        xcb_get_property_cookie_t cookie;
+        xcb_get_wm_protocols_reply_t protocols;
+        bool result = false;
+
+        cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
+        if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
+                return false;
+
+        /* Check if the client’s protocols have the requested atom set */
+        for (uint32_t i = 0; i < protocols.atoms_len; i++)
+                if (protocols.atoms[i] == atom)
+                        result = true;
+
+        xcb_get_wm_protocols_reply_wipe(&protocols);
+
+        return result;
+}
+
+/*
+ * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
+ *
+ */
+void client_kill(xcb_connection_t *conn, Client *window) {
+        /* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */
+        if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) {
+                LOG("Killing window the hard way\n");
+                xcb_kill_client(conn, window->child);
+                return;
+        }
+
+        xcb_client_message_event_t ev;
+
+        memset(&ev, 0, sizeof(xcb_client_message_event_t));
+
+        ev.response_type = XCB_CLIENT_MESSAGE;
+        ev.window = window->child;
+        ev.type = atoms[WM_PROTOCOLS];
+        ev.format = 32;
+        ev.data.data32[0] = atoms[WM_DELETE_WINDOW];
+        ev.data.data32[1] = XCB_CURRENT_TIME;
+
+        LOG("Sending WM_DELETE to the client\n");
+        xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
+        xcb_flush(conn);
+}
+
+/*
+ * Checks if the given window class and title match the given client
+ * Window title is passed as "normal" string and as UCS-2 converted string for
+ * matching _NET_WM_NAME capable clients as well as those using legacy hints.
+ *
+ */
+bool client_matches_class_name(Client *client, char *to_class, char *to_title,
+                               char *to_title_ucs, int to_title_ucs_len) {
+        /* Check if the given class is part of the window class */
+        if (client->window_class == NULL || strcasestr(client->window_class, to_class) == NULL)
+                return false;
+
+        /* If no title was given, we’re done */
+        if (to_title == NULL)
+                return true;
+
+        if (client->name_len > -1) {
+                /* UCS-2 converted window titles */
+                if (client->name == NULL || memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL)
+                        return false;
+        } else {
+                /* Legacy hints */
+                if (client->name == NULL || strcasestr(client->name, to_title) == NULL)
+                        return false;
+        }
+
+        return true;
+}
+
+/*
+ * Enters fullscreen mode for the given client. This is called by toggle_fullscreen
+ * and when moving a fullscreen client to another screen.
+ *
+ */
+void client_enter_fullscreen(xcb_connection_t *conn, Client *client) {
+        Workspace *workspace = client->workspace;
+
+        if (workspace->fullscreen_client != NULL) {
+                LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
+                return;
+        }
+
+        client->fullscreen = true;
+        workspace->fullscreen_client = client;
+        LOG("Entering fullscreen mode...\n");
+        /* We just entered fullscreen mode, let’s configure the window */
+        uint32_t mask = XCB_CONFIG_WINDOW_X |
+                        XCB_CONFIG_WINDOW_Y |
+                        XCB_CONFIG_WINDOW_WIDTH |
+                        XCB_CONFIG_WINDOW_HEIGHT;
+        uint32_t values[4] = {workspace->rect.x,
+                              workspace->rect.y,
+                              workspace->rect.width,
+                              workspace->rect.height};
+
+        LOG("child itself will be at %dx%d with size %dx%d\n",
+                        values[0], values[1], values[2], values[3]);
+
+        xcb_configure_window(conn, client->frame, mask, values);
+
+        /* Child’s coordinates are relative to the parent (=frame) */
+        values[0] = 0;
+        values[1] = 0;
+        xcb_configure_window(conn, client->child, mask, values);
+
+        /* Raise the window */
+        values[0] = XCB_STACK_MODE_ABOVE;
+        xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
+
+        Rect child_rect = workspace->rect;
+        child_rect.x = child_rect.y = 0;
+        fake_configure_notify(conn, child_rect, client->child);
+
+        xcb_flush(conn);
+}
+
+/*
+ * Toggles fullscreen mode for the given client. It updates the data structures and
+ * reconfigures (= resizes/moves) the client and its frame to the full size of the
+ * screen. When leaving fullscreen, re-rendering the layout is forced.
+ *
+ */
+void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) {
+        /* dock clients cannot enter fullscreen mode */
+        assert(!client->dock);
+
+        Workspace *workspace = client->workspace;
+
+        if (!client->fullscreen) {
+                client_enter_fullscreen(conn, client);
+                return;
+        }
+
+        LOG("leaving fullscreen mode\n");
+        client->fullscreen = false;
+        workspace->fullscreen_client = NULL;
+        if (client_is_floating(client)) {
+                /* For floating clients it’s enough if we just reconfigure that window (in fact,
+                 * re-rendering the layout will not update the client.) */
+                reposition_client(conn, client);
+                resize_client(conn, client);
+                /* redecorate_window flushes */
+                redecorate_window(conn, client);
+        } else {
+                client_set_below_floating(conn, client);
+
+                /* Because the coordinates of the window haven’t changed, it would not be
+                   re-configured if we don’t set the following flag */
+                client->force_reconfigure = true;
+                /* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */
+                render_layout(conn);
+        }
+
+        xcb_flush(conn);
+}
+
+/*
+ * Sets the position of the given client in the X stack to the highest (tiling layer is always
+ * on the same position, so this doesn’t matter) below the first floating client, so that
+ * floating windows are always on top.
+ *
+ */
+void client_set_below_floating(xcb_connection_t *conn, Client *client) {
+        /* Ensure that it is below all floating clients */
+        Client *first_floating = TAILQ_FIRST(&(client->workspace->floating_clients));
+        if (first_floating != TAILQ_END(&(client->workspace->floating_clients))) {
+                LOG("Setting below floating\n");
+                uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW };
+                xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
+        }
+}
+
+/*
+ * Returns true if the client is floating. Makes the code more beatiful, as floating
+ * is not simply a boolean, but also saves whether the user selected the current state
+ * or whether it was automatically set.
+ *
+ */
+bool client_is_floating(Client *client) {
+        return (client->floating >= FLOATING_AUTO_ON);
+}
index c0f2192d559d6b6460752c8f935d4a4d7fcb8403..275f8f96bb08c931d09842656a91b6e46f848dbe 100644 (file)
@@ -23,6 +23,9 @@
 #include "layout.h"
 #include "i3.h"
 #include "xinerama.h"
+#include "client.h"
+#include "floating.h"
+#include "xcb.h"
 
 bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
         /* If this container is empty, we’re done */
@@ -57,10 +60,17 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
 
         int new_row = current_row,
             new_col = current_col;
-
         Container *container = CUR_CELL;
         Workspace *t_ws = c_ws;
 
+        /* Makes sure new_col and new_row are within bounds of the new workspace */
+        void check_colrow_boundaries() {
+                if (new_col >= t_ws->cols)
+                        new_col = (t_ws->cols - 1);
+                if (new_row >= t_ws->rows)
+                        new_row = (t_ws->rows - 1);
+        }
+
         /* There always is a container. If not, current_col or current_row is wrong */
         assert(container != NULL);
 
@@ -103,6 +113,21 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
                         t_ws = &(workspaces[screen->current_workspace]);
                         new_row = (direction == D_UP ? (t_ws->rows - 1) : 0);
                 }
+
+                check_colrow_boundaries();
+
+                LOG("new_col = %d, new_row = %d\n", new_col, new_row);
+                if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
+                        LOG("Cell empty, checking for colspanned client above...\n");
+                        for (int cols = 0; cols < new_col; cols += t_ws->table[cols][new_row]->colspan) {
+                                if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1)))
+                                        continue;
+
+                                new_col = cols;
+                                break;
+                        }
+                        LOG("Fixed it to new col %d\n", new_col);
+                }
         } else if (direction == D_LEFT || direction == D_RIGHT) {
                 if (direction == D_RIGHT && cell_exists(current_col+1, current_row))
                         new_col = current_col + t_ws->table[current_col][current_row]->colspan;
@@ -130,16 +155,27 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
                         t_ws = &(workspaces[screen->current_workspace]);
                         new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0);
                 }
+
+                check_colrow_boundaries();
+
+                LOG("new_col = %d, new_row = %d\n", new_col, new_row);
+                if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
+                        LOG("Cell empty, checking for rowspanned client above...\n");
+                        for (int rows = 0; rows < new_row; rows += t_ws->table[new_col][rows]->rowspan) {
+                                if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1)))
+                                        continue;
+
+                                new_row = rows;
+                                break;
+                        }
+                        LOG("Fixed it to new row %d\n", new_row);
+                }
         } else {
                 LOG("direction unhandled\n");
                 return;
         }
 
-        /* Bounds checking */
-        if (new_col >= t_ws->cols)
-                new_col = (t_ws->cols - 1);
-        if (new_row >= t_ws->rows)
-                new_row = (t_ws->rows - 1);
+        check_colrow_boundaries();
 
         if (t_ws->table[new_col][new_row]->currently_focused != NULL)
                 set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true);
@@ -237,10 +273,13 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
 
                         new = CUR_TABLE[current_col][++current_row];
                         break;
+                /* To make static analyzers happy: */
+                default:
+                        return;
         }
 
         /* Remove it from the old container and put it into the new one */
-        remove_client_from_container(conn, current_client, container);
+        client_remove_from_container(conn, current_client, container, true);
 
         if (new->currently_focused != NULL)
                 CIRCLEQ_INSERT_AFTER(&(new->clients), new->currently_focused, current_client, clients);
@@ -261,7 +300,8 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
         /* Fix colspan/rowspan if it’d overlap */
         fix_colrowspan(conn, workspace);
 
-        render_layout(conn);
+        render_workspace(conn, workspace->screen, workspace);
+        xcb_flush(conn);
 
         set_focus(conn, current_client, true);
 }
@@ -309,6 +349,9 @@ static void move_current_container(xcb_connection_t *conn, direction_t direction
 
                         new = CUR_TABLE[current_col][++current_row];
                         break;
+                /* To make static analyzers happy: */
+                default:
+                        return;
         }
 
         LOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row);
@@ -415,9 +458,64 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
                         container->rowspan++;
                         break;
                 }
+                /* To make static analyzers happy: */
+                default:
+                        return;
+        }
+
+        render_layout(conn);
+}
+
+static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *client, int workspace) {
+        /* t_ws (to workspace) is just a container pointer to the workspace we’re switching to */
+        Workspace *t_ws = &(workspaces[workspace-1]),
+                  *old_ws = client->workspace;
+
+        LOG("moving floating\n");
+
+        if (t_ws->screen == NULL) {
+                LOG("initializing new workspace, setting num to %d\n", workspace-1);
+                t_ws->screen = c_ws->screen;
+                /* Copy the dimensions from the virtual screen */
+               memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
+        } else {
+                /* Check if there is already a fullscreen client on the destination workspace and
+                 * stop moving if so. */
+                if (client->fullscreen && (t_ws->fullscreen_client != NULL)) {
+                        LOG("Not moving: Fullscreen client already existing on destination workspace.\n");
+                        return;
+                }
+        }
+
+        floating_assign_to_workspace(client, t_ws);
+
+        bool target_invisible = t_ws->screen->current_workspace != t_ws->num;
+
+        /* If we’re moving it to an invisible screen, we need to unmap it */
+        if (target_invisible) {
+                LOG("This workspace is not visible, unmapping\n");
+                xcb_unmap_window(conn, client->frame);
+        } else {
+                /* If this is not the case, we move the window to a workspace
+                 * which is on another screen, so we also need to adjust its
+                 * coordinates. */
+                LOG("before x = %d, y = %d\n", client->rect.x, client->rect.y);
+                uint32_t relative_x = client->rect.x - old_ws->rect.x,
+                         relative_y = client->rect.y - old_ws->rect.y;
+                LOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y);
+                client->rect.x = t_ws->rect.x + relative_x;
+                client->rect.y = t_ws->rect.y + relative_y;
+                LOG("after x = %d, y = %d\n", client->rect.x, client->rect.y);
+                reposition_client(conn, client);
+                xcb_flush(conn);
         }
 
+        LOG("done\n");
+
         render_layout(conn);
+
+        if (!target_invisible)
+                set_focus(conn, client, true);
 }
 
 /*
@@ -461,14 +559,14 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
 
         assert(to_container != NULL);
 
-        remove_client_from_container(conn, current_client, container);
+        client_remove_from_container(conn, current_client, container, true);
         if (container->workspace->fullscreen_client == current_client)
                 container->workspace->fullscreen_client = NULL;
 
+        /* TODO: insert it to the correct position */
         CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients);
+
         SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients);
-        if (current_client->fullscreen)
-                t_ws->fullscreen_client = current_client;
         LOG("Moved.\n");
 
         current_client->container = to_container;
@@ -476,16 +574,26 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
         container->currently_focused = to_focus;
         to_container->currently_focused = current_client;
 
+        bool target_invisible = (to_container->workspace->screen->current_workspace != to_container->workspace->num);
+
         /* If we’re moving it to an invisible screen, we need to unmap it */
-        if (to_container->workspace->screen->current_workspace != to_container->workspace->num) {
+        if (target_invisible) {
                 LOG("This workspace is not visible, unmapping\n");
                 xcb_unmap_window(conn, current_client->frame);
+        } else {
+                if (current_client->fullscreen) {
+                        LOG("Calling client_enter_fullscreen again\n");
+                        client_enter_fullscreen(conn, current_client);
+                }
         }
 
         /* delete all empty columns/rows */
         cleanup_table(conn, container->workspace);
 
         render_layout(conn);
+
+        if (!target_invisible)
+                set_focus(conn, current_client, true);
 }
 
 /*
@@ -539,22 +647,25 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
 
         /* Check if we need to change something or if we’re already there */
         if (c_ws->screen->current_workspace == (workspace-1)) {
-                if (CUR_CELL->currently_focused != NULL) {
-                        set_focus(conn, CUR_CELL->currently_focused, true);
+                Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
+                if (last_focused != SLIST_END(&(c_ws->focus_stack))) {
+                        set_focus(conn, last_focused, true);
                         if (need_warp) {
-                                warp_pointer_into(conn, CUR_CELL->currently_focused);
+                                client_warp_pointer_into(conn, last_focused);
                                 xcb_flush(conn);
                         }
                 }
+
                 return;
         }
 
         t_ws->screen->current_workspace = workspace-1;
+        Workspace *old_workspace = c_ws;
+        c_ws = &workspaces[workspace-1];
 
-        /* Unmap all clients of the current workspace */
-        unmap_workspace(conn, c_ws);
+        /* Unmap all clients of the old workspace */
+        unmap_workspace(conn, old_workspace);
 
-        c_ws = &workspaces[workspace-1];
         current_row = c_ws->current_row;
         current_col = c_ws->current_col;
         LOG("new current row = %d, current col = %d\n", current_row, current_col);
@@ -566,6 +677,11 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
                 CIRCLEQ_FOREACH(client, &(c_ws->table[cols][rows]->clients), clients)
                         xcb_map_window(conn, client->frame);
 
+        /* Map all floating clients */
+        if (!c_ws->floating_hidden)
+                TAILQ_FOREACH(client, &(c_ws->floating_clients), floating_clients)
+                        xcb_map_window(conn, client->frame);
+
         /* Map all stack windows, if any */
         struct Stack_Window *stack_win;
         SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
@@ -575,10 +691,11 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
         ignore_enter_notify_forall(conn, c_ws, false);
 
         /* Restore focus on the new workspace */
-        if (CUR_CELL->currently_focused != NULL) {
-                set_focus(conn, CUR_CELL->currently_focused, true);
+        Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
+        if (last_focused != SLIST_END(&(c_ws->focus_stack))) {
+                set_focus(conn, last_focused, true);
                 if (need_warp) {
-                        warp_pointer_into(conn, CUR_CELL->currently_focused);
+                        client_warp_pointer_into(conn, last_focused);
                         xcb_flush(conn);
                 }
         } else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
@@ -586,12 +703,161 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
         render_layout(conn);
 }
 
+/*
+ * Jumps to the given window class / title.
+ * Title is matched using strstr, that is, matches if it appears anywhere
+ * in the string. Regular expressions seem to be a bit overkill here. However,
+ * if we need them for something else somewhen, we may introduce them here, too.
+ *
+ */
+static void jump_to_window(xcb_connection_t *conn, const char *arguments) {
+        char *classtitle;
+        Client *client;
+
+        /* The first character is a quote, this was checked before */
+        classtitle = sstrdup(arguments+1);
+        /* The last character is a quote, we just set it to NULL */
+        classtitle[strlen(classtitle)-1] = '\0';
+
+        if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) {
+                free(classtitle);
+                LOG("No matching client found.\n");
+                return;
+        }
+
+        free(classtitle);
+        set_focus(conn, client, true);
+}
+
+/*
+ * Jump directly to the specified workspace, row and col.
+ * Great for reaching windows that you always keep in the same spot (hello irssi, I'm looking at you)
+ *
+ */
+static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
+        int ws, row, col;
+        int result;
+
+        result = sscanf(arguments, "%d %d %d", &ws, &col, &row);
+        LOG("Jump called with %d parameters (\"%s\")\n", result, arguments);
+
+        /* No match? Either no arguments were specified, or no numbers */
+        if (result < 1) {
+                LOG("At least one valid argument required\n");
+                return;
+        }
+
+        /* Move to the target workspace */
+        show_workspace(conn, ws);
+
+        if (result < 3)
+                return;
+
+        LOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows);
+
+        /* Move to row/col */
+        if (row >= c_ws->rows)
+                row = c_ws->rows - 1;
+        if (col >= c_ws->cols)
+                col = c_ws->cols - 1;
+
+        LOG("Jumping to col %d, row %d\n", col, row);
+        if (c_ws->table[col][row]->currently_focused != NULL)
+                set_focus(conn, c_ws->table[col][row]->currently_focused, true);
+}
+
+/*
+ * Travels the focus stack by the given number of times (or once, if no argument
+ * was specified). That is, selects the window you were in before you focused
+ * the current window.
+ *
+ * The special values 'floating' (select the next floating window), 'tiling'
+ * (select the next tiling window), 'ft' (if the current window is floating,
+ * select the next tiling window and vice-versa) are also valid
+ *
+ */
+static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
+        /* Start count at -1 to always skip the first element */
+        int times, count = -1;
+        Client *current;
+        bool floating_criteria;
+
+        /* Either it’s one of the special values… */
+        if (strcasecmp(arguments, "floating") == 0) {
+                floating_criteria = true;
+        } else if (strcasecmp(arguments, "tiling") == 0) {
+                floating_criteria = false;
+        } else if (strcasecmp(arguments, "ft") == 0) {
+                Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
+                if (last_focused == SLIST_END(&(c_ws->focus_stack))) {
+                        LOG("Cannot select the next floating/tiling client because there is no client at all\n");
+                        return;
+                }
+
+                floating_criteria = !client_is_floating(last_focused);
+        } else {
+                /* …or a number was specified */
+                if (sscanf(arguments, "%u", &times) != 1) {
+                        LOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments);
+                        times = 1;
+                }
+
+                SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) {
+                        if (++count < times) {
+                                LOG("Skipping\n");
+                                continue;
+                        }
+
+                        LOG("Focussing\n");
+                        set_focus(conn, current, true);
+                        break;
+                }
+                return;
+        }
+
+        /* Select the next client matching the criteria parsed above */
+        SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients)
+                if (client_is_floating(current) == floating_criteria) {
+                        set_focus(conn, current, true);
+                        break;
+                }
+}
+
+/*
+ * Goes through the list of arguments (for exec()) and checks if the given argument
+ * is present. If not, it copies the arguments (because we cannot realloc it) and
+ * appends the given argument.
+ *
+ */
+static char **append_argument(char **original, char *argument) {
+        int num_args;
+        for (num_args = 0; original[num_args] != NULL; num_args++) {
+                LOG("original argument: \"%s\"\n", original[num_args]);
+                /* If the argument is already present we return the original pointer */
+                if (strcmp(original[num_args], argument) == 0)
+                        return original;
+        }
+        /* Copy the original array */
+        char **result = smalloc((num_args+2) * sizeof(char*));
+        memcpy(result, original, num_args * sizeof(char*));
+        result[num_args] = argument;
+        result[num_args+1] = NULL;
+
+        return result;
+}
+
 /*
  * Parses a command, see file CMDMODE for more information
  *
  */
 void parse_command(xcb_connection_t *conn, const char *command) {
         LOG("--- parsing command \"%s\" ---\n", command);
+        /* Get the first client from focus stack because floating clients are not
+         * in any container, therefore CUR_CELL is not appropriate. */
+        Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
+        if (last_focused == SLIST_END(&(c_ws->focus_stack)))
+                last_focused = NULL;
+
         /* Hmm, just to be sure */
         if (command[0] == '\0')
                 return;
@@ -612,37 +878,66 @@ void parse_command(xcb_connection_t *conn, const char *command) {
         /* Is it <restart>? Then restart in place. */
         if (STARTS_WITH(command, "restart")) {
                 LOG("restarting \"%s\"...\n", start_argv[0]);
+                /* make sure -a is in the argument list or append it */
+                start_argv = append_argument(start_argv, "-a");
+
                 execvp(start_argv[0], start_argv);
                 /* not reached */
         }
 
         if (STARTS_WITH(command, "kill")) {
-                if (CUR_CELL->currently_focused == NULL) {
+                if (last_focused == NULL) {
                         LOG("There is no window to kill\n");
                         return;
                 }
 
                 LOG("Killing current window\n");
-                kill_window(conn, CUR_CELL->currently_focused);
+                client_kill(conn, last_focused);
+                return;
+        }
+
+        /* Is it a jump to a specified workspace, row, col? */
+        if (STARTS_WITH(command, "jump ")) {
+                const char *arguments = command + strlen("jump ");
+                if (arguments[0] == '"')
+                        jump_to_window(conn, arguments);
+                else jump_to_container(conn, arguments);
+                return;
+        }
+
+        /* Should we travel the focus stack? */
+        if (STARTS_WITH(command, "focus")) {
+                const char *arguments = command + strlen("focus ");
+                travel_focus_stack(conn, arguments);
                 return;
         }
 
         /* Is it 'f' for fullscreen? */
         if (command[0] == 'f') {
-                if (CUR_CELL->currently_focused == NULL)
+                if (last_focused == NULL)
                         return;
-                toggle_fullscreen(conn, CUR_CELL->currently_focused);
+                client_toggle_fullscreen(conn, last_focused);
                 return;
         }
 
         /* Is it just 's' for stacking or 'd' for default? */
         if ((command[0] == 's' || command[0] == 'd') && (command[1] == '\0')) {
+                if (last_focused == NULL || client_is_floating(last_focused)) {
+                        LOG("not switching, this is a floating client\n");
+                        return;
+                }
                 LOG("Switching mode for current container\n");
                 switch_layout_mode(conn, CUR_CELL, (command[0] == 's' ? MODE_STACK : MODE_DEFAULT));
                 return;
         }
 
-        enum { WITH_WINDOW, WITH_CONTAINER } with = WITH_WINDOW;
+        if (command[0] == 'H') {
+                LOG("Hiding all floating windows\n");
+                floating_toggle_hide(conn, c_ws);
+                return;
+        }
+
+        enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE } with = WITH_WINDOW;
 
         /* Is it a <with>? */
         if (command[0] == 'w') {
@@ -651,13 +946,44 @@ void parse_command(xcb_connection_t *conn, const char *command) {
                 if (command[0] == 'c') {
                         with = WITH_CONTAINER;
                         command++;
+                } else if (command[0] == 'w') {
+                        with = WITH_WORKSPACE;
+                        command++;
                 } else {
                         LOG("not yet implemented.\n");
                         return;
                 }
         }
 
-        /* It's a normal <cmd> */
+        /* Is it 't' for toggle tiling/floating? */
+        if (command[0] == 't') {
+                if (with == WITH_WORKSPACE) {
+                        c_ws->auto_float = !c_ws->auto_float;
+                        LOG("autofloat is now %d\n", c_ws->auto_float);
+                        return;
+                }
+                if (last_focused == NULL) {
+                        LOG("Cannot toggle tiling/floating: workspace empty\n");
+                        return;
+                }
+
+                toggle_floating_mode(conn, last_focused, false);
+                /* delete all empty columns/rows */
+                cleanup_table(conn, last_focused->workspace);
+
+                /* Fix colspan/rowspan if it’d overlap */
+                fix_colrowspan(conn, last_focused->workspace);
+
+                render_workspace(conn, last_focused->workspace->screen, last_focused->workspace);
+
+                /* Re-focus the client because cleanup_table sets the focus to the last
+                 * focused client inside a container only. */
+                set_focus(conn, last_focused, true);
+
+                return;
+        }
+
+        /* It’s a normal <cmd> */
         char *rest = NULL;
         enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS;
         direction_t direction;
@@ -668,7 +994,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
         }
 
         if (*rest == '\0') {
-                /* No rest? This was a tag number, not a times specification */
+                /* No rest? This was a workspace number, not a times specification */
                 show_workspace(conn, times);
                 return;
         }
@@ -686,7 +1012,20 @@ void parse_command(xcb_connection_t *conn, const char *command) {
         }
 
         if (*rest == '\0') {
-                move_current_window_to_workspace(conn, workspace);
+                if (last_focused != NULL && client_is_floating(last_focused))
+                        move_floating_window_to_workspace(conn, last_focused, workspace);
+                else move_current_window_to_workspace(conn, workspace);
+                return;
+        }
+
+        if (last_focused == NULL) {
+                LOG("Not performing (no window found)\n");
+                return;
+        }
+
+        if (client_is_floating(last_focused) &&
+            (action != ACTION_FOCUS && action != ACTION_MOVE)) {
+                LOG("Not performing (floating)\n");
                 return;
         }
 
@@ -704,18 +1043,32 @@ void parse_command(xcb_connection_t *conn, const char *command) {
                         LOG("unknown direction: %c\n", *rest);
                         return;
                 }
+                rest++;
 
-                if (action == ACTION_FOCUS)
+                if (action == ACTION_FOCUS) {
+                        if (client_is_floating(last_focused)) {
+                                floating_focus_direction(conn, last_focused, direction);
+                                continue;
+                        }
                         focus_thing(conn, direction, (with == WITH_WINDOW ? THING_WINDOW : THING_CONTAINER));
-                else if (action == ACTION_MOVE) {
+                        continue;
+                }
+
+                if (action == ACTION_MOVE) {
+                        if (client_is_floating(last_focused)) {
+                                floating_move(conn, last_focused, direction);
+                                continue;
+                        }
                         if (with == WITH_WINDOW)
                                 move_current_window(conn, direction);
                         else move_current_container(conn, direction);
+                        continue;
                 }
-                else if (action == ACTION_SNAP)
-                        snap_current_container(conn, direction);
 
-                rest++;
+                if (action == ACTION_SNAP) {
+                        snap_current_container(conn, direction);
+                        continue;
+                }
         }
 
         LOG("--- done ---\n");
index 73d390215fef5c3d88546e4a523e0d4cdb17fdc0..d55c2a75542a93f393c398d19c3cf60bbe4624c4 100644 (file)
@@ -17,6 +17,7 @@
 #include "i3.h"
 #include "util.h"
 #include "config.h"
+#include "xcb.h"
 
 Config config;
 
@@ -25,14 +26,36 @@ Config config;
  *
  */
 static char *glob_path(const char *path) {
-       static glob_t globbuf;
-       if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
-               die("glob() failed");
-       char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
-       globfree(&globbuf);
-       return result;
+        static glob_t globbuf;
+        if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
+                die("glob() failed");
+        char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
+        globfree(&globbuf);
+        return result;
 }
 
+/*
+ * This function does a very simple replacement of each instance of key with value.
+ *
+ */
+static void replace_variable(char *buffer, const char *key, const char *value) {
+        char *pos;
+        /* To prevent endless recursions when the user makes an error configuring,
+         * we stop after 100 replacements. That should be vastly more than enough. */
+        int c = 0;
+        LOG("Replacing %s with %s\n", key, value);
+        while ((pos = strcasestr(buffer, key)) != NULL && c++ < 100) {
+                LOG("replacing variable %s in \"%s\" with \"%s\"\n", key, buffer, value);
+                char *rest = pos + strlen(key);
+                *pos = '\0';
+                char *replaced;
+                asprintf(&replaced, "%s%s%s", buffer, value, rest);
+                /* Hm, this is a bit ugly, but sizeof(buffer) = 4, as it’s just a pointer.
+                 * So we need to hard-code the dimensions here. */
+                strncpy(buffer, replaced, 1026);
+                free(replaced);
+        }
+}
 
 /*
  * Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
@@ -41,7 +64,9 @@ static char *glob_path(const char *path) {
  * configuration file.
  *
  */
-void load_configuration(const char *override_configpath) {
+void load_configuration(xcb_connection_t *conn, const char *override_configpath) {
+        SLIST_HEAD(variables_head, Variable) variables;
+
 #define OPTION_STRING(name) \
         if (strcasecmp(key, #name) == 0) { \
                 config.name = sstrdup(value); \
@@ -52,9 +77,51 @@ void load_configuration(const char *override_configpath) {
         if (config.name == NULL) \
                 die("You did not specify required configuration option " #name "\n");
 
+#define OPTION_COLORTRIPLE(opt, name) \
+        if (strcasecmp(key, opt) == 0) { \
+                char border[8], background[8], text[8]; \
+                memset(border, 0, sizeof(border)); \
+                memset(background, 0, sizeof(background)); \
+                memset(text, 0, sizeof(text)); \
+                border[0] = background[0] = text[0] = '#'; \
+                if (sscanf(value, "#%06[0-9a-fA-F] #%06[0-9a-fA-F] #%06[0-9a-fA-F]", \
+                    border + 1, background + 1, text + 1) != 3 || \
+                    strlen(border) != 7 || \
+                    strlen(background) != 7 || \
+                    strlen(text) != 7) \
+                        die("invalid color code line: %s\n", value); \
+                config.name.border = get_colorpixel(conn, border); \
+                config.name.background = get_colorpixel(conn, background); \
+                config.name.text = get_colorpixel(conn, text); \
+                continue; \
+        }
+
         /* Clear the old config or initialize the data structure */
         memset(&config, 0, sizeof(config));
 
+        SLIST_INIT(&variables);
+
+        /* Initialize default colors */
+        config.client.focused.border = get_colorpixel(conn, "#4c7899");
+        config.client.focused.background = get_colorpixel(conn, "#285577");
+        config.client.focused.text = get_colorpixel(conn, "#ffffff");
+
+        config.client.focused_inactive.border = get_colorpixel(conn, "#4c7899");
+        config.client.focused_inactive.background = get_colorpixel(conn, "#555555");
+        config.client.focused_inactive.text = get_colorpixel(conn, "#ffffff");
+
+        config.client.unfocused.border = get_colorpixel(conn, "#333333");
+        config.client.unfocused.background = get_colorpixel(conn, "#222222");
+        config.client.unfocused.text = get_colorpixel(conn, "#888888");
+
+        config.bar.focused.border = get_colorpixel(conn, "#4c7899");
+        config.bar.focused.background = get_colorpixel(conn, "#285577");
+        config.bar.focused.text = get_colorpixel(conn, "#ffffff");
+
+        config.bar.unfocused.border = get_colorpixel(conn, "#333333");
+        config.bar.unfocused.background = get_colorpixel(conn, "#222222");
+        config.bar.unfocused.text = get_colorpixel(conn, "#888888");
+
         FILE *handle;
         if (override_configpath != NULL) {
                 if ((handle = fopen(override_configpath, "r")) == NULL)
@@ -77,6 +144,14 @@ void load_configuration(const char *override_configpath) {
                         die("Could not read configuration file\n");
                 }
 
+                if (config.terminal != NULL)
+                        replace_variable(buffer, "$terminal", config.terminal);
+
+                /* Replace all custom variables */
+                struct Variable *current;
+                SLIST_FOREACH(current, &variables, variables)
+                        replace_variable(buffer, current->key, current->value);
+
                 /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
                 if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
                     key[0] == '#' || strlen(key) < 3)
@@ -85,6 +160,22 @@ void load_configuration(const char *override_configpath) {
                 OPTION_STRING(terminal);
                 OPTION_STRING(font);
 
+                /* Colors */
+                OPTION_COLORTRIPLE("client.focused", client.focused);
+                OPTION_COLORTRIPLE("client.focused_inactive", client.focused_inactive);
+                OPTION_COLORTRIPLE("client.unfocused", client.unfocused);
+                OPTION_COLORTRIPLE("bar.focused", bar.focused);
+                OPTION_COLORTRIPLE("bar.unfocused", bar.unfocused);
+
+                /* exec-lines (autostart) */
+                if (strcasecmp(key, "exec") == 0) {
+                        struct Autostart *new = smalloc(sizeof(struct Autostart));
+                        new->command = sstrdup(value);
+                        TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
+                        continue;
+                }
+
+                /* key bindings */
                 if (strcasecmp(key, "bind") == 0) {
                         #define CHECK_MODIFIER(name) \
                                 if (strncasecmp(walk, #name, strlen(#name)) == 0) { \
@@ -124,13 +215,105 @@ void load_configuration(const char *override_configpath) {
                         continue;
                 }
 
-                fprintf(stderr, "Unknown configfile option: %s\n", key);
-                exit(1);
+                if (strcasecmp(key, "floating_modifier") == 0) {
+                        char *walk = value;
+                        uint32_t modifiers = 0;
+
+                        while (*walk != '\0') {
+                                /* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */
+                                CHECK_MODIFIER(SHIFT);
+                                CHECK_MODIFIER(CONTROL);
+                                CHECK_MODIFIER(MODE_SWITCH);
+                                CHECK_MODIFIER(MOD1);
+                                CHECK_MODIFIER(MOD2);
+                                CHECK_MODIFIER(MOD3);
+                                CHECK_MODIFIER(MOD4);
+                                CHECK_MODIFIER(MOD5);
+
+                                /* No modifier found? Then we’re done with this step */
+                                break;
+                        }
+
+                        LOG("Floating modifiers = %d\n", modifiers);
+                        config.floating_modifier = modifiers;
+                        continue;
+                }
+
+                /* assign window class[/window title] → workspace */
+                if (strcasecmp(key, "assign") == 0) {
+                        LOG("assign: \"%s\"\n", value);
+                        char *class_title = sstrdup(value);
+                        char *target;
+
+                        /* If the window class/title is quoted we skip quotes */
+                        if (class_title[0] == '"') {
+                                class_title++;
+                                char *end = strchr(class_title, '"');
+                                if (end == NULL)
+                                        die("Malformed assignment, couldn't find terminating quote\n");
+                                *end = '\0';
+                        } else {
+                                /* If it is not quoted, we terminate it at the first space */
+                                char *end = strchr(class_title, ' ');
+                                if (end == NULL)
+                                        die("Malformed assignment, couldn't find terminating space\n");
+                                *end = '\0';
+                        }
+
+                        /* The target is the last argument separated by a space */
+                        if ((target = strrchr(value, ' ')) == NULL)
+                                die("Malformed assignment, couldn't find target\n");
+                        target++;
+
+                        if (*target != '~' && (atoi(target) < 1 || atoi(target) > 10))
+                                die("Malformed assignment, invalid workspace number\n");
+
+                        LOG("assignment parsed: \"%s\" to \"%s\"\n", class_title, target);
+
+                        struct Assignment *new = scalloc(sizeof(struct Assignment));
+                        new->windowclass_title = class_title;
+                        if (*target == '~')
+                                new->floating = true;
+                        else new->workspace = atoi(target);
+                        TAILQ_INSERT_TAIL(&assignments, new, assignments);
+                        continue;
+                }
+
+                /* set a custom variable */
+                if (strcasecmp(key, "set") == 0) {
+                        if (value[0] != '$')
+                                die("Malformed variable assignment, name has to start with $\n");
+
+                        /* get key/value for this variable */
+                        char *v_key = value, *v_value;
+                        if ((v_value = strstr(value, " ")) == NULL)
+                                die("Malformed variable assignment, need a value\n");
+
+                        *(v_value++) = '\0';
+
+                        struct Variable *new = scalloc(sizeof(struct Variable));
+                        new->key = sstrdup(v_key);
+                        new->value = sstrdup(v_value);
+                        SLIST_INSERT_HEAD(&variables, new, variables);
+                        LOG("Got new variable %s = %s\n", v_key, v_value);
+                        continue;
+                }
+
+                die("Unknown configfile option: %s\n", key);
         }
         fclose(handle);
 
         REQUIRED_OPTION(terminal);
         REQUIRED_OPTION(font);
 
+
+        while (!SLIST_EMPTY(&variables)) {
+                struct Variable *v = SLIST_FIRST(&variables);
+                SLIST_REMOVE_HEAD(&variables, variables);
+                free(v->key);
+                free(v->value);
+                free(v);
+        }
         return;
 }
diff --git a/src/floating.c b/src/floating.c
new file mode 100644 (file)
index 0000000..70112b5
--- /dev/null
@@ -0,0 +1,433 @@
+/*
+ * vim:ts=8:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * © 2009 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ * src/floating.c: contains all functions for handling floating clients
+ *
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_event.h>
+
+#include "i3.h"
+#include "config.h"
+#include "data.h"
+#include "util.h"
+#include "xcb.h"
+#include "debug.h"
+#include "layout.h"
+#include "client.h"
+#include "floating.h"
+
+/*
+ * Toggles floating mode for the given client.
+ * Correctly takes care of the position/size (separately stored for tiling/floating mode)
+ * and repositions/resizes/redecorates the client.
+ *
+ * If the automatic flag is set to true, this was an automatic update by a change of the
+ * window class from the application which can be overwritten by the user.
+ *
+ */
+void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic) {
+        Container *con = client->container;
+        i3Font *font = load_font(conn, config.font);
+
+        if (con == NULL) {
+                LOG("This client is already in floating (container == NULL), re-inserting\n");
+                Client *next_tiling;
+                SLIST_FOREACH(next_tiling, &(client->workspace->focus_stack), focus_clients)
+                        if (!client_is_floating(next_tiling))
+                                break;
+                /* If there are no tiling clients on this workspace, there can only be one
+                 * container: the first one */
+                if (next_tiling == TAILQ_END(&(client->workspace->focus_stack)))
+                        con = client->workspace->table[0][0];
+                else con = next_tiling->container;
+
+                /* Remove the client from the list of floating clients */
+                TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
+
+                LOG("destination container = %p\n", con);
+                Client *old_focused = con->currently_focused;
+                /* Preserve position/size */
+                memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect));
+
+                client->floating = FLOATING_USER_OFF;
+                client->container = con;
+
+                if (old_focused != NULL && !old_focused->dock)
+                        CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients);
+                else CIRCLEQ_INSERT_TAIL(&(con->clients), client, clients);
+
+                LOG("Re-inserted the client into the matrix.\n");
+                con->currently_focused = client;
+
+                client_set_below_floating(conn, client);
+
+                render_container(conn, con);
+                xcb_flush(conn);
+
+                return;
+        }
+
+        LOG("Entering floating for client %08x\n", client->child);
+
+        /* Remove the client of its container */
+        client_remove_from_container(conn, client, con, false);
+        client->container = NULL;
+
+        /* Add the client to the list of floating clients for its workspace */
+        TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
+
+        if (con->currently_focused == client) {
+                LOG("Need to re-adjust currently_focused\n");
+                /* Get the next client in the focus stack for this particular container */
+                con->currently_focused = get_last_focused_client(conn, con, NULL);
+        }
+
+        if (automatic)
+                client->floating = FLOATING_AUTO_ON;
+        else client->floating = FLOATING_USER_ON;
+
+        /* Initialize the floating position from the position in tiling mode, if this
+         * client never was floating (x == -1) */
+        if (client->floating_rect.x == -1) {
+                /* Copy over the position */
+                client->floating_rect.x = client->rect.x;
+                client->floating_rect.y = client->rect.y;
+
+                /* Copy size the other direction */
+                client->child_rect.width = client->floating_rect.width;
+                client->child_rect.height = client->floating_rect.height;
+
+                client->rect.width = client->child_rect.width + 2 + 2;
+                client->rect.height = client->child_rect.height + (font->height + 2 + 2) + 2;
+
+                LOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
+                                client->floating_rect.width, client->floating_rect.height);
+        } else {
+                /* If the client was already in floating before we restore the old position / size */
+                LOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
+                        client->floating_rect.width, client->floating_rect.height);
+                memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect));
+        }
+
+        /* Raise the client */
+        xcb_raise_window(conn, client->frame);
+        reposition_client(conn, client);
+        resize_client(conn, client);
+        /* redecorate_window flushes */
+        redecorate_window(conn, client);
+
+        /* Re-render the tiling layout of this container */
+        render_container(conn, con);
+        xcb_flush(conn);
+}
+
+/*
+ * Removes the floating client from its workspace and attaches it to the new workspace.
+ * This is centralized here because it may happen if you move it via keyboard and
+ * if you move it using your mouse.
+ *
+ */
+void floating_assign_to_workspace(Client *client, Workspace *new_workspace) {
+        /* Remove from focus stack and list of floating clients */
+        SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
+        TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
+
+        if (client->workspace->fullscreen_client == client)
+                client->workspace->fullscreen_client = NULL;
+
+        /* Insert into destination focus stack and list of floating clients */
+        client->workspace = new_workspace;
+        SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
+        TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
+        if (client->fullscreen)
+                client->workspace->fullscreen_client = client;
+
+}
+
+/*
+ * Called whenever the user clicks on a border (not the titlebar!) of a floating window.
+ * Determines on which border the user clicked and launches the drag_pointer function
+ * with the resize_callback.
+ *
+ */
+int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
+
+        LOG("floating border click\n");
+
+        border_t border;
+
+        void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
+                switch (border) {
+                        case BORDER_RIGHT: {
+                                int new_width = old_rect->width + (new_x - event->root_x);
+                                if ((new_width < 0) ||
+                                    (new_width < 50 && client->rect.width >= new_width))
+                                        return;
+                                client->rect.width = new_width;
+                                break;
+                        }
+
+                        case BORDER_BOTTOM: {
+                                int new_height = old_rect->height + (new_y - event->root_y);
+                                if ((new_height < 0) ||
+                                    (new_height < 20 && client->rect.height >= new_height))
+                                        return;
+                                client->rect.height = old_rect->height + (new_y - event->root_y);
+                                break;
+                        }
+
+                        case BORDER_TOP: {
+                                int new_height = old_rect->height + (event->root_y - new_y);
+                                if ((new_height < 0) ||
+                                    (new_height < 20 && client->rect.height >= new_height))
+                                        return;
+
+                                client->rect.y = old_rect->y + (new_y - event->root_y);
+                                client->rect.height = new_height;
+                                break;
+                        }
+
+                        case BORDER_LEFT: {
+                                int new_width = old_rect->width + (event->root_x - new_x);
+                                if ((new_width < 0) ||
+                                    (new_width < 50 && client->rect.width >= new_width))
+                                        return;
+                                client->rect.x = old_rect->x + (new_x - event->root_x);
+                                client->rect.width = new_width;
+                                break;
+                        }
+                }
+
+                /* Push the new position/size to X11 */
+                reposition_client(conn, client);
+                resize_client(conn, client);
+                xcb_flush(conn);
+        }
+
+        if (event->event_y < 2)
+                border = BORDER_TOP;
+        else if (event->event_y >= (client->rect.height - 2))
+                border = BORDER_BOTTOM;
+        else if (event->event_x <= 2)
+                border = BORDER_LEFT;
+        else if (event->event_x >= (client->rect.width - 2))
+                border = BORDER_RIGHT;
+        else {
+                LOG("Not on any border, not doing anything.\n");
+                return 1;
+        }
+
+        LOG("border = %d\n", border);
+
+        drag_pointer(conn, client, event, XCB_NONE, border, resize_callback);
+
+        return 1;
+}
+
+
+/*
+ * Called when the user clicked on the titlebar of a floating window.
+ * Calls the drag_pointer function with the drag_window callback
+ *
+ */
+void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
+        LOG("floating_drag_window\n");
+
+        void drag_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
+                /* Reposition the client correctly while moving */
+                client->rect.x = old_rect->x + (new_x - event->root_x);
+                client->rect.y = old_rect->y + (new_y - event->root_y);
+                reposition_client(conn, client);
+                /* Because reposition_client does not send a faked configure event (only resize does),
+                 * we need to initiate that on our own */
+                fake_absolute_configure_notify(conn, client);
+                /* fake_absolute_configure_notify flushes */
+        }
+
+
+        drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback);
+}
+
+/*
+ * This function grabs your pointer and lets you drag stuff around (borders).
+ * Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
+ * and the given callback will be called with the parameters specified (client,
+ * border on which the click originally was), the original rect of the client,
+ * the event and the new coordinates (x, y).
+ *
+ */
+void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
+                  xcb_window_t confine_to, border_t border, callback_t callback) {
+        xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
+        uint32_t new_x, new_y;
+        Rect old_rect;
+        if (client != NULL)
+                memcpy(&old_rect, &(client->rect), sizeof(Rect));
+
+        /* Grab the pointer */
+        /* TODO: returncode */
+        xcb_grab_pointer(conn, 
+                        false,               /* get all pointer events specified by the following mask */
+                        root,                /* grab the root window */
+                        XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */
+                        XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
+                        XCB_GRAB_MODE_ASYNC, /* keyboard mode */
+                        confine_to,          /* confine_to = in which window should the cursor stay */
+                        XCB_NONE,            /* don’t display a special cursor */
+                        XCB_CURRENT_TIME);
+
+        /* Go into our own event loop */
+        xcb_flush(conn);
+
+        xcb_generic_event_t *inside_event, *last_motion_notify = NULL;
+        /* I’ve always wanted to have my own eventhandler… */
+        while ((inside_event = xcb_wait_for_event(conn))) {
+                /* We now handle all events we can get using xcb_poll_for_event */
+                do {
+                        /* Same as get_event_handler in xcb */
+                        int nr = inside_event->response_type;
+                        if (nr == 0) {
+                                /* An error occured */
+                                handle_event(NULL, conn, inside_event);
+                                free(inside_event);
+                                continue;
+                        }
+                        assert(nr < 256);
+                        nr &= XCB_EVENT_RESPONSE_TYPE_MASK;
+                        assert(nr >= 2);
+
+                        switch (nr) {
+                                case XCB_BUTTON_RELEASE:
+                                        goto done;
+
+                                case XCB_MOTION_NOTIFY:
+                                        /* motion_notify events are saved for later */
+                                        FREE(last_motion_notify);
+                                        last_motion_notify = inside_event;
+
+                                        break;
+                                default:
+                                        LOG("Passing to original handler\n");
+                                        /* Use original handler */
+                                        xcb_event_handle(&evenths, inside_event);
+                                        break;
+                        }
+                        if (last_motion_notify != inside_event)
+                                free(inside_event);
+                } while ((inside_event = xcb_poll_for_event(conn)) != NULL);
+
+                if (last_motion_notify == NULL)
+                        continue;
+
+                new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x;
+                new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y;
+
+                callback(&old_rect, new_x, new_y);
+                FREE(last_motion_notify);
+        }
+done:
+        xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
+        xcb_flush(conn);
+}
+
+/*
+ * Changes focus in the given direction for floating clients.
+ *
+ * Changing to the left/right means going to the previous/next floating client,
+ * changing to top/bottom means cycling through the Z-index.
+ *
+ */
+void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
+        LOG("floating focus\n");
+
+        if (direction == D_LEFT || direction == D_RIGHT) {
+                /* Go to the next/previous floating client */
+                Client *client;
+
+                while ((client = (direction == D_LEFT ? TAILQ_PREV(currently_focused, floating_clients_head, floating_clients) :
+                                                        TAILQ_NEXT(currently_focused, floating_clients))) !=
+                       TAILQ_END(&(currently_focused->workspace->floating_clients))) {
+                        if (!client->floating)
+                                continue;
+                        set_focus(conn, client, true);
+                        return;
+                }
+        }
+}
+
+/*
+ * Moves the client 10px to the specified direction.
+ *
+ */
+void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
+        LOG("floating move\n");
+
+        switch (direction) {
+                case D_LEFT:
+                        if (currently_focused->rect.x < 10)
+                                return;
+                        currently_focused->rect.x -= 10;
+                        break;
+                case D_RIGHT:
+                        currently_focused->rect.x += 10;
+                        break;
+                case D_UP:
+                        if (currently_focused->rect.y < 10)
+                                return;
+                        currently_focused->rect.y -= 10;
+                        break;
+                case D_DOWN:
+                        currently_focused->rect.y += 10;
+                        break;
+                /* to make static analyzers happy */
+                default:
+                        break;
+        }
+
+        reposition_client(conn, currently_focused);
+
+        /* Because reposition_client does not send a faked configure event (only resize does),
+         * we need to initiate that on our own */
+        fake_absolute_configure_notify(conn, currently_focused);
+        /* fake_absolute_configure_notify flushes */
+}
+
+/*
+ * Hides all floating clients (or show them if they are currently hidden) on
+ * the specified workspace.
+ *
+ */
+void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) {
+        Client *client;
+
+        workspace->floating_hidden = !workspace->floating_hidden;
+        LOG("floating_hidden is now: %d\n", workspace->floating_hidden);
+        TAILQ_FOREACH(client, &(workspace->floating_clients), floating_clients) {
+                if (workspace->floating_hidden)
+                        xcb_unmap_window(conn, client->frame);
+                else xcb_map_window(conn, client->frame);
+        }
+
+        /* If we just unmapped all floating windows we should ensure that the focus
+         * is set correctly, that ist, to the first non-floating client in stack */
+        if (workspace->floating_hidden)
+                SLIST_FOREACH(client, &(workspace->focus_stack), focus_clients) {
+                        if (client_is_floating(client))
+                                continue;
+                        set_focus(conn, client, true);
+                        return;
+                }
+
+        xcb_flush(conn);
+}
index 4c40765f273e0d7a0b4223e36d20fd0ef21c0354..015e716a1581430a9e3bba31ac3b946c3a082d18 100644 (file)
@@ -32,6 +32,9 @@
 #include "config.h"
 #include "queue.h"
 #include "resize.h"
+#include "client.h"
+#include "manage.h"
+#include "floating.h"
 
 /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it,
    since it’d trigger an infinite loop of switching between the different windows when
@@ -149,7 +152,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
                 return 1;
         }
         /* Some events are not interesting, because they were not generated actively by the
-           user, but be reconfiguration of windows */
+           user, but by reconfiguration of windows */
         if (event_is_ignored(event->sequence))
                 return 1;
 
@@ -196,6 +199,14 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
                 return 1;
         }
 
+        if (client->workspace != c_ws && client->workspace->screen == c_ws->screen) {
+                /* This can happen when a client gets assigned to a different workspace than
+                 * the current one (see src/mainx.c:reparent_window). Shortly after it was created,
+                 * an enter_notify will follow. */
+                LOG("enter_notify for a client on a different workspace but the same screen, ignoring\n");
+                return 1;
+        }
+
         set_focus(conn, client, false);
 
         return 1;
@@ -283,6 +294,7 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e
 
 int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) {
         LOG("button press!\n");
+        LOG("state = %d\n", event->state);
         /* This was either a focus for a client’s parent (= titlebar)… */
         Client *client = table_get(&by_child, event->event);
         bool border_click = false;
@@ -290,6 +302,21 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
                 client = table_get(&by_parent, event->event);
                 border_click = true;
         }
+        /* See if this was a click with the configured modifier. If so, we need
+         * to move around the client if it was floating. if not, we just process
+         * as usual. */
+        if (config.floating_modifier != 0 &&
+            (event->state & config.floating_modifier) != 0) {
+                if (client == NULL) {
+                        LOG("Not handling, Mod1 was pressed and no client found\n");
+                        return 1;
+                }
+                if (client_is_floating(client)) {
+                        floating_drag_window(conn, client, event);
+                        return 1;
+                }
+        }
+
         if (client == NULL) {
                 /* The client was neither on a client’s titlebar nor on a client itself, maybe on a stack_window? */
                 if (button_press_stackwin(conn, event))
@@ -312,7 +339,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
         Container *con = client->container;
         int first, second;
 
-        if (con == NULL) {
+        if (client->dock) {
                 LOG("dock. done.\n");
                 xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
                 xcb_flush(conn);
@@ -324,6 +351,9 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
         if (!border_click) {
                 LOG("client. done.\n");
                 xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
+                /* Floating clients should be raised on click */
+                if (client_is_floating(client))
+                        xcb_raise_window(conn, client->frame);
                 xcb_flush(conn);
                 return 1;
         }
@@ -332,9 +362,21 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
         i3Font *font = load_font(conn, config.font);
         if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) {
                 LOG("click on titlebar\n");
+
+                /* Floating clients can be dragged by grabbing their titlebar */
+                if (client_is_floating(client)) {
+                        /* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */
+                        xcb_raise_window(conn, client->frame);
+                        xcb_flush(conn);
+
+                        floating_drag_window(conn, client, event);
+                }
                 return 1;
         }
 
+        if (client_is_floating(client))
+                return floating_border_click(conn, client, event);
+
         if (event->event_y < 2) {
                 /* This was a press on the top border */
                 if (con->row == 0)
@@ -412,8 +454,61 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
         Client *client = table_get(&by_child, event->window);
         if (client == NULL) {
                 LOG("This client is not mapped, so we don't care and just tell the client that he will get its size\n");
-                Rect rect = {event->x, event->y, event->width, event->height};
-                fake_configure_notify(conn, rect, event->window);
+                uint32_t mask = 0;
+                uint32_t values[7];
+                int c = 0;
+#define COPY_MASK_MEMBER(mask_member, event_member) do { \
+                if (event->value_mask & mask_member) { \
+                        mask |= mask_member; \
+                        values[c++] = event->event_member; \
+                } \
+} while (0)
+
+                COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_X, x);
+                COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_Y, y);
+                COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_WIDTH, width);
+                COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_HEIGHT, height);
+                COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_BORDER_WIDTH, border_width);
+                COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_SIBLING, sibling);
+                COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_STACK_MODE, stack_mode);
+
+                xcb_configure_window(conn, event->window, mask, values);
+                xcb_flush(conn);
+
+                return 1;
+        }
+
+        /* Floating clients can be reconfigured */
+        if (client_is_floating(client)) {
+                i3Font *font = load_font(conn, config.font);
+
+                if (event->value_mask & XCB_CONFIG_WINDOW_X)
+                        client->rect.x = event->x;
+                if (event->value_mask & XCB_CONFIG_WINDOW_Y)
+                        client->rect.y = event->y;
+                if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH)
+                        client->rect.width = event->width + 2 + 2;
+                if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)
+                        client->rect.height = event->height + (font->height + 2 + 2) + 2;
+
+                LOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n",
+                    client->rect.x, client->rect.y, client->rect.width, client->rect.height);
+
+                /* Push the new position/size to X11 */
+                reposition_client(conn, client);
+                resize_client(conn, client);
+                xcb_flush(conn);
+
+                return 1;
+        }
+
+        if (client->fullscreen) {
+                LOG("Client is in fullscreen mode\n");
+
+                Rect child_rect = client->container->workspace->rect;
+                child_rect.x = child_rect.y = 0;
+                fake_configure_notify(conn, child_rect, client->child);
+
                 return 1;
         }
 
@@ -495,18 +590,16 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
 
         client = table_remove(&by_child, event->window);
 
-        if (client->name != NULL)
-                free(client->name);
+        /* If this was the fullscreen client, we need to unset it */
+        if (client->fullscreen)
+                client->workspace->fullscreen_client = NULL;
 
+        /* Clients without a container are either floating or dock windows */
         if (client->container != NULL) {
                 Container *con = client->container;
 
-                /* If this was the fullscreen client, we need to unset it */
-                if (client->fullscreen)
-                        con->workspace->fullscreen_client = NULL;
-
                 /* Remove the client from the list of clients */
-                remove_client_from_container(conn, client, con);
+                client_remove_from_container(conn, client, con, true);
 
                 /* Set focus to the last focused client in this container */
                 con->currently_focused = get_last_focused_client(conn, con, NULL);
@@ -514,6 +607,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
                 /* Only if this is the active container, we need to really change focus */
                 if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen))
                         set_focus(conn, con->currently_focused, true);
+        } else if (client_is_floating(client)) {
+                LOG("Removing from floating clients\n");
+                TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
+                SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
         }
 
         if (client->dock) {
@@ -528,32 +625,41 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
         table_remove(&by_parent, client->frame);
 
         if (client->container != NULL) {
-                cleanup_table(conn, client->container->workspace);
-                fix_colrowspan(conn, client->container->workspace);
+                Workspace *workspace = client->container->workspace;
+                cleanup_table(conn, workspace);
+                fix_colrowspan(conn, workspace);
         }
 
         /* Let’s see how many clients there are left on the workspace to delete it if it’s empty */
-        bool workspace_empty = true;
-        FOR_TABLE(client->workspace)
-                if (!CIRCLEQ_EMPTY(&(client->workspace->table[cols][rows]->clients))) {
-                        workspace_empty = false;
-                        break;
-                }
+        bool workspace_empty = SLIST_EMPTY(&(client->workspace->focus_stack));
+        bool workspace_active = false;
+        Client *to_focus = (!workspace_empty ? SLIST_FIRST(&(client->workspace->focus_stack)) : NULL);
 
+        /* If this workspace is currently active, we don’t delete it */
         i3Screen *screen;
         TAILQ_FOREACH(screen, virtual_screens, screens)
                 if (screen->current_workspace == client->workspace->num) {
+                        workspace_active = true;
                         workspace_empty = false;
                         break;
                 }
 
-        if (workspace_empty)
+        if (workspace_empty) {
+                LOG("setting ws to NULL for workspace %d (%p)\n", client->workspace->num,
+                                client->workspace);
                 client->workspace->screen = NULL;
+        }
 
+        FREE(client->window_class);
+        FREE(client->name);
         free(client);
 
         render_layout(conn);
 
+        /* Ensure the focus is set to the next client in the focus stack */
+        if (workspace_active && to_focus != NULL)
+                set_focus(conn, to_focus, true);
+
         return 1;
 }
 
@@ -598,14 +704,13 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
         client->name_len = new_len;
         client->uses_net_wm_name = true;
 
-        if (old_name != NULL)
-                free(old_name);
+        FREE(old_name);
 
         /* If the client is a dock window, we don’t need to render anything */
         if (client->dock)
                 return 1;
 
-        if (client->container->mode == MODE_STACK)
+        if (client->container != NULL && client->container->mode == MODE_STACK)
                 render_container(conn, client->container);
         else decorate_window(conn, client, client->frame, client->titlegc, 0);
         xcb_flush(conn);
@@ -673,7 +778,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
         if (client->dock)
                 return 1;
 
-        if (client->container->mode == MODE_STACK)
+        if (client->container != NULL && client->container->mode == MODE_STACK)
                 render_container(conn, client->container);
         else decorate_window(conn, client, client->frame, client->titlegc, 0);
         xcb_flush(conn);
@@ -681,6 +786,46 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
         return 1;
 }
 
+/*
+ * Updates the client’s WM_CLASS property
+ *
+ */
+int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
+                             xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
+        LOG("window class changed\n");
+        if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
+                LOG("prop == NULL\n");
+                return 1;
+        }
+        Client *client = table_get(&by_child, window);
+        if (client == NULL)
+                return 1;
+        char *new_class;
+        if (asprintf(&new_class, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) {
+                perror("Could not get window class");
+                LOG("Could not get window class\n");
+                return 1;
+        }
+
+        LOG("changed to %s\n", new_class);
+        char *old_class = client->window_class;
+        client->window_class = new_class;
+        FREE(old_class);
+
+        if (!client->initialized) {
+                LOG("Client is not yet initialized, not putting it to floating\n");
+                return 1;
+        }
+
+        if (strcmp(new_class, "tools") == 0 || strcmp(new_class, "Dialog") == 0) {
+                LOG("tool/dialog window, should we put it floating?\n");
+                if (client->floating == FLOATING_AUTO_OFF)
+                        toggle_floating_mode(conn, client, true);
+        }
+
+        return 1;
+}
+
 /*
  * Expose event means we should redraw our windows (= title bar)
  *
@@ -717,15 +862,15 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
                 return 1;
         }
 
-        if (client->container->mode != MODE_STACK)
+        if (client->container == NULL || client->container->mode != MODE_STACK)
                 decorate_window(conn, client, client->frame, client->titlegc, 0);
         else {
                 uint32_t background_color;
                 /* Distinguish if the window is currently focused… */
                 if (CUR_CELL->currently_focused == client)
-                        background_color = get_colorpixel(conn, "#285577");
+                        background_color = config.client.focused.background;
                 /* …or if it is the focused window in a not focused container */
-                else background_color = get_colorpixel(conn, "#555555");
+                else background_color = config.client.focused_inactive.background;
 
                 /* Set foreground color to current focused color, line width to 2 */
                 uint32_t values[] = {background_color, 2};
@@ -771,7 +916,7 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message
                     (!client->fullscreen &&
                      (event->data.data32[0] == _NET_WM_STATE_ADD ||
                       event->data.data32[0] == _NET_WM_STATE_TOGGLE)))
-                        toggle_fullscreen(conn, client);
+                        client_toggle_fullscreen(conn, client);
         } else {
                 LOG("unhandled clientmessage\n");
                 return 0;
@@ -804,6 +949,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
                 return 1;
         }
         xcb_size_hints_t size_hints;
+        LOG("client is %08x / child %08x\n", client->frame, client->child);
 
         /* If the hints were already in this event, use them, if not, request them */
         if (reply != NULL)
@@ -811,6 +957,11 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
         else
                 xcb_get_wm_normal_hints_reply(conn, xcb_get_wm_normal_hints_unchecked(conn, client->child), &size_hints, NULL);
 
+        if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) {
+                LOG("min size set\n");
+                LOG("gots min_width = %d, min_height = %d\n", size_hints.min_width, size_hints.min_height);
+        }
+
         /* If no aspect ratio was set or if it was invalid, we ignore the hints */
         if (!(size_hints.flags & XCB_SIZE_HINT_P_ASPECT) ||
             (size_hints.min_aspect_num <= 0) ||
@@ -821,8 +972,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
 
         LOG("window is %08x / %s\n", client->child, client->name);
 
-        int base_width = 0, base_height = 0,
-            min_width = 0, min_height = 0;
+        int base_width = 0, base_height = 0;
 
         /* base_width/height are the desired size of the window.
            We check if either the program-specified size or the program-specified
@@ -835,14 +985,6 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
                 base_height = size_hints.min_height;
         }
 
-        if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) {
-                min_width = size_hints.min_width;
-                min_height = size_hints.min_height;
-        } else if (size_hints.flags & XCB_SIZE_HINT_P_SIZE) {
-                min_width = size_hints.base_width;
-                min_height = size_hints.base_height;
-        }
-
         double width = client->rect.width - base_width;
         double height = client->rect.height - base_height;
         /* Convert numerator/denominator to a double */
@@ -874,3 +1016,42 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
 
         return 1;
 }
+
+/*
+ * Handles the transient for hints set by a window, signalizing that this window is a popup window
+ * for some other window.
+ *
+ * See ICCCM 4.1.2.6 for more details
+ *
+ */
+int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
+                         xcb_atom_t name, xcb_get_property_reply_t *reply) {
+        LOG("Transient hint!\n");
+        Client *client = table_get(&by_child, window);
+        if (client == NULL) {
+                LOG("No such client\n");
+                return 1;
+        }
+
+        xcb_window_t transient_for;
+
+        if (reply != NULL) {
+                if (!xcb_get_wm_transient_for_from_reply(&transient_for, reply)) {
+                        LOG("Not transient for any window\n");
+                        return 1;
+                }
+        } else {
+                if (!xcb_get_wm_transient_for_reply(conn, xcb_get_wm_transient_for_unchecked(conn, window),
+                                                    &transient_for, NULL)) {
+                        LOG("Not transient for any window\n");
+                        return 1;
+                }
+        }
+
+        if (client->floating == FLOATING_AUTO_OFF) {
+                LOG("This is a popup window, putting into floating\n");
+                toggle_floating_mode(conn, client, true);
+        }
+
+        return 1;
+}
index ea9ff4f260a8e097b998d8f20c0b4261233c633c..4f132aea8cc2d8d1f54f2f902b2880a1c6429c5a 100644 (file)
@@ -24,6 +24,8 @@
 #include "util.h"
 #include "xinerama.h"
 #include "layout.h"
+#include "client.h"
+#include "floating.h"
 
 /*
  * Updates *destination with new_value and returns true if it was changed or false
@@ -83,7 +85,7 @@ int get_unoccupied_y(Workspace *workspace, int col) {
  *
  */
 void redecorate_window(xcb_connection_t *conn, Client *client) {
-        if (client->container->mode == MODE_STACK) {
+        if (client->container != NULL && client->container->mode == MODE_STACK) {
                 render_container(conn, client->container);
                 /* We clear the frame to generate exposure events, because the color used
                    in drawing may be different */
@@ -100,47 +102,39 @@ void redecorate_window(xcb_connection_t *conn, Client *client) {
 void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable, xcb_gcontext_t gc, int offset) {
         i3Font *font = load_font(conn, config.font);
         int decoration_height = font->height + 2 + 2;
-        uint32_t background_color,
-                 text_color,
-                 border_color;
+        struct Colortriple *color;
 
         /* Clients without a container (docks) won’t get decorated */
-        if (client->container == NULL)
+        if (client->dock)
                 return;
 
-        if (client->container->currently_focused == client) {
+        LOG("redecorating child %08x\n", client->child);
+        if (client_is_floating(client) || client->container->currently_focused == client) {
                 /* Distinguish if the window is currently focused… */
-                if (CUR_CELL->currently_focused == client)
-                        background_color = get_colorpixel(conn, "#285577");
+                if (client_is_floating(client) || CUR_CELL->currently_focused == client)
+                        color = &(config.client.focused);
                 /* …or if it is the focused window in a not focused container */
-                else background_color = get_colorpixel(conn, "#555555");
-
-                text_color = get_colorpixel(conn, "#ffffff");
-                border_color = get_colorpixel(conn, "#4c7899");
-        } else {
-                background_color = get_colorpixel(conn, "#222222");
-                text_color = get_colorpixel(conn, "#888888");
-                border_color = get_colorpixel(conn, "#333333");
-        }
+                else color = &(config.client.focused_inactive);
+        } else color = &(config.client.unfocused);
 
         /* Our plan is the following:
-           - Draw a rect around the whole client in background_color
+           - Draw a rect around the whole client in color->background
            - Draw two lines in a lighter color
            - Draw the window’s title
          */
 
         /* Draw a rectangle in background color around the window */
-        xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, background_color);
+        xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background);
 
         /* In stacking mode, we only render the rect for this specific decoration */
-        if (client->container->mode == MODE_STACK) {
-                xcb_rectangle_t rect = {0, offset, client->container->width, offset + decoration_height };
-                xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
-        } else {
+        if (client->container != NULL && client->container->mode == MODE_STACK) {
                 /* We need to use the container’s width because it is the more recent value - when
                    in stacking mode, clients get reconfigured only on demand (the not active client
                    is not reconfigured), so the client’s rect.width would be wrong */
-                xcb_rectangle_t rect = {0, 0, client->container->width, client->rect.height};
+                xcb_rectangle_t rect = {0, offset, client->container->width, offset + decoration_height };
+                xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
+        } else {
+                xcb_rectangle_t rect = {0, 0, client->rect.width, client->rect.height};
                 xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
 
                 /* Draw the inner background to have a black frame around clients (such as mplayer)
@@ -152,15 +146,15 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
         }
 
         /* Draw the lines */
-        xcb_draw_line(conn, drawable, gc, border_color, 2, offset, client->rect.width, offset);
-        xcb_draw_line(conn, drawable, gc, border_color, 2, offset + font->height + 3,
-                      2 + client->rect.width, offset + font->height + 3);
+        xcb_draw_line(conn, drawable, gc, color->border, 0, offset, client->rect.width, offset);
+        xcb_draw_line(conn, drawable, gc, color->border, 2, offset + font->height + 3,
+                      client->rect.width - 3, offset + font->height + 3);
 
         /* If the client has a title, we draw it */
         if (client->name != NULL) {
                 /* Draw the font */
                 uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
-                uint32_t values[] = { text_color, background_color, font->id };
+                uint32_t values[] = { color->text, color->background, font->id };
                 xcb_change_gc(conn, gc, mask, values);
 
                 /* name_len == -1 means this is a legacy application which does not specify _NET_WM_NAME,
@@ -179,18 +173,37 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
  * Pushes the client’s x and y coordinates to X11
  *
  */
-static void reposition_client(xcb_connection_t *conn, Client *client) {
+void reposition_client(xcb_connection_t *conn, Client *client) {
+        i3Screen *screen;
+
         LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
         /* Note: We can use a pointer to client->x like an array of uint32_ts
            because it is followed by client->y by definition */
         xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
+
+        if (!client_is_floating(client))
+                return;
+
+        /* If the client is floating, we need to check if we moved it to a different workspace */
+        if (client->workspace->screen == (screen = get_screen_containing(client->rect.x, client->rect.y)))
+                return;
+
+        if (screen == NULL) {
+                LOG("Boundary checking disabled, no screen found for (%d, %d)\n", client->rect.x, client->rect.y);
+                return;
+        }
+
+        LOG("Client is on workspace %p with screen %p\n", client->workspace, client->workspace->screen);
+        LOG("but screen at %d, %d is %p\n", client->rect.x, client->rect.y, screen);
+        floating_assign_to_workspace(client, &workspaces[screen->current_workspace]);
+        LOG("fixed that\n");
 }
 
 /*
  * Pushes the client’s width/height to X11 and resizes the child window
  *
  */
-static void resize_client(xcb_connection_t *conn, Client *client) {
+void resize_client(xcb_connection_t *conn, Client *client) {
         i3Font *font = load_font(conn, config.font);
 
         LOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
@@ -335,8 +348,13 @@ void render_container(xcb_connection_t *conn, Container *container) {
                                         XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
                                         XCB_CONFIG_WINDOW_STACK_MODE;
 
-                        /* If there is no fullscreen client, we raise the stack window */
-                        if (container->workspace->fullscreen_client != NULL) {
+                        /* Raise the stack window, but keep it below the first floating client
+                         * and below the fullscreen client (if any) */
+                        Client *first_floating = TAILQ_FIRST(&(container->workspace->floating_clients));
+                        if (first_floating != TAILQ_END(&(container->workspace->floating_clients))) {
+                                mask |= XCB_CONFIG_WINDOW_SIBLING;
+                                values[4] = first_floating->frame;
+                        } else if (container->workspace->fullscreen_client != NULL) {
                                 mask |= XCB_CONFIG_WINDOW_SIBLING;
                                 values[4] = container->workspace->fullscreen_client->frame;
                         }
@@ -344,9 +362,6 @@ void render_container(xcb_connection_t *conn, Container *container) {
                         xcb_configure_window(conn, stack_win->window, mask, values);
                 }
 
-                /* Reconfigure the currently focused client, if necessary. It is the only visible one */
-                client = container->currently_focused;
-
                 /* Render the decorations of all clients */
                 CIRCLEQ_FOREACH(client, &(container->clients), clients) {
                         /* If the client is in fullscreen mode, it does not get reconfigured */
@@ -400,24 +415,10 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid
         i3Font *font = load_font(conn, config.font);
         i3Screen *screen = r_ws->screen;
         enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
-        uint32_t background_color[2],
-                 text_color[2],
-                 border_color[2],
-                 black;
         char label[3];
 
-        black = get_colorpixel(conn, "#000000");
-
-        background_color[SET_NORMAL] = get_colorpixel(conn, "#222222");
-        text_color[SET_NORMAL] = get_colorpixel(conn, "#888888");
-        border_color[SET_NORMAL] = get_colorpixel(conn, "#333333");
-
-        background_color[SET_FOCUSED] = get_colorpixel(conn, "#285577");
-        text_color[SET_FOCUSED] = get_colorpixel(conn, "#ffffff");
-        border_color[SET_FOCUSED] = get_colorpixel(conn, "#4c7899");
-
         /* Fill the whole bar in black */
-        xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, black);
+        xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
         xcb_rectangle_t rect = {0, 0, width, height};
         xcb_poly_fill_rectangle(conn, screen->bar, screen->bargc, 1, &rect);
 
@@ -429,16 +430,17 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid
                 if (workspaces[c].screen != screen)
                         continue;
 
-                int set = (screen->current_workspace == c ? SET_FOCUSED : SET_NORMAL);
+                struct Colortriple *color = (screen->current_workspace == c ? &(config.bar.focused) :
+                                             &(config.bar.unfocused));
 
-                xcb_draw_rect(conn, screen->bar, screen->bargc, border_color[set],
+                xcb_draw_rect(conn, screen->bar, screen->bargc, color->border,
                               drawn * height, 1, height - 2, height - 2);
-                xcb_draw_rect(conn, screen->bar, screen->bargc, background_color[set],
+                xcb_draw_rect(conn, screen->bar, screen->bargc, color->background,
                               drawn * height + 1, 2, height - 4, height - 4);
 
                 snprintf(label, sizeof(label), "%d", c+1);
-                xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, text_color[set]);
-                xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, background_color[set]);
+                xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, color->text);
+                xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, color->background);
                 xcb_image_text_8(conn, strlen(label), screen->bar, screen->bargc, drawn * height + 5 /* X */,
                                                 font->height + 1 /* Y = baseline of font */, label);
                 drawn++;
index 38ae2511bf898422ae52d4e57b9fa46f9195490e..d95ca4653dcff818b0d8cb19c82d8b49db373421 100644 (file)
@@ -31,6 +31,8 @@
 #include <xcb/xcb_icccm.h>
 #include <xcb/xinerama.h>
 
+#include <ev.h>
+
 #include "config.h"
 #include "data.h"
 #include "debug.h"
@@ -42,6 +44,7 @@
 #include "util.h"
 #include "xcb.h"
 #include "xinerama.h"
+#include "manage.h"
 
 /* This is the path to i3, copied from argv[0] when starting up */
 char **start_argv;
@@ -52,6 +55,12 @@ Display *xkbdpy;
 /* The list of key bindings */
 struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
 
+/* The list of exec-lines */
+struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts);
+
+/* The list of assignments */
+struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments);
+
 /* This is a list of Stack_Windows, global, for easier/faster access on expose events */
 struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
 
@@ -63,273 +72,40 @@ xcb_atom_t atoms[NUM_ATOMS];
 int num_screens = 0;
 
 /*
- * Do some sanity checks and then reparent the window.
+ * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
+ * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
  *
  */
-void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
-        LOG("managing window.\n");
-        xcb_drawable_t d = { window };
-        xcb_get_geometry_cookie_t geomc;
-        xcb_get_geometry_reply_t *geom;
-        xcb_get_window_attributes_reply_t *attr = 0;
-
-        if (wa.tag == TAG_COOKIE) {
-                /* Check if the window is mapped (it could be not mapped when intializing and
-                   calling manage_window() for every window) */
-                if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
-                        return;
-
-                if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
-                        goto out;
-
-                wa.tag = TAG_VALUE;
-                wa.u.override_redirect = attr->override_redirect;
-        }
-
-        /* Don’t manage clients with the override_redirect flag */
-        if (wa.u.override_redirect)
-                goto out;
-
-        /* Check if the window is already managed */
-        if (table_get(&by_child, window))
-                goto out;
-
-        /* Get the initial geometry (position, size, …) */
-        geomc = xcb_get_geometry(conn, d);
-        if (!attr) {
-                wa.tag = TAG_COOKIE;
-                wa.u.cookie = xcb_get_window_attributes(conn, window);
-                attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0);
-        }
-        geom = xcb_get_geometry_reply(conn, geomc, 0);
-        if (attr && geom) {
-                reparent_window(conn, window, attr->visual, geom->root, geom->depth,
-                                geom->x, geom->y, geom->width, geom->height);
-                xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
-                xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
-                xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
-        }
-
-        free(geom);
-out:
-        free(attr);
-        return;
+static void xcb_got_event(EV_P_ struct ev_io *w, int revents) {
+        /* empty, because xcb_prepare_cb and xcb_check_cb are used */
 }
 
 /*
- * reparent_window() gets called when a new window was opened and becomes a child of the root
- * window, or it gets called by us when we manage the already existing windows at startup.
- *
- * Essentially, this is the point where we take over control.
+ * Flush before blocking (and waiting for new events)
  *
  */
-void reparent_window(xcb_connection_t *conn, xcb_window_t child,
-                     xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
-                     int16_t x, int16_t y, uint16_t width, uint16_t height) {
-
-        xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie;
-        uint32_t mask = 0;
-        uint32_t values[3];
-
-        /* We are interested in property changes */
-        mask = XCB_CW_EVENT_MASK;
-        values[0] = CHILD_EVENT_MASK;
-        xcb_change_window_attributes(conn, child, mask, values);
-
-        /* Map the window first to avoid flickering */
-        xcb_map_window(conn, child);
-
-        /* Place requests for properties ASAP */
-        wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
-        strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
-        state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
-
-        Client *new = table_get(&by_child, child);
-
-        /* Events for already managed windows should already be filtered in manage_window() */
-        assert(new == NULL);
-
-        LOG("reparenting new client\n");
-        new = calloc(sizeof(Client), 1);
-        new->force_reconfigure = true;
-
-        /* Update the data structures */
-        Client *old_focused = CUR_CELL->currently_focused;
-
-        new->container = CUR_CELL;
-        new->workspace = new->container->workspace;
-
-        new->frame = xcb_generate_id(conn);
-        new->child = child;
-        new->rect.width = width;
-        new->rect.height = height;
-
-        mask = 0;
-
-        /* Don’t generate events for our new window, it should *not* be managed */
-        mask |= XCB_CW_OVERRIDE_REDIRECT;
-        values[0] = 1;
-
-        /* We want to know when… */
-        mask |= XCB_CW_EVENT_MASK;
-        values[1] = FRAME_EVENT_MASK;
-
-        LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
-
-        i3Font *font = load_font(conn, config.font);
-        width = min(width, c_ws->rect.x + c_ws->rect.width);
-        height = min(height, c_ws->rect.y + c_ws->rect.height);
-
-        Rect framerect = {x, y,
-                          width + 2 + 2,                  /* 2 px border at each side */
-                          height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
-
-        /* Yo dawg, I heard you like windows, so I create a window around your window… */
-        new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
-
-        long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
-        xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
-
-        /* Put the client inside the save set. Upon termination (whether killed or normal exit
-           does not matter) of the window manager, these clients will be correctly reparented
-           to their most closest living ancestor (= cleanup) */
-        xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
-
-        /* Generate a graphics context for the titlebar */
-        new->titlegc = xcb_generate_id(conn);
-        xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
-
-        /* Moves the original window into the new frame we've created for it */
-        new->awaiting_useless_unmap = true;
-        xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
-        if (xcb_request_check(conn, cookie) != NULL) {
-                LOG("Could not reparent the window, aborting\n");
-                xcb_destroy_window(conn, new->frame);
-                free(new);
-                return;
-        }
-
-        /* Put our data structure (Client) into the table */
-        table_put(&by_parent, new->frame, new);
-        table_put(&by_child, child, new);
-
-        /* We need to grab the mouse buttons for click to focus */
-        xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
-                        XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
-                        1 /* left mouse button */,
-                        XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
-
-        /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
-        xcb_atom_t *atom;
-        xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
-        if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
-                for (int i = 0; i < xcb_get_property_value_length(preply); i++)
-                        if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
-                                LOG("Window is a dock.\n");
-                                new->dock = true;
-                                new->titlebar_position = TITLEBAR_OFF;
-                                new->force_reconfigure = true;
-                                new->container = NULL;
-                                SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
-                        }
-        }
-
-        if (new->dock) {
-                /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
-                uint32_t *strut;
-                preply = xcb_get_property_reply(conn, strut_cookie, NULL);
-                if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
-                        /* We only use a subset of the provided values, namely the reserved space at the top/bottom
-                           of the screen. This is because the only possibility for bars is at to be at the top/bottom
-                           with maximum horizontal size.
-                           TODO: bars at the top */
-                        new->desired_height = strut[3];
-                        if (new->desired_height == 0) {
-                                LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height);
-                                new->desired_height = height;
-                        }
-                        LOG("the client wants to be %d pixels high\n", new->desired_height);
-                } else {
-                        LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height);
-                        new->desired_height = height;
-                }
-        }
-
-        /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
-        if (CUR_CELL->workspace->fullscreen_client == NULL) {
-                if (!new->dock) {
-                        CUR_CELL->currently_focused = new;
-                        xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
-                }
-        } else {
-                /* If we are in fullscreen, we should lower the window to not be annoying */
-                uint32_t values[] = { XCB_STACK_MODE_BELOW };
-                xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
-        }
-
-        /* Insert into the currently active container, if it’s not a dock window */
-        if (!new->dock) {
-                /* Insert after the old active client, if existing. If it does not exist, the
-                   container is empty and it does not matter, where we insert it */
-                if (old_focused != NULL && !old_focused->dock)
-                        CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
-                else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
-
-                SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
-        }
-
-        /* Check if the window already got the fullscreen hint set */
-        xcb_atom_t *state;
-        if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
-            (state = xcb_get_property_value(preply)) != NULL)
-                /* Check all set _NET_WM_STATEs */
-                for (int i = 0; i < xcb_get_property_value_length(preply); i++)
-                        if (state[i] == atoms[_NET_WM_STATE_FULLSCREEN]) {
-                                /* If the window got the fullscreen state, we just toggle fullscreen
-                                   and don’t event bother to redraw the layout – that would not change
-                                   anything anyways */
-                                toggle_fullscreen(conn, new);
-                                return;
-                        }
-
-        render_layout(conn);
+static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
+        xcb_flush(evenths.c);
 }
 
 /*
- * Go through all existing windows (if the window manager is restarted) and manage them
+ * Instead of polling the X connection socket we leave this to
+ * xcb_poll_for_event() which knows better than we can ever know.
  *
  */
-void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
-        xcb_query_tree_reply_t *reply;
-        int i, len;
-        xcb_window_t *children;
-        xcb_get_window_attributes_cookie_t *cookies;
-
-        /* Get the tree of windows whose parent is the root window (= all) */
-        if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
-                return;
-
-        len = xcb_query_tree_children_length(reply);
-        cookies = smalloc(len * sizeof(*cookies));
-
-        /* Request the window attributes for every window */
-        children = xcb_query_tree_children(reply);
-        for(i = 0; i < len; ++i)
-                cookies[i] = xcb_get_window_attributes(conn, children[i]);
-
-        /* Call manage_window with the attributes for every window */
-        for(i = 0; i < len; ++i) {
-                window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
-                manage_window(prophs, conn, children[i], wa);
-        }
+static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
+        xcb_generic_event_t *event;
 
-        free(reply);
-        free(cookies);
+        while ((event = xcb_poll_for_event(evenths.c)) != NULL) {
+                xcb_event_handle(&evenths, event);
+                free(event);
+        }
 }
 
 int main(int argc, char *argv[], char *env[]) {
         int i, screens, opt;
         char *override_configpath = NULL;
+        bool autostart = true;
         xcb_connection_t *conn;
         xcb_property_handlers_t prophs;
         xcb_window_t root;
@@ -343,8 +119,12 @@ int main(int argc, char *argv[], char *env[]) {
 
         start_argv = argv;
 
-        while ((opt = getopt(argc, argv, "c:v")) != -1) {
+        while ((opt = getopt(argc, argv, "c:va")) != -1) {
                 switch (opt) {
+                        case 'a':
+                                LOG("Autostart disabled using -a\n");
+                                autostart = false;
+                                break;
                         case 'c':
                                 override_configpath = sstrdup(optarg);
                                 break;
@@ -365,10 +145,13 @@ int main(int argc, char *argv[], char *env[]) {
         memset(&evenths, 0, sizeof(xcb_event_handlers_t));
         memset(&prophs, 0, sizeof(xcb_property_handlers_t));
 
-        load_configuration(override_configpath);
-
         conn = xcb_connect(NULL, &screens);
 
+        if (xcb_connection_has_error(conn))
+                die("Cannot open display\n");
+
+        load_configuration(conn, override_configpath);
+
         /* Place requests for the atoms we need as soon as possible */
         #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name);
 
@@ -380,6 +163,10 @@ int main(int argc, char *argv[], char *env[]) {
         REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
         REQUEST_ATOM(_NET_WM_DESKTOP);
         REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
+        REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DIALOG);
+        REQUEST_ATOM(_NET_WM_WINDOW_TYPE_UTILITY);
+        REQUEST_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR);
+        REQUEST_ATOM(_NET_WM_WINDOW_TYPE_SPLASH);
         REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
         REQUEST_ATOM(WM_PROTOCOLS);
         REQUEST_ATOM(WM_DELETE_WINDOW);
@@ -406,6 +193,27 @@ int main(int argc, char *argv[], char *env[]) {
         }
         /* end of ugliness */
 
+        /* Initialize event loop using libev */
+        struct ev_loop *loop = ev_default_loop(0);
+        if (loop == NULL)
+                die("Could not initialize libev. Bad LIBEV_FLAGS?\n");
+
+        struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io));
+        struct ev_check *xcb_check = scalloc(sizeof(struct ev_check));
+        struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare));
+
+        ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
+        ev_io_start(loop, xcb_watcher);
+
+        ev_check_init(xcb_check, xcb_check_cb);
+        ev_check_start(loop, xcb_check);
+
+        ev_prepare_init(xcb_prepare, xcb_prepare_cb);
+        ev_prepare_start(loop, xcb_prepare);
+
+        /* Grab the server to delay any events until we enter the eventloop */
+        xcb_grab_server(conn);
+
         xcb_event_handlers_init(conn, &evenths);
 
         /* DEBUG: Trap all events and print them */
@@ -483,6 +291,10 @@ int main(int argc, char *argv[], char *env[]) {
         GET_ATOM(_NET_WM_WINDOW_TYPE);
         GET_ATOM(_NET_WM_DESKTOP);
         GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
+        GET_ATOM(_NET_WM_WINDOW_TYPE_DIALOG);
+        GET_ATOM(_NET_WM_WINDOW_TYPE_UTILITY);
+        GET_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR);
+        GET_ATOM(_NET_WM_WINDOW_TYPE_SPLASH);
         GET_ATOM(_NET_WM_STRUT_PARTIAL);
         GET_ATOM(WM_PROTOCOLS);
         GET_ATOM(WM_DELETE_WINDOW);
@@ -495,9 +307,15 @@ int main(int argc, char *argv[], char *env[]) {
         /* Watch _NET_WM_NAME (= title of the window in UTF-8) property */
         xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL);
 
+        /* Watch WM_TRANSIENT_FOR property (to which client this popup window belongs) */
+        xcb_property_set_handler(&prophs, WM_TRANSIENT_FOR, UINT_MAX, handle_transient_for, NULL);
+
         /* Watch WM_NAME (= title of the window in compound text) property for legacy applications */
         xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL);
 
+        /* Watch WM_CLASS (= class of the window) */
+        xcb_property_set_handler(&prophs, WM_CLASS, 128, handle_windowclass_change, NULL);
+
         /* Set up the atoms we support */
         check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
                        ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
@@ -523,6 +341,15 @@ int main(int argc, char *argv[], char *env[]) {
                 }
         }
 
+        /* Autostarting exec-lines */
+        struct Autostart *exec;
+        if (autostart) {
+                TAILQ_FOREACH(exec, &autostarts, autostarts) {
+                        LOG("auto-starting %s\n", exec->command);
+                        start_application(exec->command);
+                }
+        }
+
         /* check for Xinerama */
         LOG("Checking for Xinerama...\n");
         initialize_xinerama(conn);
@@ -548,8 +375,12 @@ int main(int argc, char *argv[], char *env[]) {
                 c_ws = &workspaces[screen->current_workspace];
         }
 
-        /* Enter xcb’s event handler */
-        xcb_event_wait_for_event_loop(&evenths);
+        /* Handle the events which arrived until now */
+        xcb_check_cb(NULL, NULL, 0);
+
+        /* Ungrab the server to receive events and enter libev’s eventloop */
+        xcb_ungrab_server(conn);
+        ev_loop(loop, 0);
 
         /* not reached */
         return 0;
diff --git a/src/manage.c b/src/manage.c
new file mode 100644 (file)
index 0000000..d4641db
--- /dev/null
@@ -0,0 +1,425 @@
+/*
+ * vim:ts=8:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * © 2009 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ * src/manage.c: Contains all functions for initially managing new windows
+ *               (or existing ones on restart).
+ *
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_icccm.h>
+
+#include "xcb.h"
+#include "data.h"
+#include "util.h"
+#include "i3.h"
+#include "table.h"
+#include "config.h"
+#include "handlers.h"
+#include "layout.h"
+#include "manage.h"
+#include "floating.h"
+#include "client.h"
+
+/*
+ * Go through all existing windows (if the window manager is restarted) and manage them
+ *
+ */
+void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
+        xcb_query_tree_reply_t *reply;
+        int i, len;
+        xcb_window_t *children;
+        xcb_get_window_attributes_cookie_t *cookies;
+
+        /* Get the tree of windows whose parent is the root window (= all) */
+        if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
+                return;
+
+        len = xcb_query_tree_children_length(reply);
+        cookies = smalloc(len * sizeof(*cookies));
+
+        /* Request the window attributes for every window */
+        children = xcb_query_tree_children(reply);
+        for(i = 0; i < len; ++i)
+                cookies[i] = xcb_get_window_attributes(conn, children[i]);
+
+        /* Call manage_window with the attributes for every window */
+        for(i = 0; i < len; ++i) {
+                window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
+                manage_window(prophs, conn, children[i], wa);
+        }
+
+        free(reply);
+        free(cookies);
+}
+
+/*
+ * Do some sanity checks and then reparent the window.
+ *
+ */
+void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
+        LOG("managing window.\n");
+        xcb_drawable_t d = { window };
+        xcb_get_geometry_cookie_t geomc;
+        xcb_get_geometry_reply_t *geom;
+        xcb_get_window_attributes_reply_t *attr = 0;
+
+        if (wa.tag == TAG_COOKIE) {
+                /* Check if the window is mapped (it could be not mapped when intializing and
+                   calling manage_window() for every window) */
+                if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
+                        return;
+
+                if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
+                        goto out;
+
+                wa.tag = TAG_VALUE;
+                wa.u.override_redirect = attr->override_redirect;
+        }
+
+        /* Don’t manage clients with the override_redirect flag */
+        if (wa.u.override_redirect)
+                goto out;
+
+        /* Check if the window is already managed */
+        if (table_get(&by_child, window))
+                goto out;
+
+        /* Get the initial geometry (position, size, …) */
+        geomc = xcb_get_geometry(conn, d);
+        if (!attr) {
+                wa.tag = TAG_COOKIE;
+                wa.u.cookie = xcb_get_window_attributes(conn, window);
+                if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
+                        return;
+        }
+        if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
+                goto out;
+
+        /* Reparent the window and add it to our list of managed windows */
+        reparent_window(conn, window, attr->visual, geom->root, geom->depth,
+                        geom->x, geom->y, geom->width, geom->height);
+
+        /* Generate callback events for every property we watch */
+        xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
+        xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
+        xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
+        xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR);
+        xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
+
+        free(geom);
+out:
+        free(attr);
+        return;
+}
+
+/*
+ * reparent_window() gets called when a new window was opened and becomes a child of the root
+ * window, or it gets called by us when we manage the already existing windows at startup.
+ *
+ * Essentially, this is the point where we take over control.
+ *
+ */
+void reparent_window(xcb_connection_t *conn, xcb_window_t child,
+                     xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
+                     int16_t x, int16_t y, uint16_t width, uint16_t height) {
+
+        xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
+                                  utf8_title_cookie, title_cookie, class_cookie;
+        uint32_t mask = 0;
+        uint32_t values[3];
+        uint16_t original_height = height;
+
+        /* We are interested in property changes */
+        mask = XCB_CW_EVENT_MASK;
+        values[0] = CHILD_EVENT_MASK;
+        xcb_change_window_attributes(conn, child, mask, values);
+
+        /* Map the window first to avoid flickering */
+        xcb_map_window(conn, child);
+
+        /* Place requests for properties ASAP */
+        wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
+        strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
+        state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
+        utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
+        title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
+        class_cookie  = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
+
+        Client *new = table_get(&by_child, child);
+
+        /* Events for already managed windows should already be filtered in manage_window() */
+        assert(new == NULL);
+
+        LOG("reparenting new client\n");
+        LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
+        new = calloc(sizeof(Client), 1);
+        new->force_reconfigure = true;
+
+        /* Update the data structures */
+        Client *old_focused = CUR_CELL->currently_focused;
+
+        new->container = CUR_CELL;
+        new->workspace = new->container->workspace;
+
+        /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
+        width = max(width, 75);
+        height = max(height, 50);
+
+        new->frame = xcb_generate_id(conn);
+        new->child = child;
+        new->rect.width = width;
+        new->rect.height = height;
+        /* Pre-initialize the values for floating */
+        new->floating_rect.x = -1;
+        new->floating_rect.width = width;
+        new->floating_rect.height = height;
+
+        mask = 0;
+
+        /* Don’t generate events for our new window, it should *not* be managed */
+        mask |= XCB_CW_OVERRIDE_REDIRECT;
+        values[0] = 1;
+
+        /* We want to know when… */
+        mask |= XCB_CW_EVENT_MASK;
+        values[1] = FRAME_EVENT_MASK;
+
+        LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
+
+        i3Font *font = load_font(conn, config.font);
+        width = min(width, c_ws->rect.x + c_ws->rect.width);
+        height = min(height, c_ws->rect.y + c_ws->rect.height);
+
+        Rect framerect = {x, y,
+                          width + 2 + 2,                  /* 2 px border at each side */
+                          height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
+
+        /* Yo dawg, I heard you like windows, so I create a window around your window… */
+        new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
+
+        /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t.
+         * Also, xprop(1) needs that to work. */
+        long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
+        xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
+
+        /* Put the client inside the save set. Upon termination (whether killed or normal exit
+           does not matter) of the window manager, these clients will be correctly reparented
+           to their most closest living ancestor (= cleanup) */
+        xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
+
+        /* Generate a graphics context for the titlebar */
+        new->titlegc = xcb_generate_id(conn);
+        xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
+
+        /* Moves the original window into the new frame we've created for it */
+        new->awaiting_useless_unmap = true;
+        xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
+        if (xcb_request_check(conn, cookie) != NULL) {
+                LOG("Could not reparent the window, aborting\n");
+                xcb_destroy_window(conn, new->frame);
+                free(new);
+                return;
+        }
+
+        /* Put our data structure (Client) into the table */
+        table_put(&by_parent, new->frame, new);
+        table_put(&by_child, child, new);
+
+        /* We need to grab the mouse buttons for click to focus */
+        xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
+                        XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
+                        1 /* left mouse button */,
+                        XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
+
+        xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
+                        XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
+                        1 /* left mouse button */, XCB_MOD_MASK_1);
+
+        /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
+        xcb_atom_t *atom;
+        xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
+        if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
+                for (int i = 0; i < xcb_get_property_value_length(preply); i++)
+                        if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
+                                LOG("Window is a dock.\n");
+                                new->dock = true;
+                                new->titlebar_position = TITLEBAR_OFF;
+                                new->force_reconfigure = true;
+                                new->container = NULL;
+                                SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
+                                /* If it’s a dock we can’t make it float, so we break */
+                                break;
+                        } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
+                                   atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
+                                   atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
+                                   atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
+                                /* Set the dialog window to automatically floating, will be used below */
+                                new->floating = FLOATING_AUTO_ON;
+                                LOG("dialog/utility/toolbar/splash window, automatically floating\n");
+                        }
+        }
+
+        if (new->workspace->auto_float) {
+                new->floating = FLOATING_AUTO_ON;
+                LOG("workspace is in autofloat mode, setting floating\n");
+        }
+
+        if (new->dock) {
+                /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
+                uint32_t *strut;
+                preply = xcb_get_property_reply(conn, strut_cookie, NULL);
+                if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
+                        /* We only use a subset of the provided values, namely the reserved space at the top/bottom
+                           of the screen. This is because the only possibility for bars is at to be at the top/bottom
+                           with maximum horizontal size.
+                           TODO: bars at the top */
+                        new->desired_height = strut[3];
+                        if (new->desired_height == 0) {
+                                LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
+                                new->desired_height = original_height;
+                        }
+                        LOG("the client wants to be %d pixels high\n", new->desired_height);
+                } else {
+                        LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
+                        new->desired_height = original_height;
+                }
+        } else {
+                /* If it’s not a dock, we can check on which workspace we should put it. */
+
+                /* Firstly, we need to get the window’s class / title. We asked for the properties at the
+                 * top of this function, get them now and pass them to our callback function for window class / title
+                 * changes. It is important that the client was already inserted into the by_child table,
+                 * because the callbacks won’t work otherwise. */
+                preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
+                handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
+
+                preply = xcb_get_property_reply(conn, title_cookie, NULL);
+                handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
+
+                preply = xcb_get_property_reply(conn, class_cookie, NULL);
+                handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
+
+                LOG("DEBUG: should have all infos now\n");
+                struct Assignment *assign;
+                TAILQ_FOREACH(assign, &assignments, assignments) {
+                        if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
+                                continue;
+
+                        if (assign->floating) {
+                                new->floating = FLOATING_AUTO_ON;
+                                LOG("Assignment matches, putting client into floating mode\n");
+                                break;
+                        }
+
+                        LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
+                            assign->windowclass_title, assign->workspace);
+
+                        if (c_ws->screen->current_workspace == (assign->workspace-1)) {
+                                LOG("We are already there, no need to do anything\n");
+                                break;
+                        }
+
+                        LOG("Changin container/workspace and unmapping the client\n");
+                        Workspace *t_ws = &(workspaces[assign->workspace-1]);
+                        if (t_ws->screen == NULL) {
+                                LOG("initializing new workspace, setting num to %d\n", assign->workspace);
+                                t_ws->screen = c_ws->screen;
+                                /* Copy the dimensions from the virtual screen */
+                                memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
+                        }
+
+                        new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
+                        new->workspace = t_ws;
+                        old_focused = new->container->currently_focused;
+
+                        xcb_unmap_window(conn, new->frame);
+                        break;
+                }
+        }
+
+        if (CUR_CELL->workspace->fullscreen_client != NULL) {
+                if (new->container == CUR_CELL) {
+                        /* If we are in fullscreen, we should lower the window to not be annoying */
+                        uint32_t values[] = { XCB_STACK_MODE_BELOW };
+                        xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
+                }
+        } else if (!new->dock) {
+                /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
+                if (new->container->workspace->fullscreen_client == NULL) {
+                        if (!client_is_floating(new))
+                                new->container->currently_focused = new;
+                        if (new->container == CUR_CELL)
+                                xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
+                }
+        }
+
+        /* Insert into the currently active container, if it’s not a dock window */
+        if (!new->dock && !client_is_floating(new)) {
+                /* Insert after the old active client, if existing. If it does not exist, the
+                   container is empty and it does not matter, where we insert it */
+                if (old_focused != NULL && !old_focused->dock)
+                        CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
+                else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
+
+                if (new->container->workspace->fullscreen_client != NULL)
+                        SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
+                else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
+
+                client_set_below_floating(conn, new);
+        }
+
+        if (client_is_floating(new)) {
+                SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
+
+                /* Add the client to the list of floating clients for its workspace */
+                TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
+
+                new->container = NULL;
+
+                new->floating_rect.x = new->rect.x = x;
+                new->floating_rect.y = new->rect.y = y;
+                new->rect.width = new->floating_rect.width + 2 + 2;
+                new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
+                LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
+                                new->floating_rect.x, new->floating_rect.y,
+                                new->floating_rect.width, new->floating_rect.height);
+                LOG("outer rect (%d, %d) size (%d, %d)\n",
+                                new->rect.x, new->rect.y, new->rect.width, new->rect.height);
+
+                /* Make sure it is on top of the other windows */
+                xcb_raise_window(conn, new->frame);
+                reposition_client(conn, new);
+                resize_client(conn, new);
+                /* redecorate_window flushes */
+                redecorate_window(conn, new);
+        }
+
+        new->initialized = true;
+
+        /* Check if the window already got the fullscreen hint set */
+        xcb_atom_t *state;
+        if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
+            (state = xcb_get_property_value(preply)) != NULL)
+                /* Check all set _NET_WM_STATEs */
+                for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
+                        if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
+                                continue;
+                        /* If the window got the fullscreen state, we just toggle fullscreen
+                           and don’t event bother to redraw the layout – that would not change
+                           anything anyways */
+                        client_toggle_fullscreen(conn, new);
+                        return;
+                }
+
+        render_layout(conn);
+}
index 9c6cbab40a60361cdf0ac7628d2e76772a291d90..3d6344202a63bc642e64d4f1be05d796cc126a7c 100644 (file)
@@ -24,6 +24,9 @@
 #include "xcb.h"
 #include "debug.h"
 #include "layout.h"
+#include "xinerama.h"
+#include "config.h"
+#include "floating.h"
 
 /*
  * Renders the resize window between the first/second container and resizes
 int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second,
                              resize_orientation_t orientation, xcb_button_press_event_t *event) {
         int new_position;
-        xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
         xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
+        i3Screen *screen = get_screen_containing(event->root_x, event->root_y);
+        if (screen == NULL) {
+                LOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y);
+                return 1;
+        }
+
+        LOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
 
         /* FIXME: horizontal resizing causes empty spaces to exist */
         if (orientation == O_HORIZONTAL) {
@@ -57,9 +66,9 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
         Rect helprect;
         if (orientation == O_VERTICAL) {
                 helprect.x = event->root_x;
-                helprect.y = 0;
+                helprect.y = screen->rect.y;
                 helprect.width = 2;
-                helprect.height = root_screen->height_in_pixels;
+                helprect.height = screen->rect.height;
                 new_position = event->root_x;
         } else {
                 helprect.x = 0;
@@ -70,7 +79,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
         }
 
         mask = XCB_CW_BACK_PIXEL;
-        values[0] = get_colorpixel(conn, "#4c7899");
+        values[0] = config.client.focused.border;
 
         mask |= XCB_CW_OVERRIDE_REDIRECT;
         values[1] = 1;
@@ -82,52 +91,32 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
 
         xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin);
 
-        xcb_grab_pointer(conn, false, root, XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION,
-                        XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, grabwin, XCB_NONE, XCB_CURRENT_TIME);
-
         xcb_flush(conn);
 
-        xcb_generic_event_t *inside_event;
-        /* I’ve always wanted to have my own eventhandler… */
-        while ((inside_event = xcb_wait_for_event(conn))) {
-                /* Same as get_event_handler in xcb */
-                int nr = inside_event->response_type;
-                if (nr == 0) {
-                        /* An error occured */
-                        handle_event(NULL, conn, inside_event);
-                        free(inside_event);
-                        continue;
+        void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
+                LOG("new x = %d, y = %d\n", new_x, new_y);
+                if (orientation == O_VERTICAL) {
+                        /* Check if the new coordinates are within screen boundaries */
+                        if (new_x > (screen->rect.x + screen->rect.width - 25) ||
+                            new_x < (screen->rect.x + 25))
+                                return;
+
+                        values[0] = new_position = new_x;
+                        xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values);
+                } else {
+                        if (new_y > (screen->rect.y + screen->rect.height - 25) ||
+                            new_y < (screen->rect.y + 25))
+                                return;
+
+                        values[0] = new_position = new_y;
+                        xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values);
                 }
-                assert(nr < 256);
-                nr &= XCB_EVENT_RESPONSE_TYPE_MASK;
-                assert(nr >= 2);
-
-                /* Check if we need to escape this loop */
-                if (nr == XCB_BUTTON_RELEASE)
-                        break;
-
-                switch (nr) {
-                        case XCB_MOTION_NOTIFY:
-                                if (orientation == O_VERTICAL) {
-                                        values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_x;
-                                        xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values);
-                                } else {
-                                        values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_y;
-                                        xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values);
-                                }
-
-                                xcb_flush(conn);
-                                break;
-                        default:
-                                LOG("Passing to original handler\n");
-                                /* Use original handler */
-                                xcb_event_handle(&evenths, inside_event);
-                                break;
-                }
-                free(inside_event);
+
+                xcb_flush(conn);
         }
 
-        xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
+        drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback);
+
         xcb_destroy_window(conn, helpwin);
         xcb_destroy_window(conn, grabwin);
         xcb_flush(conn);
index 20b88cec76a828ecc6a53bf5929cf5cea724c194..dab4b9ac1665f75c0be578c81d72a0c8bdc6cac7 100644 (file)
@@ -43,6 +43,7 @@ void init_table() {
         for (int i = 0; i < 10; i++) {
                 workspaces[i].screen = NULL;
                 workspaces[i].num = i;
+                TAILQ_INIT(&(workspaces[i].floating_clients));
                 expand_table_cols(&(workspaces[i]));
                 expand_table_rows(&(workspaces[i]));
         }
index 47d8c542238b5824bedec8f15f8604c418e3e5a4..bccb9941bb1ce0ff27291de6fd3294b027605409 100644 (file)
@@ -27,6 +27,7 @@
 #include "layout.h"
 #include "util.h"
 #include "xcb.h"
+#include "client.h"
 
 static iconv_t conversion_descriptor = 0;
 struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent);
@@ -213,34 +214,17 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
        int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
         if (rc == (size_t)-1) {
                 perror("Converting to UCS-2 failed");
-               *real_strlen = 0;
+                if (real_strlen != NULL)
+                       *real_strlen = 0;
                 return NULL;
        }
 
-       *real_strlen = ((buffer_size - output_size) / 2) - 1;
+        if (real_strlen != NULL)
+               *real_strlen = ((buffer_size - output_size) / 2) - 1;
 
        return buffer;
 }
 
-/*
- * Removes the given client from the container, either because it will be inserted into another
- * one or because it was unmapped
- *
- */
-void remove_client_from_container(xcb_connection_t *conn, Client *client, Container *container) {
-        CIRCLEQ_REMOVE(&(container->clients), client, clients);
-
-        SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients);
-
-        /* If the container will be empty now and is in stacking mode, we need to
-           unmap the stack_win */
-        if (CIRCLEQ_EMPTY(&(container->clients)) && container->mode == MODE_STACK) {
-                struct Stack_Window *stack_win = &(container->stack_win);
-                stack_win->rect.height = 0;
-                xcb_unmap_window(conn, stack_win->window);
-        }
-}
-
 /*
  * Returns the client which comes next in focus stack (= was selected before) for
  * the given container, optionally excluding the given client.
@@ -270,19 +254,43 @@ void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) {
         /* Ignore notify events because they would cause focus to be changed */
         ignore_enter_notify_forall(conn, u_ws, true);
 
-        /* Unmap all clients of the current workspace */
+        /* Unmap all clients of the given workspace */
         int unmapped_clients = 0;
         FOR_TABLE(u_ws)
                 CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) {
+                        LOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child);
                         xcb_unmap_window(conn, client->frame);
                         unmapped_clients++;
                 }
 
-        /* If we did not unmap any clients, the workspace is empty and we can destroy it */
-        if (unmapped_clients == 0)
+        /* To find floating clients, we traverse the focus stack */
+        SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) {
+                if (!client_is_floating(client))
+                        continue;
+
+                LOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child);
+
+                xcb_unmap_window(conn, client->frame);
+                unmapped_clients++;
+        }
+
+        /* If we did not unmap any clients, the workspace is empty and we can destroy it, at least
+         * if it is not the current workspace. */
+        if (unmapped_clients == 0 && u_ws != c_ws) {
+                /* Re-assign the workspace of all dock clients which use this workspace */
+                Client *dock;
+                LOG("workspace %p is empty\n", u_ws);
+                SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) {
+                        if (dock->workspace != u_ws)
+                                continue;
+
+                        LOG("Re-assigning dock client to c_ws (%p)\n", c_ws);
+                        dock->workspace = c_ws;
+                }
                 u_ws->screen = NULL;
+        }
 
-        /* Unmap the stack windows on the current workspace, if any */
+        /* Unmap the stack windows on the given workspace, if any */
         SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
                 if (stack_win->container->workspace == u_ws)
                         xcb_unmap_window(conn, stack_win->window);
@@ -302,7 +310,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
                 return;
 
         /* Store the old client */
-        Client *old_client = CUR_CELL->currently_focused;
+        Client *old_client = SLIST_FIRST(&(c_ws->focus_stack));
 
         /* Check if the focus needs to be changed at all */
         if (!set_anyways && (old_client == client)) {
@@ -313,47 +321,54 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
         /* Store current_row/current_col */
         c_ws->current_row = current_row;
         c_ws->current_col = current_col;
-        c_ws = client->container->workspace;
+        c_ws = client->workspace;
+        /* Load current_col/current_row if we switch to a client without a container */
+        current_col = c_ws->current_col;
+        current_row = c_ws->current_row;
 
         /* Update container */
-        client->container->currently_focused = client;
+        if (client->container != NULL) {
+                client->container->currently_focused = client;
 
-        current_col = client->container->col;
-        current_row = client->container->row;
+                current_col = client->container->col;
+                current_row = client->container->row;
+        }
 
         LOG("set_focus(frame %08x, child %08x, name %s)\n", client->frame, client->child, client->name);
         /* Set focus to the entered window, and flush xcb buffer immediately */
         xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME);
         //xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10);
 
-        /* Get the client which was last focused in this particular container, it may be a different
-           one than old_client */
-        Client *last_focused = get_last_focused_client(conn, client->container, NULL);
-
-        /* In stacking containers, raise the client in respect to the one which was focused before */
-        if (client->container->mode == MODE_STACK && client->container->workspace->fullscreen_client == NULL) {
-                /* We need to get the client again, this time excluding the current client, because
-                 * we might have just gone into stacking mode and need to raise */
-                Client *last_focused = get_last_focused_client(conn, client->container, client);
-
-                if (last_focused != NULL) {
-                        LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child);
-                        uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE };
-                        xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
+        if (client->container != NULL) {
+                /* Get the client which was last focused in this particular container, it may be a different
+                   one than old_client */
+                Client *last_focused = get_last_focused_client(conn, client->container, NULL);
+
+                /* In stacking containers, raise the client in respect to the one which was focused before */
+                if (client->container->mode == MODE_STACK && client->container->workspace->fullscreen_client == NULL) {
+                        /* We need to get the client again, this time excluding the current client, because
+                         * we might have just gone into stacking mode and need to raise */
+                        Client *last_focused = get_last_focused_client(conn, client->container, client);
+
+                        if (last_focused != NULL) {
+                                LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child);
+                                uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE };
+                                xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
+                        }
                 }
-        }
 
-        /* If it is the same one as old_client, we save us the unnecessary redecorate */
-        if ((last_focused != NULL) && (last_focused != old_client))
-                redecorate_window(conn, last_focused);
+                /* If it is the same one as old_client, we save us the unnecessary redecorate */
+                if ((last_focused != NULL) && (last_focused != old_client))
+                        redecorate_window(conn, last_focused);
+        }
 
         /* If we’re in stacking mode, this renders the container to update changes in the title
            bars and to raise the focused client */
         if ((old_client != NULL) && (old_client != client) && !old_client->dock)
                 redecorate_window(conn, old_client);
 
-        SLIST_REMOVE(&(client->container->workspace->focus_stack), client, Client, focus_clients);
-        SLIST_INSERT_HEAD(&(client->container->workspace->focus_stack), client, focus_clients);
+        SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
+        SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
 
         /* redecorate_window flushes, so we don’t need to */
         redecorate_window(conn, client);
@@ -390,7 +405,7 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode)
                 /* When entering stacking mode, we need to open a window on which we can draw the
                    title bars of the clients, it has height 1 because we don’t bother here with
                    calculating the correct height - it will be adjusted when rendering anyways. */
-                Rect rect = {container->x, container->y, container->width, 1 };
+                Rect rect = {container->x, container->y, container->width, 1};
 
                 uint32_t mask = 0;
                 uint32_t values[2];
@@ -429,129 +444,79 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode)
 
         render_layout(conn);
 
-        if (container->currently_focused != NULL)
-                set_focus(conn, container->currently_focused, true);
-}
+        if (container->currently_focused != NULL) {
+                /* We need to make sure that this client is above *each* of the
+                 * other clients in this container */
+                Client *last_focused = get_last_focused_client(conn, container, container->currently_focused);
 
-/*
- * Warps the pointer into the given client (in the middle of it, to be specific), therefore
- * selecting it
- *
- */
-void warp_pointer_into(xcb_connection_t *conn, Client *client) {
-        int mid_x = client->rect.width / 2,
-            mid_y = client->rect.height / 2;
-        xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
-}
+                CIRCLEQ_FOREACH(client, &(container->clients), clients) {
+                        if (client == container->currently_focused || client == last_focused)
+                                continue;
 
-/*
- * Toggles fullscreen mode for the given client. It updates the data structures and
- * reconfigures (= resizes/moves) the client and its frame to the full size of the
- * screen. When leaving fullscreen, re-rendering the layout is forced.
- *
- */
-void toggle_fullscreen(xcb_connection_t *conn, Client *client) {
-        /* clients without a container (docks) cannot be focused */
-        assert(client->container != NULL);
-
-        Workspace *workspace = client->container->workspace;
-
-        if (!client->fullscreen) {
-                if (workspace->fullscreen_client != NULL) {
-                        LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
-                        return;
+                        LOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame);
+                        uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
+                        xcb_configure_window(conn, client->frame,
+                                             XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
                 }
-                client->fullscreen = true;
-                workspace->fullscreen_client = client;
-                LOG("Entering fullscreen mode...\n");
-                /* We just entered fullscreen mode, let’s configure the window */
-                 uint32_t mask = XCB_CONFIG_WINDOW_X |
-                                 XCB_CONFIG_WINDOW_Y |
-                                 XCB_CONFIG_WINDOW_WIDTH |
-                                 XCB_CONFIG_WINDOW_HEIGHT;
-                uint32_t values[4] = {workspace->rect.x,
-                                      workspace->rect.y,
-                                      workspace->rect.width,
-                                      workspace->rect.height};
-
-                LOG("child itself will be at %dx%d with size %dx%d\n",
-                                values[0], values[1], values[2], values[3]);
-
-                xcb_configure_window(conn, client->frame, mask, values);
-
-                /* Child’s coordinates are relative to the parent (=frame) */
-                values[0] = 0;
-                values[1] = 0;
-                xcb_configure_window(conn, client->child, mask, values);
-
-                /* Raise the window */
-                values[0] = XCB_STACK_MODE_ABOVE;
-                xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
-
-                Rect child_rect = workspace->rect;
-                child_rect.x = child_rect.y = 0;
-                fake_configure_notify(conn, child_rect, client->child);
-        } else {
-                LOG("leaving fullscreen mode\n");
-                client->fullscreen = false;
-                workspace->fullscreen_client = NULL;
-                /* Because the coordinates of the window haven’t changed, it would not be
-                   re-configured if we don’t set the following flag */
-                client->force_reconfigure = true;
-                /* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */
-                render_layout(conn);
-        }
-
-        xcb_flush(conn);
-}
-
-/*
- * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
- *
- */
-static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
-        xcb_get_property_cookie_t cookie;
-        xcb_get_wm_protocols_reply_t protocols;
-        bool result = false;
 
-        cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
-        if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
-                return false;
-
-        /* Check if the client’s protocols have the requested atom set */
-        for (uint32_t i = 0; i < protocols.atoms_len; i++)
-                if (protocols.atoms[i] == atom)
-                        result = true;
+                if (last_focused != NULL) {
+                        LOG("Putting last_focused directly underneath the currently focused\n");
+                        uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
+                        xcb_configure_window(conn, last_focused->frame,
+                                             XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
+                }
 
-        xcb_get_wm_protocols_reply_wipe(&protocols);
 
-        return result;
+                set_focus(conn, container->currently_focused, true);
+        }
 }
 
 /*
- * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
+ * Gets the first matching client for the given window class/window title.
+ * If the paramater specific is set to a specific client, only this one
+ * will be checked.
  *
  */
-void kill_window(xcb_connection_t *conn, Client *window) {
-        /* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */
-        if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) {
-                LOG("Killing window the hard way\n");
-                xcb_kill_client(conn, window->child);
-                return;
+Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle,
+                            Client *specific) {
+        char *to_class, *to_title, *to_title_ucs = NULL;
+        int to_title_ucs_len = 0;
+        Client *matching = NULL;
+
+        to_class = sstrdup(window_classtitle);
+
+        /* If a title was specified, split both strings at the slash */
+        if ((to_title = strstr(to_class, "/")) != NULL) {
+                *(to_title++) = '\0';
+                /* Convert to UCS-2 */
+                to_title_ucs = convert_utf8_to_ucs2(to_title, &to_title_ucs_len);
+        }
+
+        /* If we were given a specific client we only check if that one matches */
+        if (specific != NULL) {
+                if (client_matches_class_name(specific, to_class, to_title, to_title_ucs, to_title_ucs_len))
+                        matching = specific;
+                goto done;
         }
 
-        xcb_client_message_event_t ev;
+        LOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title);
+        for (int workspace = 0; workspace < 10; workspace++) {
+                if (workspaces[workspace].screen == NULL)
+                        continue;
 
-        memset(&ev, 0, sizeof(xcb_client_message_event_t));
+                Client *client;
+                SLIST_FOREACH(client, &(workspaces[workspace].focus_stack), focus_clients) {
+                        LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name);
+                        if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len))
+                                continue;
 
-        ev.response_type = XCB_CLIENT_MESSAGE;
-        ev.window = window->child;
-        ev.type = atoms[WM_PROTOCOLS];
-        ev.format = 32;
-        ev.data.data32[0] = atoms[WM_DELETE_WINDOW];
-        ev.data.data32[1] = XCB_CURRENT_TIME;
+                        matching = client;
+                        goto done;
+                }
+        }
 
-        LOG("Sending WM_DELETE to the client\n");
-        xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
-        xcb_flush(conn);
+done:
+        free(to_class);
+        FREE(to_title_ucs);
+        return matching;
 }
index 10608ac4fc733e5f367512d54d26830afcfb31b2..4062f648e35254125bf448b90c044165e1e1af48 100644 (file)
--- a/src/xcb.c
+++ b/src/xcb.c
@@ -22,7 +22,6 @@
 #include "xcb.h"
 
 TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts);
-SLIST_HEAD(colorpixel_head, Colorpixel) colorpixels;
 unsigned int xcb_numlock_mask;
 
 /*
@@ -74,42 +73,14 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern) {
  *
  */
 uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
-        /* Lookup this colorpixel in the cache */
-        struct Colorpixel *colorpixel;
-        SLIST_FOREACH(colorpixel, &(colorpixels), colorpixels)
-                if (strcmp(colorpixel->hex, hex) == 0)
-                        return colorpixel->pixel;
-
-        #define RGB_8_TO_16(i) (65535 * ((i) & 0xFF) / 255)
         char strgroups[3][3] = {{hex[1], hex[2], '\0'},
                                 {hex[3], hex[4], '\0'},
                                 {hex[5], hex[6], '\0'}};
-        int rgb16[3] = {RGB_8_TO_16(strtol(strgroups[0], NULL, 16)),
-                        RGB_8_TO_16(strtol(strgroups[1], NULL, 16)),
-                        RGB_8_TO_16(strtol(strgroups[2], NULL, 16))};
-
-        xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
-        xcb_alloc_color_reply_t *reply;
-        
-        reply = xcb_alloc_color_reply(conn, xcb_alloc_color(conn, root_screen->default_colormap,
-                                                            rgb16[0], rgb16[1], rgb16[2]), NULL);
-
-        if (!reply) {
-                LOG("Could not allocate color\n");
-                exit(1);
-        }
-
-        uint32_t pixel = reply->pixel;
-        free(reply);
-
-        /* Store the result in the cache */
-        struct Colorpixel *cache_pixel = scalloc(sizeof(struct Colorpixel));
-        cache_pixel->hex = sstrdup(hex);
-        cache_pixel->pixel = pixel;
-
-        SLIST_INSERT_HEAD(&(colorpixels), cache_pixel, colorpixels);
+        uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
+                             (strtol(strgroups[1], NULL, 16)),
+                             (strtol(strgroups[2], NULL, 16))};
 
-        return pixel;
+        return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
 }
 
 /*
@@ -267,10 +238,11 @@ void xcb_get_numlock_mask(xcb_connection_t *conn) {
         /* For now, we only use the first keysymbol. */
         xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
         xcb_keycode_t numlock = *numlock_syms;
+        free(numlock_syms);
 #endif
 
         /* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */
-        for (mask = 0; mask < sizeof(masks); mask++)
+        for (mask = 0; mask < 8; mask++)
                 for (i = 0; i < reply->keycodes_per_modifier; i++)
                         if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock)
                                 xcb_numlock_mask = masks[mask];
@@ -278,3 +250,12 @@ void xcb_get_numlock_mask(xcb_connection_t *conn) {
         xcb_key_symbols_free(keysyms);
         free(reply);
 }
+
+/*
+ * Raises the given window (typically client->frame) above all other windows
+ *
+ */
+void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) {
+        uint32_t values[] = { XCB_STACK_MODE_ABOVE };
+        xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values);
+}
index 818df00abf70114e1c5fb8bfbf9a5da2efec493a..f1becf8c423c0677e133bd060e0433ea901ff565 100644 (file)
@@ -100,6 +100,31 @@ i3Screen *get_screen_most(direction_t direction) {
         return candidate;
 }
 
+static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspace *workspace) {
+        i3Font *font = load_font(conn, config.font);
+
+        workspace->screen = screen;
+        screen->current_workspace = workspace->num;
+
+        /* Create a bar for each screen */
+        Rect bar_rect = {screen->rect.x,
+                         screen->rect.height - (font->height + 6),
+                         screen->rect.x + screen->rect.width,
+                         font->height + 6};
+        uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
+        uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS};
+        screen->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
+        screen->bargc = xcb_generate_id(conn);
+        xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0);
+
+        SLIST_INIT(&(screen->dock_clients));
+
+        /* Copy dimensions */
+        memcpy(&(workspace->rect), &(screen->rect), sizeof(Rect));
+        LOG("that is virtual screen at %d x %d with %d x %d\n",
+                        screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
+}
+
 /*
  * Fills virtual_screens with exactly one screen with width/height of the whole X server.
  *
@@ -114,6 +139,10 @@ static void disable_xinerama(xcb_connection_t *conn) {
         s->rect.width = root_screen->width_in_pixels;
         s->rect.height = root_screen->height_in_pixels;
 
+        num_screens = 1;
+        s->num = 0;
+        initialize_screen(conn, s, &(workspaces[0]));
+
         TAILQ_INSERT_TAIL(virtual_screens, s, screens);
 
         xinerama_enabled = false;
@@ -170,31 +199,6 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis
         }
 }
 
-static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspace *workspace) {
-        i3Font *font = load_font(conn, config.font);
-
-        workspace->screen = screen;
-        screen->current_workspace = workspace->num;
-
-        /* Create a bar for each screen */
-        Rect bar_rect = {screen->rect.x,
-                         screen->rect.height - (font->height + 6),
-                         screen->rect.x + screen->rect.width,
-                         font->height + 6};
-        uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
-        uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS};
-        screen->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
-        screen->bargc = xcb_generate_id(conn);
-        xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0);
-
-        SLIST_INIT(&(screen->dock_clients));
-
-        /* Copy dimensions */
-        memcpy(&(workspace->rect), &(screen->rect), sizeof(Rect));
-        LOG("that is virtual screen at %d x %d with %d x %d\n",
-                        screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
-}
-
 /*
  * We have just established a connection to the X server and need the initial Xinerama
  * information to setup workspaces for each screen.
index a7f95852e8fda88b78be8ba7055492e9b3b01ff0..a46ea8ebab6d2d943803a64dbea99f7f29760d39 100644 (file)
@@ -78,7 +78,6 @@ li {
   <li>
     <a href="/screenshots/i3-5.png">i3 v3.α-bf2</a>, mc, vim, xosview, mplayer, irssi, gajim, i3status
   </li>
-
 </ul>
 
 <h2>Screencasts</h2>