-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
* 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
* 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
LDFLAGS += -lxcb-icccm
LDFLAGS += -lxcb-xinerama
LDFLAGS += -lX11
+LDFLAGS += -lev
LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib
ifeq ($(UNAME),NetBSD)
--- /dev/null
+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
+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
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
--- /dev/null
+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
# Add here commands to compile the package.
$(MAKE)
$(MAKE) -C man
+ $(MAKE) -C docs
touch $@
-all: hacking-howto.html debugging.html
+all: hacking-howto.html debugging.html userguide.html
hacking-howto.html: hacking-howto
asciidoc -a toc -n $<
debugging.html: debugging
asciidoc -n $<
+userguide.html: userguide
+ asciidoc -a toc -n $<
clean:
rm -f */*.{aux,log,toc,bm,pdf,dvi}
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
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.
* ``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())
=== 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)
== 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
--- /dev/null
+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
+--------------------------------------
# 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
# 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
--- /dev/null
+/*
+ * 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
+/*
+ * 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;
};
/**
* configuration file.
*
*/
-void load_configuration(const char *override_configfile);
+void load_configuration(xcb_connection_t *conn, const char *override_configfile);
#endif
*
* i3 - an improved dynamic tiling window manager
*
- * (c) 2009 Michael Stapelberg and contributors
+ * © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
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;
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)
*
*/
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;
/* 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;
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;
CIRCLEQ_ENTRY(Client) clients;
SLIST_ENTRY(Client) dock_clients;
SLIST_ENTRY(Client) focus_clients;
+ TAILQ_ENTRY(Client) floating_clients;
};
/*
--- /dev/null
+/*
+ * 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
*
*/
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)
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
*
* 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
*/
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)
--- /dev/null
+/*
+ * 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
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; \
} \
* 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
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.
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
_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,
*/
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
<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>
i3(1)
=====
Michael Stapelberg <michael+i3@stapelberg.de>
-v3.alpha-bf1, May 2009
+v3.beta, May 2009
== NAME
== SYNOPSIS
-i3 [-c configfile]
+i3 [-c configfile] [-a]
+
+== OPTIONS
+
+-c::
+Specifies an alternate configuration file path
+
+-a::
+Disables autostart.
== DESCRIPTION
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).
# 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
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
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
--- /dev/null
+/*
+ * 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);
+}
#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 */
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);
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;
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);
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);
/* 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);
}
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);
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);
}
/*
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;
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);
}
/*
/* 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);
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)
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);
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", ×) != 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;
/* 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') {
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;
}
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;
}
}
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;
}
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");
#include "i3.h"
#include "util.h"
#include "config.h"
+#include "xcb.h"
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.
* 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); \
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)
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)
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) { \
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;
}
--- /dev/null
+/*
+ * 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);
+}
#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
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;
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;
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;
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))
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);
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;
}
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)
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;
}
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);
/* 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) {
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;
}
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);
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);
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)
*
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};
(!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;
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)
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) ||
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
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 */
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;
+}
#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
*
*/
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 */
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)
}
/* 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,
* 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);
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;
}
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 */
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);
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++;
#include <xcb/xcb_icccm.h>
#include <xcb/xinerama.h>
+#include <ev.h>
+
#include "config.h"
#include "data.h"
#include "debug.h"
#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;
/* 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);
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;
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;
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);
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);
}
/* 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 */
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);
/* 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");
}
}
+ /* 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);
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;
--- /dev/null
+/*
+ * 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);
+}
#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) {
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;
}
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;
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);
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]));
}
#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);
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.
/* 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);
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)) {
/* 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);
/* 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];
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;
}
#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;
/*
*
*/
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];
}
/*
/* 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];
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);
+}
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.
*
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;
}
}
-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.
<li>
<a href="/screenshots/i3-5.png">i3 v3.α-bf2</a>, mc, vim, xosview, mplayer, irssi, gajim, i3status
</li>
-
</ul>
<h2>Screencasts</h2>