Merge branch 'next' (3.β is stable now)

next
Michael Stapelberg 2009-06-26 13:27:06 +02:00
commit 28c4b045d6
43 changed files with 3218 additions and 764 deletions

50
CMDMODE
View File

@ -1,36 +1,44 @@
left := <h> | <cursor-left>
---------------------
- Command mode
---------------------
This is the grammar for the command mode (your configuration file uses these commands, too).
left := <h> | <cursor-left>
right := <l> | <cursor-right>
up := <j> | <cursor-up>
down := <k> | <cursor-down>
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>
oder
with := <w> { [ <times> ] <where> }+ <space> <cmd>
oder
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> ]
Beispiele:
you can cancel command mode by pressing escape anytime.
Fenster links neben dem aktuellen auswählen:
Some examples:
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

View File

@ -5,6 +5,7 @@ In that case, please try using the versions mentioned below until a fix is provi
* xcb-proto-1.3 (2008-12-10)
* libxcb-1.1.93 (2008-12-11)
* xcb-util-0.3.3 (2009-01-31)
* libev
* asciidoc >= 8.3.0 for docs/hacking-howto
* asciidoc, xmlto, docbook-xml for man/i3.man
* Xlib, the one that comes with your X-Server
@ -14,9 +15,10 @@ Recommendations:
* dmenu for launching applications
Get the libraries from:
http://xcb.freedesktop.org/dist/xcb-proto-1.3.tar.bz2
http://xcb.freedesktop.org/dist/xcb-proto-1.5.tar.bz2
http://xcb.freedesktop.org/dist/libxcb-1.1.93.tar.bz2
http://xcb.freedesktop.org/dist/xcb-util-0.3.3.tar.bz2
http://xcb.freedesktop.org/dist/xcb-util-0.3.5.tar.bz2
http://libev.schmorp.de/
http://i3.zekjur.net/i3lock/
http://tools.suckless.org/dmenu

View File

@ -37,6 +37,7 @@ LDFLAGS += -lxcb-aux
LDFLAGS += -lxcb-icccm
LDFLAGS += -lxcb-xinerama
LDFLAGS += -lX11
LDFLAGS += -lev
LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib
ifeq ($(UNAME),NetBSD)

51
RELEASE-NOTES-3.b Normal file
View File

@ -0,0 +1,51 @@
Release notes for i3 v3.β
-----------------------------
This is the second version (3.β, transcribed 3.b) of i3. It is considered stable.
The most important change probably is the implementation of floating clients,
primarily useful for dialog/toolbar/popup/splash windows. When using i3 for
managing floating windows other than the ones mentioned beforehand, please
keep in mind that i3 is a tiling window manager in the first place and thus
you might better use a "traditional" window manager when having to deal a
lot with floating windows.
Now that youre warned, lets 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 users 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

27
debian/changelog vendored
View File

@ -1,3 +1,30 @@
i3-wm (3.b-1) unstable; urgency=low
* Bugfix: Correctly handle col-/rowspanned containers when setting focus.
* Bugfix: Correctly handle col-/rowspanned containers when snapping.
* Bugfix: Force reconfiguration of all windows on workspaces which are
re-assigned because a screen was detached.
* Bugfix: Several bugs in resizing table columns fixed.
* Bugfix: Resizing should now work correctly in all cases.
* Bugfix: Correctly re-assign dock windows when workspace is destroyed.
* Bugfix: Correctly handle Mode_switch modifier.
* Bugfix: Don't raise clients in fullscreen mode.
* Bugfix: Re-assign dock windows to different workspaces when a workspace
is detached.
* Bugfix: Fix crash because of workspace-pointer which did not get updated
* Bugfix: Correctly initialize screen when Xinerama is disabled.
* Bugfix: Fullscreen window movement and focus problems fixed
* Implement jumping to other windows by specifying their position or
window class/title.
* Implement jumping back by using the focus stack.
* Implement autostart (exec-command in configuration file).
* Implement floating.
* Implement automatically assigning clients on specific workspaces.
* Implement variables in configfile.
* Colors are now configurable.
-- Michael Stapelberg <michael@stapelberg.de> Fri, 26 Jun 2009 04:42:23 +0200
i3-wm (3.a-bf2-1) unstable; urgency=low
* Bugfix: Don't crash when setting focus

20
debian/control vendored
View File

@ -1,26 +1,28 @@
Source: i3-wm
Section: utils
Priority: optional
Priority: extra
Maintainer: Michael Stapelberg <michael@stapelberg.de>
DM-Upload-Allowed: yes
Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4-1), xmlto, docbook-xml, pkg-config
Standards-Version: 3.8.0
Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4-1), xmlto, docbook-xml, pkg-config, libev-dev
Standards-Version: 3.8.2
Homepage: http://i3.zekjur.net/
Package: i3
Architecture: any
Priority: optional
Priority: extra
Section: x11
Depends: i3-wm, ${misc:Depends}
Recommends: i3lock, dwm-tools
Description: metapackage (i3 window manager, i3lock (screen locker), dwm-tools)
Recommends: i3lock, dwm-tools, i3status
Description: metapackage (i3 window manager, screen locker, menu, statusbar)
This metapackage installs the i3 window manager (i3-wm), the i3lock screen
locker (slightly improved version of slock) and dwm-tools which contains dmenu.
These are all the tools you need to use the i3 window manager efficiently.
locker (slightly improved version of slock), dwm-tools which contains dmenu
and i3status, which displays useful information about your system in
combination with dzen2. These are all the tools you need to use the i3 window
manager efficiently.
Package: i3-wm
Architecture: any
Priority: optional
Priority: extra
Section: x11
Depends: ${shlibs:Depends}, ${misc:Depends}
Provides: x-window-manager

8
debian/i3-wm.docs vendored Normal file
View File

@ -0,0 +1,8 @@
docs/debugging.html
docs/hacking-howto.html
docs/userguide.html
docs/bigpicture.png
docs/single_terminal.png
docs/snapping.png
docs/two_columns.png
docs/two_terminals.png

1
debian/rules vendored
View File

@ -19,6 +19,7 @@ build:
# Add here commands to compile the package.
$(MAKE)
$(MAKE) -C man
$(MAKE) -C docs
touch $@

View File

@ -1,5 +1,5 @@
all: hacking-howto.html debugging.html
all: hacking-howto.html debugging.html userguide.html
hacking-howto.html: hacking-howto
asciidoc -a toc -n $<
@ -7,6 +7,8 @@ hacking-howto.html: hacking-howto
debugging.html: debugging
asciidoc -n $<
userguide.html: userguide
asciidoc -a toc -n $<
clean:
rm -f */*.{aux,log,toc,bm,pdf,dvi}

View File

@ -84,35 +84,9 @@ gdb $(which i3) core.i3.3849
Then, generate a backtrace using:
---------
backtrace
---------
Also, getting an overview of the local variables might help:
-----------
info locals
-----------
If your backtrace looks like this:
---------------------------------------------------------------------------------------------------
(gdb) backtrace
#0 0x041b1a01 in vfprintf () from /lib/libc.so.6
#1 0x041b2f80 in vprintf () from /lib/libc.so.6
#2 0x080555de in slog (fmt=0x8059ba0 "%s:%s:%d - Name should change to \"%s\"\n") at src/util.c:60
#3 0x0804fa73 in handle_windowname_change_legacy (data=0x0, conn=0x42da908,
state=0 '\0', window=8389918, atom=39, prop=0x4303f90) at src/handlers.c:752
#4 0x0406cace in ?? () from /usr/lib/libxcb-property.so.1
#5 0x00000000 in ?? ()
---------------------------------------------------------------------------------------------------
you need to find the first frame which actually belongs to i3 code. You can easily spot them, as
their filename starts with src/ and has a line number. In this case, frame 2 would be the correct
frame, so before getting the local variables, switch to frame 2:
-----------
frame 2
info locals
-----------
--------------
backtrace full
--------------
== Sending bugreports/debugging on IRC

View File

@ -107,23 +107,38 @@ Contains forward definitions for all public functions, aswell as doxygen-compati
comments (so if you want to get a bit more of the big picture, either browse all
header files or use doxygen if you prefer that).
src/client.c::
Contains all functions which are specific to a certain client (make it
fullscreen, see if its class/name matches a pattern, kill it, …).
src/commands.c::
Parsing commands
Parsing commands and actually execute them (focussing, moving, …).
src/config.c::
Parses the configuration file
Parses the configuration file.
src/debug.c::
Contains debugging functions to print unhandled X events
Contains debugging functions to print unhandled X events.
src/floating.c::
Contains functions for floating mode (mostly resizing/dragging).
src/handlers.c::
Contains all handlers for all kind of X events
Contains all handlers for all kind of X events (new window title, new hints,
unmapping, key presses, button presses, …).
src/layout.c::
Renders your layout (screens, workspaces, containers)
Renders your layout (screens, workspaces, containers).
src/mainx.c::
Initializes the window manager
Initializes the window manager.
src/manage.c::
Looks at existing or new windows and decides whether to manage them. If so, it
reparents the window and inserts it into our data structures.
src/resize.c::
Contains the functions to resize columns/rows in the table.
src/resize.c::
Contains the functions to resize columns/rows in the table.
@ -213,7 +228,7 @@ chosen for those:
* ``conn'' is the xcb_connection_t
* ``event'' is the event of the particular type
* ``container'' names a container
* ``client'' names a client, for example when using a `CIRCLEQ_FOREACH`
* ``client'' names a client, for example when using a +CIRCLEQ_FOREACH+
== Startup (src/mainx.c, main())
@ -361,9 +376,26 @@ when rendering.
=== Resizing containers
By clicking and dragging the border of a container, you can resize it freely.
By clicking and dragging the border of a container, you can resize the whole column
(respectively row) which this container is in. This is necessary to keep the table
layout working and consistent.
TODO
Currently, only vertical resizing is implemented.
The resizing works similarly to the resizing of floating windows or movement of floating
windows:
* A new, invisible window with the size of the root window is created (+grabwin+)
* Another window, 2px width and as high as your screen (or vice versa for horizontal
resizing) is created. Its background color is the border color and it is only
there to signalize the user how big the container will be (it creates the impression
of dragging the border out of the container).
* The +drag_pointer+ function of +src/floating.c+ is called to grab the pointer and
enter an own event loop which will pass all events (expose events) but motion notify
events. This function then calls the specified callback (+resize_callback+) which
does some boundary checking and moves the helper window. As soon as the mouse
button is released, this loop will be terminated.
* The new width_factor for each involved column (respectively row) will be calculated.
== User commands / commandmode (src/commands.c)
@ -396,7 +428,8 @@ direction to move a window respectively or snap.
== Using git / sending patches
For a short introduction into using git, see TODO.
For a short introduction into using git, see http://www.spheredev.org/wiki/Git_for_the_lazy
or, for more documentation, see http://git-scm.com/documentation
When you want to send a patch because you fixed a bug or implemented a cool feature (please
talk to us before working on features to see whether they are maybe already implemented, not

BIN
docs/single_terminal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
docs/snapping.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
docs/two_columns.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
docs/two_terminals.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

371
docs/userguide Normal file
View File

@ -0,0 +1,371 @@
i3 Users 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
Ill 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, lets 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 doesnt 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 windows 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 cant 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), youd 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 youve 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
--------------------------------------

View File

@ -1,11 +1,17 @@
# This configuration uses Mod1 and Mod3. Make sure they are mapped properly using xev(1)
# and xmodmap(1). Usually, Mod1 is Alt (Alt_L) and Mod3 is Windows (Super_L)
# Tell i3 about your preferred terminal. You can refer to this as $terminal
# later. It is recommended to set this option to allow i3 to open a terminal
# containing the introduction on first start.
terminal /usr/bin/urxvt
# ISO 10646 = Unicode
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# Use Mouse+Mod1 to drag floating windows to their wanted position
floating_modifier Mod1
# Fullscreen (Mod1+f)
bind Mod1+41 f
@ -15,6 +21,13 @@ bind Mod1+43 s
# Default (Mod1+e)
bind Mod1+26 d
# Toggle tiling/floating of the current window (Mod1+Shift+Space)
bind Mod1+Shift+65 t
# Go into the tiling layer / floating layer, depending on whether
# the current window is tiling / floating (Mod1+t)
bind Mod1+28 focus ft
# Focus (Mod1+j/k/l/;)
bind Mod1+44 h
bind Mod1+45 j

78
include/client.h Normal file
View File

@ -0,0 +1,78 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include <xcb/xcb.h>
#include "data.h"
#ifndef _CLIENT_H
#define _CLIENT_H
/**
* Removes the given client from the container, either because it will be inserted into another
* one or because it was unmapped
*
*/
void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container, bool remove_from_focusstack);
/**
* Warps the pointer into the given client (in the middle of it, to be specific), therefore
* selecting it
*
*/
void client_warp_pointer_into(xcb_connection_t *conn, Client *client);
/**
* Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
*
*/
void client_kill(xcb_connection_t *conn, Client *window);
/**
* Checks if the given window class and title match the given client
* Window title is passed as "normal" string and as UCS-2 converted string for
* matching _NET_WM_NAME capable clients as well as those using legacy hints.
*
*/
bool client_matches_class_name(Client *client, char *to_class, char *to_title,
char *to_title_ucs, int to_title_ucs_len);
/**
* Enters fullscreen mode for the given client. This is called by toggle_fullscreen
* and when moving a fullscreen client to another screen.
*
*/
void client_enter_fullscreen(xcb_connection_t *conn, Client *client);
/**
* Toggles fullscreen mode for the given client. It updates the data structures and
* reconfigures (= resizes/moves) the client and its frame to the full size of the
* screen. When leaving fullscreen, re-rendering the layout is forced.
*
*/
void client_toggle_fullscreen(xcb_connection_t *conn, Client *client);
/**
* Sets the position of the given client in the X stack to the highest (tiling layer is always
* on the same position, so this doesnt 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

View File

@ -1,12 +1,56 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* include/config.h: Contains all structs/variables for
* the configurable part of i3
*
*/
#ifndef _CONFIG_H
#define _CONFIG_H
#include "queue.h"
typedef struct Config Config;
extern Config config;
struct Colortriple {
uint32_t border;
uint32_t background;
uint32_t text;
};
struct Variable {
char *key;
char *value;
SLIST_ENTRY(Variable) variables;
};
struct Config {
const char *terminal;
const char *font;
const char *terminal;
const char *font;
/** The modifier which needs to be pressed in combination with your mouse
* buttons to do things with floating windows (move, resize) */
uint32_t floating_modifier;
/* Color codes are stored here */
struct config_client {
struct Colortriple focused;
struct Colortriple focused_inactive;
struct Colortriple unfocused;
} client;
struct config_bar {
struct Colortriple focused;
struct Colortriple unfocused;
} bar;
};
/**
@ -16,6 +60,6 @@ struct Config {
* configuration file.
*
*/
void load_configuration(const char *override_configfile);
void load_configuration(xcb_connection_t *conn, const char *override_configfile);
#endif

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -161,12 +161,21 @@ struct Workspace {
int current_row;
int current_col;
/* Should clients on this workspace be automatically floating? */
bool auto_float;
/* Are the floating clients on this workspace currently hidden? */
bool floating_hidden;
Client *fullscreen_client;
/* The focus stack contains the clients in the correct order of focus so that
the focus can be reverted correctly when a client is closed */
SLIST_HEAD(focus_stack_head, Client) focus_stack;
/* This tail queue contains the floating clients in order of when they were first
* set to floating (new floating clients are just appended) */
TAILQ_HEAD(floating_clients_head, Client) floating_clients;
/* Backpointer to the screen this workspace is on */
i3Screen *screen;
@ -197,6 +206,30 @@ struct Binding {
TAILQ_ENTRY(Binding) bindings;
};
/*
* Holds a command specified by an exec-line in the config (see src/config.c)
*
*/
struct Autostart {
/* Command, like in command mode */
char *command;
TAILQ_ENTRY(Autostart) autostarts;
};
/*
* Holds an assignment for a given window class/title to a specific workspace
* (see src/config.c)
*
*/
struct Assignment {
char *windowclass_title;
/* floating is true if this was an assignment to the special workspace "~".
* Matching clients will be put into floating mode automatically. */
bool floating;
int workspace;
TAILQ_ENTRY(Assignment) assignments;
};
/*
* Data structure for cached font information:
* - font id in X11 (load it once)
@ -221,6 +254,10 @@ struct Font {
*
*/
struct Client {
/* initialized will be set to true if the client was fully initialized by
* manage_window() and all functions can be used normally */
bool initialized;
/* if you set a client to floating and set it back to managed, it does remember its old
position and *tries* to get back there */
Cell old_position;
@ -232,6 +269,8 @@ struct Client {
/* x, y, width, height of the frame */
Rect rect;
/* Position in floating mode and in tiling mode are saved separately */
Rect floating_rect;
/* x, y, width, height of the child (relative to its frame) */
Rect child_rect;
@ -255,9 +294,18 @@ struct Client {
legacy window names are ignored. */
bool uses_net_wm_name;
/* Holds the WM_CLASS, useful for matching the client in commands */
char *window_class;
/* fullscreen is pretty obvious */
bool fullscreen;
/* floating? (= not in tiling layout) This cannot be simply a bool because we want to keep track
* of whether the status was set by the application (by setting WM_CLASS to tools for example) or
* by the user. The users choice overwrites automatic mode, of course. The order of the values
* is important because we check with >= FLOATING_AUTO_ON if a client is floating. */
enum { FLOATING_AUTO_OFF = 0, FLOATING_USER_OFF = 1, FLOATING_AUTO_ON = 2, FLOATING_USER_ON = 3 } floating;
/* Ensure TITLEBAR_TOP maps to 0 because we use calloc for initialization later */
enum { TITLEBAR_TOP = 0, TITLEBAR_LEFT, TITLEBAR_RIGHT, TITLEBAR_BOTTOM, TITLEBAR_OFF } titlebar_position;
@ -283,6 +331,7 @@ struct Client {
CIRCLEQ_ENTRY(Client) clients;
SLIST_ENTRY(Client) dock_clients;
SLIST_ENTRY(Client) focus_clients;
TAILQ_ENTRY(Client) floating_clients;
};
/*

87
include/floating.h Normal file
View File

@ -0,0 +1,87 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#ifndef _FLOATING_H
#define _FLOATING_H
/** Callback for dragging */
typedef void(*callback_t)(Rect*, uint32_t, uint32_t);
/** On which border was the dragging initiated? */
typedef enum { BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM} border_t;
/**
* Enters floating mode for the given client.
* Correctly takes care of the position/size (separately stored for tiling/floating mode)
* and repositions/resizes/redecorates the client.
*
* If the automatic flag is set to true, this was an automatic update by a change of the
* window class from the application which can be overwritten by the user.
*
*/
void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic);
/**
* Removes the floating client from its workspace and attaches it to the new workspace.
* This is centralized here because it may happen if you move it via keyboard and
* if you move it using your mouse.
*
*/
void floating_assign_to_workspace(Client *client, Workspace *new_workspace);
/**
* Called whenever the user clicks on a border (not the titlebar!) of a floating window.
* Determines on which border the user clicked and launches the drag_pointer function
* with the resize_callback.
*
*/
int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event);
/**
* Called when the user clicked on the titlebar of a floating window.
* Calls the drag_pointer function with the drag_window callback
*
*/
void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event);
/**
* Changes focus in the given direction for floating clients.
*
* Changing to the left/right means going to the previous/next floating client,
* changing to top/bottom means cycling through the Z-index.
*
*/
void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction);
/**
* Moves the client 10px to the specified direction.
*
*/
void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction);
/**
* Hides all floating clients (or show them if they are currently hidden) on
* the specified workspace.
*
*/
void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace);
/**
* This function grabs your pointer and lets you drag stuff around (borders).
* Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
* and the given callback will be called with the parameters specified (client,
* border on which the click originally was), the original rect of the client,
* the event and the new coordinates (x, y).
*
*/
void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
xcb_window_t confine_to, border_t border, callback_t callback);
#endif

View File

@ -85,7 +85,15 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
*
*/
int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop);
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop);
/**
* Store the window classes for jumping to them later.
*
*/
int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop);
/**
* Expose event means we should redraw our windows (= title bar)
@ -116,4 +124,14 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi
int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
xcb_atom_t name, xcb_get_property_reply_t *reply);
/**
* Handles the transient for hints set by a window, signalizing that this window is a popup window
* for some other window.
*
* See ICCCM 4.1.2.6 for more details
*
*/
int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
xcb_atom_t name, xcb_get_property_reply_t *reply);
#endif

View File

@ -3,7 +3,7 @@
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
@ -20,19 +20,16 @@
#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

View File

@ -36,6 +36,18 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
*/
void redecorate_window(xcb_connection_t *conn, Client *client);
/**
* Pushes the clients x and y coordinates to X11
*
*/
void reposition_client(xcb_connection_t *conn, Client *client);
/**
* Pushes the clients 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)

42
include/manage.h Normal file
View File

@ -0,0 +1,42 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* (c) 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include <xcb/xcb.h>
#include "data.h"
#ifndef _MANAGE_H
#define _MANAGE_H
/**
* Go through all existing windows (if the window manager is restarted) and manage them
*
*/
void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root);
/**
* Do some sanity checks and then reparent the window.
*
*/
void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
xcb_window_t window, window_attributes_t wa);
/**
* reparent_window() gets called when a new window was opened and becomes a child of the root
* window, or it gets called by us when we manage the already existing windows at startup.
*
* Essentially, this is the point where we take over control.
*
*/
void reparent_window(xcb_connection_t *conn, xcb_window_t child,
xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
int16_t x, int16_t y, uint16_t width, uint16_t height);
#endif

View File

@ -25,7 +25,7 @@
for (int cols = 0; cols < (workspace)->cols; cols++) \
for (int rows = 0; rows < (workspace)->rows; rows++)
#define FREE(pointer) do { \
if (pointer == NULL) { \
if (pointer != NULL) { \
free(pointer); \
pointer = NULL; \
} \
@ -54,7 +54,7 @@ void slog(char *fmt, ...);
* Prints the message (see printf()) to stderr, then exits the program.
*
*/
void die(char *fmt, ...);
void die(char *fmt, ...) __attribute__((__noreturn__));
/**
* Safe-wrapper around malloc which exits if malloc returns NULL (meaning that there
@ -123,13 +123,6 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes
*/
char *convert_utf8_to_ucs2(char *input, int *real_strlen);
/**
* Removes the given client from the container, either because it will be inserted into another
* one or because it was unmapped
*
*/
void remove_client_from_container(xcb_connection_t *conn, Client *client, Container *container);
/**
* Returns the client which comes next in focus stack (= was selected before) for
* the given container, optionally excluding the given client.
@ -148,6 +141,17 @@ Client *get_last_focused_client(xcb_connection_t *conn, Container *container, Cl
*/
void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws);
/**
* 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.
*
*/
void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws);
/**
* Sets the given client as focused by updating the data structures correctly,
* updating the X input focus and finally re-decorating both windows (to signalize
@ -170,24 +174,12 @@ void leave_stack_mode(xcb_connection_t *conn, Container *container);
void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode);
/**
* Warps the pointer into the given client (in the middle of it, to be specific), therefore
* selecting it
* 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 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
*
*/
void kill_window(xcb_connection_t *conn, Client *window);
Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle,
Client *specific);
#endif

View File

@ -49,6 +49,10 @@ enum { _NET_SUPPORTED = 0,
_NET_WM_STATE,
_NET_WM_WINDOW_TYPE,
_NET_WM_WINDOW_TYPE_DOCK,
_NET_WM_WINDOW_TYPE_DIALOG,
_NET_WM_WINDOW_TYPE_UTILITY,
_NET_WM_WINDOW_TYPE_TOOLBAR,
_NET_WM_WINDOW_TYPE_SPLASH,
_NET_WM_DESKTOP,
_NET_WM_STRUT_PARTIAL,
WM_PROTOCOLS,
@ -126,4 +130,10 @@ void fake_absolute_configure_notify(xcb_connection_t *conn, Client *client);
*/
void xcb_get_numlock_mask(xcb_connection_t *conn);
/**
* Raises the given window (typically client->frame) above all other windows
*
*/
void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window);
#endif

View File

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

View File

@ -1,7 +1,7 @@
i3(1)
=====
Michael Stapelberg <michael+i3@stapelberg.de>
v3.alpha-bf1, May 2009
v3.beta, May 2009
== NAME
@ -9,7 +9,15 @@ i3 - an improved dynamic, tiling window manager
== SYNOPSIS
i3 [-c configfile]
i3 [-c configfile] [-a]
== OPTIONS
-c::
Specifies an alternate configuration file path
-a::
Disables autostart.
== DESCRIPTION
@ -98,8 +106,17 @@ Enable stacking layout for the current container.
Mod1+e::
Enable default layout for the current container.
Mod1+Shift+Space::
Toggle tiling/floating for the current window.
Mod1+t::
Select the first tiling window if the current window is floating and vice-versa.
Mod1+Shift+q::
Kills the current client.
Kills the current window. This is equivalent to "clicking on the close button", meaning a polite
request to the application to close this window. For example, Firefox will save its session
upon such a request. If the application does not support that, the window will be killed and
it depends on the application what happens.
Mod1+Shift+r::
Restarts i3 in place (without losing any windows, but the layout).
@ -157,6 +174,13 @@ bind Mod1+43 s
# Default (Mod1+e)
bind Mod1+26 d
# Toggle tiling/floating of the current window (Mod1+Shift+Space)
bind Mod1+Shift+65 t
# Go into the tiling layer / floating layer, depending on whether
# the current window is tiling / floating (Mod1+t)
bind Mod1+28 focus ft
# Focus (Mod1+j/k/l/;)
bind Mod1+44 h
bind Mod1+45 j
@ -225,6 +249,9 @@ export LC_TELEPHONE=de_DE.UTF-8
export LC_MEASUREMENT=de_DE.UTF-8
export LC_IDENTIFICATION=de_DE.UTF-8
# Set background color
xsetroot -solid "#333333"
# Enable core dumps in case something goes wrong
ulimit -c unlimited
@ -238,6 +265,14 @@ exec /usr/bin/i3 >> ~/.i3/logfile
There is still lot of work to do. Please check our bugtracker for up-to-date information
about tasks which are still not finished.
== SEE ALSO
You should have a copy of the userguide (featuring nice screenshots/graphics which is why this
is not integrated into this manpage), the debugging guide and the "how to hack" guide. If you
are building from source, run +make -C docs+.
You can also access these documents online at http://i3.zekjur.net/
== AUTHOR
Michael Stapelberg and contributors

250
src/client.c Normal file
View File

@ -0,0 +1,250 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* client.c: holds all client-specific functions
*
*/
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
#include "data.h"
#include "i3.h"
#include "xcb.h"
#include "util.h"
#include "queue.h"
#include "layout.h"
#include "client.h"
/*
* Removes the given client from the container, either because it will be inserted into another
* one or because it was unmapped
*
*/
void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container, bool remove_from_focusstack) {
CIRCLEQ_REMOVE(&(container->clients), client, clients);
if (remove_from_focusstack)
SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients);
/* If the container will be empty now and is in stacking mode, we need to
unmap the stack_win */
if (CIRCLEQ_EMPTY(&(container->clients)) && container->mode == MODE_STACK) {
struct Stack_Window *stack_win = &(container->stack_win);
stack_win->rect.height = 0;
xcb_unmap_window(conn, stack_win->window);
}
}
/*
* Warps the pointer into the given client (in the middle of it, to be specific), therefore
* selecting it
*
*/
void client_warp_pointer_into(xcb_connection_t *conn, Client *client) {
int mid_x = client->rect.width / 2,
mid_y = client->rect.height / 2;
xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
}
/*
* Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
*
*/
static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
xcb_get_property_cookie_t cookie;
xcb_get_wm_protocols_reply_t protocols;
bool result = false;
cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
return false;
/* Check if the clients 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, were 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, lets 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);
/* Childs 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 its 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 havent changed, it would not be
re-configured if we dont 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 doesnt 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);
}

View File

@ -23,6 +23,9 @@
#include "layout.h"
#include "i3.h"
#include "xinerama.h"
#include "client.h"
#include "floating.h"
#include "xcb.h"
bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
/* If this container is empty, were done */
@ -57,10 +60,17 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
int new_row = current_row,
new_col = current_col;
Container *container = CUR_CELL;
Workspace *t_ws = c_ws;
/* Makes sure new_col and new_row are within bounds of the new workspace */
void check_colrow_boundaries() {
if (new_col >= t_ws->cols)
new_col = (t_ws->cols - 1);
if (new_row >= t_ws->rows)
new_row = (t_ws->rows - 1);
}
/* There always is a container. If not, current_col or current_row is wrong */
assert(container != NULL);
@ -103,6 +113,21 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
t_ws = &(workspaces[screen->current_workspace]);
new_row = (direction == D_UP ? (t_ws->rows - 1) : 0);
}
check_colrow_boundaries();
LOG("new_col = %d, new_row = %d\n", new_col, new_row);
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
LOG("Cell empty, checking for colspanned client above...\n");
for (int cols = 0; cols < new_col; cols += t_ws->table[cols][new_row]->colspan) {
if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1)))
continue;
new_col = cols;
break;
}
LOG("Fixed it to new col %d\n", new_col);
}
} else if (direction == D_LEFT || direction == D_RIGHT) {
if (direction == D_RIGHT && cell_exists(current_col+1, current_row))
new_col = current_col + t_ws->table[current_col][current_row]->colspan;
@ -130,16 +155,27 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
t_ws = &(workspaces[screen->current_workspace]);
new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0);
}
check_colrow_boundaries();
LOG("new_col = %d, new_row = %d\n", new_col, new_row);
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
LOG("Cell empty, checking for rowspanned client above...\n");
for (int rows = 0; rows < new_row; rows += t_ws->table[new_col][rows]->rowspan) {
if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1)))
continue;
new_row = rows;
break;
}
LOG("Fixed it to new row %d\n", new_row);
}
} else {
LOG("direction unhandled\n");
return;
}
/* Bounds checking */
if (new_col >= t_ws->cols)
new_col = (t_ws->cols - 1);
if (new_row >= t_ws->rows)
new_row = (t_ws->rows - 1);
check_colrow_boundaries();
if (t_ws->table[new_col][new_row]->currently_focused != NULL)
set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true);
@ -237,10 +273,13 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
new = CUR_TABLE[current_col][++current_row];
break;
/* To make static analyzers happy: */
default:
return;
}
/* Remove it from the old container and put it into the new one */
remove_client_from_container(conn, current_client, container);
client_remove_from_container(conn, current_client, container, true);
if (new->currently_focused != NULL)
CIRCLEQ_INSERT_AFTER(&(new->clients), new->currently_focused, current_client, clients);
@ -261,7 +300,8 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
/* Fix colspan/rowspan if itd overlap */
fix_colrowspan(conn, workspace);
render_layout(conn);
render_workspace(conn, workspace->screen, workspace);
xcb_flush(conn);
set_focus(conn, current_client, true);
}
@ -309,6 +349,9 @@ static void move_current_container(xcb_connection_t *conn, direction_t direction
new = CUR_TABLE[current_col][++current_row];
break;
/* To make static analyzers happy: */
default:
return;
}
LOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row);
@ -415,11 +458,66 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
container->rowspan++;
break;
}
/* To make static analyzers happy: */
default:
return;
}
render_layout(conn);
}
static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *client, int workspace) {
/* t_ws (to workspace) is just a container pointer to the workspace were 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 were 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);
}
/*
* Moves the currently selected window to the given workspace
*
@ -461,14 +559,14 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
assert(to_container != NULL);
remove_client_from_container(conn, current_client, container);
client_remove_from_container(conn, current_client, container, true);
if (container->workspace->fullscreen_client == current_client)
container->workspace->fullscreen_client = NULL;
/* TODO: insert it to the correct position */
CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients);
SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients);
if (current_client->fullscreen)
t_ws->fullscreen_client = current_client;
LOG("Moved.\n");
current_client->container = to_container;
@ -476,16 +574,26 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
container->currently_focused = to_focus;
to_container->currently_focused = current_client;
bool target_invisible = (to_container->workspace->screen->current_workspace != to_container->workspace->num);
/* If were moving it to an invisible screen, we need to unmap it */
if (to_container->workspace->screen->current_workspace != to_container->workspace->num) {
if (target_invisible) {
LOG("This workspace is not visible, unmapping\n");
xcb_unmap_window(conn, current_client->frame);
} else {
if (current_client->fullscreen) {
LOG("Calling client_enter_fullscreen again\n");
client_enter_fullscreen(conn, current_client);
}
}
/* delete all empty columns/rows */
cleanup_table(conn, container->workspace);
render_layout(conn);
if (!target_invisible)
set_focus(conn, current_client, true);
}
/*
@ -539,22 +647,25 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
/* Check if we need to change something or if were 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;
/* Unmap all clients of the current workspace */
unmap_workspace(conn, c_ws);
Workspace *old_workspace = c_ws;
c_ws = &workspaces[workspace-1];
/* Unmap all clients of the old workspace */
unmap_workspace(conn, old_workspace);
current_row = c_ws->current_row;
current_col = c_ws->current_col;
LOG("new current row = %d, current col = %d\n", current_row, current_col);
@ -566,6 +677,11 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
CIRCLEQ_FOREACH(client, &(c_ws->table[cols][rows]->clients), clients)
xcb_map_window(conn, client->frame);
/* Map all floating clients */
if (!c_ws->floating_hidden)
TAILQ_FOREACH(client, &(c_ws->floating_clients), floating_clients)
xcb_map_window(conn, client->frame);
/* Map all stack windows, if any */
struct Stack_Window *stack_win;
SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
@ -575,10 +691,11 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
ignore_enter_notify_forall(conn, c_ws, false);
/* Restore focus on the new workspace */
if (CUR_CELL->currently_focused != NULL) {
set_focus(conn, CUR_CELL->currently_focused, true);
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused != SLIST_END(&(c_ws->focus_stack))) {
set_focus(conn, last_focused, true);
if (need_warp) {
warp_pointer_into(conn, CUR_CELL->currently_focused);
client_warp_pointer_into(conn, last_focused);
xcb_flush(conn);
}
} else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
@ -586,12 +703,161 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
render_layout(conn);
}
/*
* Jumps to the given window class / title.
* Title is matched using strstr, that is, matches if it appears anywhere
* in the string. Regular expressions seem to be a bit overkill here. However,
* if we need them for something else somewhen, we may introduce them here, too.
*
*/
static void jump_to_window(xcb_connection_t *conn, const char *arguments) {
char *classtitle;
Client *client;
/* The first character is a quote, this was checked before */
classtitle = sstrdup(arguments+1);
/* The last character is a quote, we just set it to NULL */
classtitle[strlen(classtitle)-1] = '\0';
if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) {
free(classtitle);
LOG("No matching client found.\n");
return;
}
free(classtitle);
set_focus(conn, client, true);
}
/*
* Jump directly to the specified workspace, row and col.
* Great for reaching windows that you always keep in the same spot (hello irssi, I'm looking at you)
*
*/
static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
int ws, row, col;
int result;
result = sscanf(arguments, "%d %d %d", &ws, &col, &row);
LOG("Jump called with %d parameters (\"%s\")\n", result, arguments);
/* No match? Either no arguments were specified, or no numbers */
if (result < 1) {
LOG("At least one valid argument required\n");
return;
}
/* Move to the target workspace */
show_workspace(conn, ws);
if (result < 3)
return;
LOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows);
/* Move to row/col */
if (row >= c_ws->rows)
row = c_ws->rows - 1;
if (col >= c_ws->cols)
col = c_ws->cols - 1;
LOG("Jumping to col %d, row %d\n", col, row);
if (c_ws->table[col][row]->currently_focused != NULL)
set_focus(conn, c_ws->table[col][row]->currently_focused, true);
}
/*
* Travels the focus stack by the given number of times (or once, if no argument
* was specified). That is, selects the window you were in before you focused
* the current window.
*
* The special values 'floating' (select the next floating window), 'tiling'
* (select the next tiling window), 'ft' (if the current window is floating,
* select the next tiling window and vice-versa) are also valid
*
*/
static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
/* Start count at -1 to always skip the first element */
int times, count = -1;
Client *current;
bool floating_criteria;
/* Either its one of the special values… */
if (strcasecmp(arguments, "floating") == 0) {
floating_criteria = true;
} else if (strcasecmp(arguments, "tiling") == 0) {
floating_criteria = false;
} else if (strcasecmp(arguments, "ft") == 0) {
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused == SLIST_END(&(c_ws->focus_stack))) {
LOG("Cannot select the next floating/tiling client because there is no client at all\n");
return;
}
floating_criteria = !client_is_floating(last_focused);
} else {
/* …or a number was specified */
if (sscanf(arguments, "%u", &times) != 1) {
LOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments);
times = 1;
}
SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) {
if (++count < times) {
LOG("Skipping\n");
continue;
}
LOG("Focussing\n");
set_focus(conn, current, true);
break;
}
return;
}
/* Select the next client matching the criteria parsed above */
SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients)
if (client_is_floating(current) == floating_criteria) {
set_focus(conn, current, true);
break;
}
}
/*
* Goes through the list of arguments (for exec()) and checks if the given argument
* is present. If not, it copies the arguments (because we cannot realloc it) and
* appends the given argument.
*
*/
static char **append_argument(char **original, char *argument) {
int num_args;
for (num_args = 0; original[num_args] != NULL; num_args++) {
LOG("original argument: \"%s\"\n", original[num_args]);
/* If the argument is already present we return the original pointer */
if (strcmp(original[num_args], argument) == 0)
return original;
}
/* Copy the original array */
char **result = smalloc((num_args+2) * sizeof(char*));
memcpy(result, original, num_args * sizeof(char*));
result[num_args] = argument;
result[num_args+1] = NULL;
return result;
}
/*
* Parses a command, see file CMDMODE for more information
*
*/
void parse_command(xcb_connection_t *conn, const char *command) {
LOG("--- parsing command \"%s\" ---\n", command);
/* Get the first client from focus stack because floating clients are not
* in any container, therefore CUR_CELL is not appropriate. */
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused == SLIST_END(&(c_ws->focus_stack)))
last_focused = NULL;
/* Hmm, just to be sure */
if (command[0] == '\0')
return;
@ -612,37 +878,66 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* Is it <restart>? Then restart in place. */
if (STARTS_WITH(command, "restart")) {
LOG("restarting \"%s\"...\n", start_argv[0]);
/* make sure -a is in the argument list or append it */
start_argv = append_argument(start_argv, "-a");
execvp(start_argv[0], start_argv);
/* not reached */
}
if (STARTS_WITH(command, "kill")) {
if (CUR_CELL->currently_focused == NULL) {
if (last_focused == NULL) {
LOG("There is no window to kill\n");
return;
}
LOG("Killing current window\n");
kill_window(conn, CUR_CELL->currently_focused);
client_kill(conn, last_focused);
return;
}
/* Is it a jump to a specified workspace, row, col? */
if (STARTS_WITH(command, "jump ")) {
const char *arguments = command + strlen("jump ");
if (arguments[0] == '"')
jump_to_window(conn, arguments);
else jump_to_container(conn, arguments);
return;
}
/* Should we travel the focus stack? */
if (STARTS_WITH(command, "focus")) {
const char *arguments = command + strlen("focus ");
travel_focus_stack(conn, arguments);
return;
}
/* Is it 'f' for fullscreen? */
if (command[0] == 'f') {
if (CUR_CELL->currently_focused == NULL)
if (last_focused == NULL)
return;
toggle_fullscreen(conn, CUR_CELL->currently_focused);
client_toggle_fullscreen(conn, last_focused);
return;
}
/* Is it just 's' for stacking or 'd' for default? */
if ((command[0] == 's' || command[0] == 'd') && (command[1] == '\0')) {
if (last_focused == NULL || client_is_floating(last_focused)) {
LOG("not switching, this is a floating client\n");
return;
}
LOG("Switching mode for current container\n");
switch_layout_mode(conn, CUR_CELL, (command[0] == 's' ? MODE_STACK : MODE_DEFAULT));
return;
}
enum { WITH_WINDOW, WITH_CONTAINER } with = WITH_WINDOW;
if (command[0] == 'H') {
LOG("Hiding all floating windows\n");
floating_toggle_hide(conn, c_ws);
return;
}
enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE } with = WITH_WINDOW;
/* Is it a <with>? */
if (command[0] == 'w') {
@ -651,13 +946,44 @@ void parse_command(xcb_connection_t *conn, const char *command) {
if (command[0] == 'c') {
with = WITH_CONTAINER;
command++;
} else if (command[0] == 'w') {
with = WITH_WORKSPACE;
command++;
} else {
LOG("not yet implemented.\n");
return;
}
}
/* It's a normal <cmd> */
/* Is it 't' for toggle tiling/floating? */
if (command[0] == 't') {
if (with == WITH_WORKSPACE) {
c_ws->auto_float = !c_ws->auto_float;
LOG("autofloat is now %d\n", c_ws->auto_float);
return;
}
if (last_focused == NULL) {
LOG("Cannot toggle tiling/floating: workspace empty\n");
return;
}
toggle_floating_mode(conn, last_focused, false);
/* delete all empty columns/rows */
cleanup_table(conn, last_focused->workspace);
/* Fix colspan/rowspan if itd 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;
}
/* Its a normal <cmd> */
char *rest = NULL;
enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS;
direction_t direction;
@ -668,7 +994,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
}
if (*rest == '\0') {
/* No rest? This was a tag number, not a times specification */
/* No rest? This was a workspace number, not a times specification */
show_workspace(conn, times);
return;
}
@ -686,7 +1012,20 @@ void parse_command(xcb_connection_t *conn, const char *command) {
}
if (*rest == '\0') {
move_current_window_to_workspace(conn, workspace);
if (last_focused != NULL && client_is_floating(last_focused))
move_floating_window_to_workspace(conn, last_focused, workspace);
else move_current_window_to_workspace(conn, workspace);
return;
}
if (last_focused == NULL) {
LOG("Not performing (no window found)\n");
return;
}
if (client_is_floating(last_focused) &&
(action != ACTION_FOCUS && action != ACTION_MOVE)) {
LOG("Not performing (floating)\n");
return;
}
@ -704,18 +1043,32 @@ void parse_command(xcb_connection_t *conn, const char *command) {
LOG("unknown direction: %c\n", *rest);
return;
}
rest++;
if (action == ACTION_FOCUS)
if (action == ACTION_FOCUS) {
if (client_is_floating(last_focused)) {
floating_focus_direction(conn, last_focused, direction);
continue;
}
focus_thing(conn, direction, (with == WITH_WINDOW ? THING_WINDOW : THING_CONTAINER));
else if (action == ACTION_MOVE) {
continue;
}
if (action == ACTION_MOVE) {
if (client_is_floating(last_focused)) {
floating_move(conn, last_focused, direction);
continue;
}
if (with == WITH_WINDOW)
move_current_window(conn, direction);
else move_current_container(conn, direction);
continue;
}
else if (action == ACTION_SNAP)
snap_current_container(conn, direction);
rest++;
if (action == ACTION_SNAP) {
snap_current_container(conn, direction);
continue;
}
}
LOG("--- done ---\n");

View File

@ -17,6 +17,7 @@
#include "i3.h"
#include "util.h"
#include "config.h"
#include "xcb.h"
Config config;
@ -25,14 +26,36 @@ Config config;
*
*/
static char *glob_path(const char *path) {
static glob_t globbuf;
if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
die("glob() failed");
char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
globfree(&globbuf);
return result;
static glob_t globbuf;
if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
die("glob() failed");
char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
globfree(&globbuf);
return result;
}
/*
* This function does a very simple replacement of each instance of key with value.
*
*/
static void replace_variable(char *buffer, const char *key, const char *value) {
char *pos;
/* To prevent endless recursions when the user makes an error configuring,
* we stop after 100 replacements. That should be vastly more than enough. */
int c = 0;
LOG("Replacing %s with %s\n", key, value);
while ((pos = strcasestr(buffer, key)) != NULL && c++ < 100) {
LOG("replacing variable %s in \"%s\" with \"%s\"\n", key, buffer, value);
char *rest = pos + strlen(key);
*pos = '\0';
char *replaced;
asprintf(&replaced, "%s%s%s", buffer, value, rest);
/* Hm, this is a bit ugly, but sizeof(buffer) = 4, as its just a pointer.
* So we need to hard-code the dimensions here. */
strncpy(buffer, replaced, 1026);
free(replaced);
}
}
/*
* Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
@ -41,7 +64,9 @@ static char *glob_path(const char *path) {
* configuration file.
*
*/
void load_configuration(const char *override_configpath) {
void load_configuration(xcb_connection_t *conn, const char *override_configpath) {
SLIST_HEAD(variables_head, Variable) variables;
#define OPTION_STRING(name) \
if (strcasecmp(key, #name) == 0) { \
config.name = sstrdup(value); \
@ -52,9 +77,51 @@ void load_configuration(const char *override_configpath) {
if (config.name == NULL) \
die("You did not specify required configuration option " #name "\n");
#define OPTION_COLORTRIPLE(opt, name) \
if (strcasecmp(key, opt) == 0) { \
char border[8], background[8], text[8]; \
memset(border, 0, sizeof(border)); \
memset(background, 0, sizeof(background)); \
memset(text, 0, sizeof(text)); \
border[0] = background[0] = text[0] = '#'; \
if (sscanf(value, "#%06[0-9a-fA-F] #%06[0-9a-fA-F] #%06[0-9a-fA-F]", \
border + 1, background + 1, text + 1) != 3 || \
strlen(border) != 7 || \
strlen(background) != 7 || \
strlen(text) != 7) \
die("invalid color code line: %s\n", value); \
config.name.border = get_colorpixel(conn, border); \
config.name.background = get_colorpixel(conn, background); \
config.name.text = get_colorpixel(conn, text); \
continue; \
}
/* Clear the old config or initialize the data structure */
memset(&config, 0, sizeof(config));
SLIST_INIT(&variables);
/* Initialize default colors */
config.client.focused.border = get_colorpixel(conn, "#4c7899");
config.client.focused.background = get_colorpixel(conn, "#285577");
config.client.focused.text = get_colorpixel(conn, "#ffffff");
config.client.focused_inactive.border = get_colorpixel(conn, "#4c7899");
config.client.focused_inactive.background = get_colorpixel(conn, "#555555");
config.client.focused_inactive.text = get_colorpixel(conn, "#ffffff");
config.client.unfocused.border = get_colorpixel(conn, "#333333");
config.client.unfocused.background = get_colorpixel(conn, "#222222");
config.client.unfocused.text = get_colorpixel(conn, "#888888");
config.bar.focused.border = get_colorpixel(conn, "#4c7899");
config.bar.focused.background = get_colorpixel(conn, "#285577");
config.bar.focused.text = get_colorpixel(conn, "#ffffff");
config.bar.unfocused.border = get_colorpixel(conn, "#333333");
config.bar.unfocused.background = get_colorpixel(conn, "#222222");
config.bar.unfocused.text = get_colorpixel(conn, "#888888");
FILE *handle;
if (override_configpath != NULL) {
if ((handle = fopen(override_configpath, "r")) == NULL)
@ -77,6 +144,14 @@ void load_configuration(const char *override_configpath) {
die("Could not read configuration file\n");
}
if (config.terminal != NULL)
replace_variable(buffer, "$terminal", config.terminal);
/* Replace all custom variables */
struct Variable *current;
SLIST_FOREACH(current, &variables, variables)
replace_variable(buffer, current->key, current->value);
/* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
key[0] == '#' || strlen(key) < 3)
@ -85,6 +160,22 @@ void load_configuration(const char *override_configpath) {
OPTION_STRING(terminal);
OPTION_STRING(font);
/* Colors */
OPTION_COLORTRIPLE("client.focused", client.focused);
OPTION_COLORTRIPLE("client.focused_inactive", client.focused_inactive);
OPTION_COLORTRIPLE("client.unfocused", client.unfocused);
OPTION_COLORTRIPLE("bar.focused", bar.focused);
OPTION_COLORTRIPLE("bar.unfocused", bar.unfocused);
/* exec-lines (autostart) */
if (strcasecmp(key, "exec") == 0) {
struct Autostart *new = smalloc(sizeof(struct Autostart));
new->command = sstrdup(value);
TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
continue;
}
/* key bindings */
if (strcasecmp(key, "bind") == 0) {
#define CHECK_MODIFIER(name) \
if (strncasecmp(walk, #name, strlen(#name)) == 0) { \
@ -124,13 +215,105 @@ void load_configuration(const char *override_configpath) {
continue;
}
fprintf(stderr, "Unknown configfile option: %s\n", key);
exit(1);
if (strcasecmp(key, "floating_modifier") == 0) {
char *walk = value;
uint32_t modifiers = 0;
while (*walk != '\0') {
/* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */
CHECK_MODIFIER(SHIFT);
CHECK_MODIFIER(CONTROL);
CHECK_MODIFIER(MODE_SWITCH);
CHECK_MODIFIER(MOD1);
CHECK_MODIFIER(MOD2);
CHECK_MODIFIER(MOD3);
CHECK_MODIFIER(MOD4);
CHECK_MODIFIER(MOD5);
/* No modifier found? Then were 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;
}

433
src/floating.c Normal file
View File

@ -0,0 +1,433 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* src/floating.c: contains all functions for handling floating clients
*
*/
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <xcb/xcb.h>
#include <xcb/xcb_event.h>
#include "i3.h"
#include "config.h"
#include "data.h"
#include "util.h"
#include "xcb.h"
#include "debug.h"
#include "layout.h"
#include "client.h"
#include "floating.h"
/*
* Toggles floating mode for the given client.
* Correctly takes care of the position/size (separately stored for tiling/floating mode)
* and repositions/resizes/redecorates the client.
*
* If the automatic flag is set to true, this was an automatic update by a change of the
* window class from the application which can be overwritten by the user.
*
*/
void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic) {
Container *con = client->container;
i3Font *font = load_font(conn, config.font);
if (con == NULL) {
LOG("This client is already in floating (container == NULL), re-inserting\n");
Client *next_tiling;
SLIST_FOREACH(next_tiling, &(client->workspace->focus_stack), focus_clients)
if (!client_is_floating(next_tiling))
break;
/* If there are no tiling clients on this workspace, there can only be one
* container: the first one */
if (next_tiling == TAILQ_END(&(client->workspace->focus_stack)))
con = client->workspace->table[0][0];
else con = next_tiling->container;
/* Remove the client from the list of floating clients */
TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
LOG("destination container = %p\n", con);
Client *old_focused = con->currently_focused;
/* Preserve position/size */
memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect));
client->floating = FLOATING_USER_OFF;
client->container = con;
if (old_focused != NULL && !old_focused->dock)
CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients);
else CIRCLEQ_INSERT_TAIL(&(con->clients), client, clients);
LOG("Re-inserted the client into the matrix.\n");
con->currently_focused = client;
client_set_below_floating(conn, client);
render_container(conn, con);
xcb_flush(conn);
return;
}
LOG("Entering floating for client %08x\n", client->child);
/* Remove the client of its container */
client_remove_from_container(conn, client, con, false);
client->container = NULL;
/* Add the client to the list of floating clients for its workspace */
TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
if (con->currently_focused == client) {
LOG("Need to re-adjust currently_focused\n");
/* Get the next client in the focus stack for this particular container */
con->currently_focused = get_last_focused_client(conn, con, NULL);
}
if (automatic)
client->floating = FLOATING_AUTO_ON;
else client->floating = FLOATING_USER_ON;
/* Initialize the floating position from the position in tiling mode, if this
* client never was floating (x == -1) */
if (client->floating_rect.x == -1) {
/* Copy over the position */
client->floating_rect.x = client->rect.x;
client->floating_rect.y = client->rect.y;
/* Copy size the other direction */
client->child_rect.width = client->floating_rect.width;
client->child_rect.height = client->floating_rect.height;
client->rect.width = client->child_rect.width + 2 + 2;
client->rect.height = client->child_rect.height + (font->height + 2 + 2) + 2;
LOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
client->floating_rect.width, client->floating_rect.height);
} else {
/* If the client was already in floating before we restore the old position / size */
LOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
client->floating_rect.width, client->floating_rect.height);
memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect));
}
/* Raise the client */
xcb_raise_window(conn, client->frame);
reposition_client(conn, client);
resize_client(conn, client);
/* redecorate_window flushes */
redecorate_window(conn, client);
/* Re-render the tiling layout of this container */
render_container(conn, con);
xcb_flush(conn);
}
/*
* Removes the floating client from its workspace and attaches it to the new workspace.
* This is centralized here because it may happen if you move it via keyboard and
* if you move it using your mouse.
*
*/
void floating_assign_to_workspace(Client *client, Workspace *new_workspace) {
/* Remove from focus stack and list of floating clients */
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
if (client->workspace->fullscreen_client == client)
client->workspace->fullscreen_client = NULL;
/* Insert into destination focus stack and list of floating clients */
client->workspace = new_workspace;
SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
if (client->fullscreen)
client->workspace->fullscreen_client = client;
}
/*
* Called whenever the user clicks on a border (not the titlebar!) of a floating window.
* Determines on which border the user clicked and launches the drag_pointer function
* with the resize_callback.
*
*/
int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
LOG("floating border click\n");
border_t border;
void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
switch (border) {
case BORDER_RIGHT: {
int new_width = old_rect->width + (new_x - event->root_x);
if ((new_width < 0) ||
(new_width < 50 && client->rect.width >= new_width))
return;
client->rect.width = new_width;
break;
}
case BORDER_BOTTOM: {
int new_height = old_rect->height + (new_y - event->root_y);
if ((new_height < 0) ||
(new_height < 20 && client->rect.height >= new_height))
return;
client->rect.height = old_rect->height + (new_y - event->root_y);
break;
}
case BORDER_TOP: {
int new_height = old_rect->height + (event->root_y - new_y);
if ((new_height < 0) ||
(new_height < 20 && client->rect.height >= new_height))
return;
client->rect.y = old_rect->y + (new_y - event->root_y);
client->rect.height = new_height;
break;
}
case BORDER_LEFT: {
int new_width = old_rect->width + (event->root_x - new_x);
if ((new_width < 0) ||
(new_width < 50 && client->rect.width >= new_width))
return;
client->rect.x = old_rect->x + (new_x - event->root_x);
client->rect.width = new_width;
break;
}
}
/* Push the new position/size to X11 */
reposition_client(conn, client);
resize_client(conn, client);
xcb_flush(conn);
}
if (event->event_y < 2)
border = BORDER_TOP;
else if (event->event_y >= (client->rect.height - 2))
border = BORDER_BOTTOM;
else if (event->event_x <= 2)
border = BORDER_LEFT;
else if (event->event_x >= (client->rect.width - 2))
border = BORDER_RIGHT;
else {
LOG("Not on any border, not doing anything.\n");
return 1;
}
LOG("border = %d\n", border);
drag_pointer(conn, client, event, XCB_NONE, border, resize_callback);
return 1;
}
/*
* Called when the user clicked on the titlebar of a floating window.
* Calls the drag_pointer function with the drag_window callback
*
*/
void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
LOG("floating_drag_window\n");
void drag_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
/* Reposition the client correctly while moving */
client->rect.x = old_rect->x + (new_x - event->root_x);
client->rect.y = old_rect->y + (new_y - event->root_y);
reposition_client(conn, client);
/* Because reposition_client does not send a faked configure event (only resize does),
* we need to initiate that on our own */
fake_absolute_configure_notify(conn, client);
/* fake_absolute_configure_notify flushes */
}
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback);
}
/*
* This function grabs your pointer and lets you drag stuff around (borders).
* Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
* and the given callback will be called with the parameters specified (client,
* border on which the click originally was), the original rect of the client,
* the event and the new coordinates (x, y).
*
*/
void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
xcb_window_t confine_to, border_t border, callback_t callback) {
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
uint32_t new_x, new_y;
Rect old_rect;
if (client != NULL)
memcpy(&old_rect, &(client->rect), sizeof(Rect));
/* Grab the pointer */
/* TODO: returncode */
xcb_grab_pointer(conn,
false, /* get all pointer events specified by the following mask */
root, /* grab the root window */
XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */
XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
XCB_GRAB_MODE_ASYNC, /* keyboard mode */
confine_to, /* confine_to = in which window should the cursor stay */
XCB_NONE, /* dont 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;
/* Ive 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);
}

View File

@ -32,6 +32,9 @@
#include "config.h"
#include "queue.h"
#include "resize.h"
#include "client.h"
#include "manage.h"
#include "floating.h"
/* After mapping/unmapping windows, a notify event is generated. However, we dont want it,
since itd trigger an infinite loop of switching between the different windows when
@ -149,7 +152,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
return 1;
}
/* Some events are not interesting, because they were not generated actively by the
user, but be reconfiguration of windows */
user, but by reconfiguration of windows */
if (event_is_ignored(event->sequence))
return 1;
@ -196,6 +199,14 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
return 1;
}
if (client->workspace != c_ws && client->workspace->screen == c_ws->screen) {
/* This can happen when a client gets assigned to a different workspace than
* the current one (see src/mainx.c:reparent_window). Shortly after it was created,
* an enter_notify will follow. */
LOG("enter_notify for a client on a different workspace but the same screen, ignoring\n");
return 1;
}
set_focus(conn, client, false);
return 1;
@ -283,6 +294,7 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e
int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) {
LOG("button press!\n");
LOG("state = %d\n", event->state);
/* This was either a focus for a clients parent (= titlebar)… */
Client *client = table_get(&by_child, event->event);
bool border_click = false;
@ -290,6 +302,21 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
client = table_get(&by_parent, event->event);
border_click = true;
}
/* See if this was a click with the configured modifier. If so, we need
* to move around the client if it was floating. if not, we just process
* as usual. */
if (config.floating_modifier != 0 &&
(event->state & config.floating_modifier) != 0) {
if (client == NULL) {
LOG("Not handling, Mod1 was pressed and no client found\n");
return 1;
}
if (client_is_floating(client)) {
floating_drag_window(conn, client, event);
return 1;
}
}
if (client == NULL) {
/* The client was neither on a clients titlebar nor on a client itself, maybe on a stack_window? */
if (button_press_stackwin(conn, event))
@ -312,7 +339,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
Container *con = client->container;
int first, second;
if (con == NULL) {
if (client->dock) {
LOG("dock. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);
@ -324,6 +351,9 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
if (!border_click) {
LOG("client. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
/* Floating clients should be raised on click */
if (client_is_floating(client))
xcb_raise_window(conn, client->frame);
xcb_flush(conn);
return 1;
}
@ -332,9 +362,21 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
i3Font *font = load_font(conn, config.font);
if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) {
LOG("click on titlebar\n");
/* Floating clients can be dragged by grabbing their titlebar */
if (client_is_floating(client)) {
/* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */
xcb_raise_window(conn, client->frame);
xcb_flush(conn);
floating_drag_window(conn, client, event);
}
return 1;
}
if (client_is_floating(client))
return floating_border_click(conn, client, event);
if (event->event_y < 2) {
/* This was a press on the top border */
if (con->row == 0)
@ -412,8 +454,61 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
Client *client = table_get(&by_child, event->window);
if (client == NULL) {
LOG("This client is not mapped, so we don't care and just tell the client that he will get its size\n");
Rect rect = {event->x, event->y, event->width, event->height};
fake_configure_notify(conn, rect, event->window);
uint32_t mask = 0;
uint32_t values[7];
int c = 0;
#define COPY_MASK_MEMBER(mask_member, event_member) do { \
if (event->value_mask & mask_member) { \
mask |= mask_member; \
values[c++] = event->event_member; \
} \
} while (0)
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_X, x);
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_Y, y);
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_WIDTH, width);
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_HEIGHT, height);
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_BORDER_WIDTH, border_width);
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_SIBLING, sibling);
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_STACK_MODE, stack_mode);
xcb_configure_window(conn, event->window, mask, values);
xcb_flush(conn);
return 1;
}
/* Floating clients can be reconfigured */
if (client_is_floating(client)) {
i3Font *font = load_font(conn, config.font);
if (event->value_mask & XCB_CONFIG_WINDOW_X)
client->rect.x = event->x;
if (event->value_mask & XCB_CONFIG_WINDOW_Y)
client->rect.y = event->y;
if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH)
client->rect.width = event->width + 2 + 2;
if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)
client->rect.height = event->height + (font->height + 2 + 2) + 2;
LOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n",
client->rect.x, client->rect.y, client->rect.width, client->rect.height);
/* Push the new position/size to X11 */
reposition_client(conn, client);
resize_client(conn, client);
xcb_flush(conn);
return 1;
}
if (client->fullscreen) {
LOG("Client is in fullscreen mode\n");
Rect child_rect = client->container->workspace->rect;
child_rect.x = child_rect.y = 0;
fake_configure_notify(conn, child_rect, client->child);
return 1;
}
@ -495,18 +590,16 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
client = table_remove(&by_child, event->window);
if (client->name != NULL)
free(client->name);
/* If this was the fullscreen client, we need to unset it */
if (client->fullscreen)
client->workspace->fullscreen_client = NULL;
/* Clients without a container are either floating or dock windows */
if (client->container != NULL) {
Container *con = client->container;
/* If this was the fullscreen client, we need to unset it */
if (client->fullscreen)
con->workspace->fullscreen_client = NULL;
/* Remove the client from the list of clients */
remove_client_from_container(conn, client, con);
client_remove_from_container(conn, client, con, true);
/* Set focus to the last focused client in this container */
con->currently_focused = get_last_focused_client(conn, con, NULL);
@ -514,6 +607,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
/* Only if this is the active container, we need to really change focus */
if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen))
set_focus(conn, con->currently_focused, true);
} else if (client_is_floating(client)) {
LOG("Removing from floating clients\n");
TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
}
if (client->dock) {
@ -528,32 +625,41 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
table_remove(&by_parent, client->frame);
if (client->container != NULL) {
cleanup_table(conn, client->container->workspace);
fix_colrowspan(conn, client->container->workspace);
Workspace *workspace = client->container->workspace;
cleanup_table(conn, workspace);
fix_colrowspan(conn, workspace);
}
/* Lets see how many clients there are left on the workspace to delete it if its 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 dont delete it */
i3Screen *screen;
TAILQ_FOREACH(screen, virtual_screens, screens)
if (screen->current_workspace == client->workspace->num) {
workspace_active = true;
workspace_empty = false;
break;
}
if (workspace_empty)
if (workspace_empty) {
LOG("setting ws to NULL for workspace %d (%p)\n", client->workspace->num,
client->workspace);
client->workspace->screen = NULL;
}
FREE(client->window_class);
FREE(client->name);
free(client);
render_layout(conn);
/* Ensure the focus is set to the next client in the focus stack */
if (workspace_active && to_focus != NULL)
set_focus(conn, to_focus, true);
return 1;
}
@ -598,14 +704,13 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
client->name_len = new_len;
client->uses_net_wm_name = true;
if (old_name != NULL)
free(old_name);
FREE(old_name);
/* If the client is a dock window, we dont need to render anything */
if (client->dock)
return 1;
if (client->container->mode == MODE_STACK)
if (client->container != NULL && client->container->mode == MODE_STACK)
render_container(conn, client->container);
else decorate_window(conn, client, client->frame, client->titlegc, 0);
xcb_flush(conn);
@ -673,7 +778,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
if (client->dock)
return 1;
if (client->container->mode == MODE_STACK)
if (client->container != NULL && client->container->mode == MODE_STACK)
render_container(conn, client->container);
else decorate_window(conn, client, client->frame, client->titlegc, 0);
xcb_flush(conn);
@ -681,6 +786,46 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
return 1;
}
/*
* Updates the clients WM_CLASS property
*
*/
int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
LOG("window class changed\n");
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
LOG("prop == NULL\n");
return 1;
}
Client *client = table_get(&by_child, window);
if (client == NULL)
return 1;
char *new_class;
if (asprintf(&new_class, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) {
perror("Could not get window class");
LOG("Could not get window class\n");
return 1;
}
LOG("changed to %s\n", new_class);
char *old_class = client->window_class;
client->window_class = new_class;
FREE(old_class);
if (!client->initialized) {
LOG("Client is not yet initialized, not putting it to floating\n");
return 1;
}
if (strcmp(new_class, "tools") == 0 || strcmp(new_class, "Dialog") == 0) {
LOG("tool/dialog window, should we put it floating?\n");
if (client->floating == FLOATING_AUTO_OFF)
toggle_floating_mode(conn, client, true);
}
return 1;
}
/*
* Expose event means we should redraw our windows (= title bar)
*
@ -717,15 +862,15 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
return 1;
}
if (client->container->mode != MODE_STACK)
if (client->container == NULL || client->container->mode != MODE_STACK)
decorate_window(conn, client, client->frame, client->titlegc, 0);
else {
uint32_t background_color;
/* Distinguish if the window is currently focused… */
if (CUR_CELL->currently_focused == client)
background_color = get_colorpixel(conn, "#285577");
background_color = config.client.focused.background;
/* …or if it is the focused window in a not focused container */
else background_color = get_colorpixel(conn, "#555555");
else background_color = config.client.focused_inactive.background;
/* Set foreground color to current focused color, line width to 2 */
uint32_t values[] = {background_color, 2};
@ -771,7 +916,7 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message
(!client->fullscreen &&
(event->data.data32[0] == _NET_WM_STATE_ADD ||
event->data.data32[0] == _NET_WM_STATE_TOGGLE)))
toggle_fullscreen(conn, client);
client_toggle_fullscreen(conn, client);
} else {
LOG("unhandled clientmessage\n");
return 0;
@ -804,6 +949,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
return 1;
}
xcb_size_hints_t size_hints;
LOG("client is %08x / child %08x\n", client->frame, client->child);
/* If the hints were already in this event, use them, if not, request them */
if (reply != NULL)
@ -811,6 +957,11 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
else
xcb_get_wm_normal_hints_reply(conn, xcb_get_wm_normal_hints_unchecked(conn, client->child), &size_hints, NULL);
if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) {
LOG("min size set\n");
LOG("gots min_width = %d, min_height = %d\n", size_hints.min_width, size_hints.min_height);
}
/* If no aspect ratio was set or if it was invalid, we ignore the hints */
if (!(size_hints.flags & XCB_SIZE_HINT_P_ASPECT) ||
(size_hints.min_aspect_num <= 0) ||
@ -821,8 +972,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
LOG("window is %08x / %s\n", client->child, client->name);
int base_width = 0, base_height = 0,
min_width = 0, min_height = 0;
int base_width = 0, base_height = 0;
/* base_width/height are the desired size of the window.
We check if either the program-specified size or the program-specified
@ -835,14 +985,6 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
base_height = size_hints.min_height;
}
if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) {
min_width = size_hints.min_width;
min_height = size_hints.min_height;
} else if (size_hints.flags & XCB_SIZE_HINT_P_SIZE) {
min_width = size_hints.base_width;
min_height = size_hints.base_height;
}
double width = client->rect.width - base_width;
double height = client->rect.height - base_height;
/* Convert numerator/denominator to a double */
@ -874,3 +1016,42 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
return 1;
}
/*
* Handles the transient for hints set by a window, signalizing that this window is a popup window
* for some other window.
*
* See ICCCM 4.1.2.6 for more details
*
*/
int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
xcb_atom_t name, xcb_get_property_reply_t *reply) {
LOG("Transient hint!\n");
Client *client = table_get(&by_child, window);
if (client == NULL) {
LOG("No such client\n");
return 1;
}
xcb_window_t transient_for;
if (reply != NULL) {
if (!xcb_get_wm_transient_for_from_reply(&transient_for, reply)) {
LOG("Not transient for any window\n");
return 1;
}
} else {
if (!xcb_get_wm_transient_for_reply(conn, xcb_get_wm_transient_for_unchecked(conn, window),
&transient_for, NULL)) {
LOG("Not transient for any window\n");
return 1;
}
}
if (client->floating == FLOATING_AUTO_OFF) {
LOG("This is a popup window, putting into floating\n");
toggle_floating_mode(conn, client, true);
}
return 1;
}

View File

@ -24,6 +24,8 @@
#include "util.h"
#include "xinerama.h"
#include "layout.h"
#include "client.h"
#include "floating.h"
/*
* Updates *destination with new_value and returns true if it was changed or false
@ -83,7 +85,7 @@ int get_unoccupied_y(Workspace *workspace, int col) {
*
*/
void redecorate_window(xcb_connection_t *conn, Client *client) {
if (client->container->mode == MODE_STACK) {
if (client->container != NULL && client->container->mode == MODE_STACK) {
render_container(conn, client->container);
/* We clear the frame to generate exposure events, because the color used
in drawing may be different */
@ -100,47 +102,39 @@ void redecorate_window(xcb_connection_t *conn, Client *client) {
void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable, xcb_gcontext_t gc, int offset) {
i3Font *font = load_font(conn, config.font);
int decoration_height = font->height + 2 + 2;
uint32_t background_color,
text_color,
border_color;
struct Colortriple *color;
/* Clients without a container (docks) wont 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 windows 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 containers 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 clients rect.width would be wrong */
xcb_rectangle_t rect = {0, 0, client->container->width, client->rect.height};
xcb_rectangle_t rect = {0, offset, client->container->width, offset + decoration_height };
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
} else {
xcb_rectangle_t rect = {0, 0, client->rect.width, client->rect.height};
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
/* Draw the inner background to have a black frame around clients (such as mplayer)
@ -152,15 +146,15 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
}
/* Draw the lines */
xcb_draw_line(conn, drawable, gc, border_color, 2, offset, client->rect.width, offset);
xcb_draw_line(conn, drawable, gc, border_color, 2, offset + font->height + 3,
2 + client->rect.width, offset + font->height + 3);
xcb_draw_line(conn, drawable, gc, color->border, 0, offset, client->rect.width, offset);
xcb_draw_line(conn, drawable, gc, color->border, 2, offset + font->height + 3,
client->rect.width - 3, offset + font->height + 3);
/* If the client has a title, we draw it */
if (client->name != NULL) {
/* Draw the font */
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
uint32_t values[] = { text_color, background_color, font->id };
uint32_t values[] = { color->text, color->background, font->id };
xcb_change_gc(conn, gc, mask, values);
/* name_len == -1 means this is a legacy application which does not specify _NET_WM_NAME,
@ -179,18 +173,37 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
* Pushes the clients 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 clients width/height to X11 and resizes the child window
*
*/
static void resize_client(xcb_connection_t *conn, Client *client) {
void resize_client(xcb_connection_t *conn, Client *client) {
i3Font *font = load_font(conn, config.font);
LOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
@ -335,8 +348,13 @@ void render_container(xcb_connection_t *conn, Container *container) {
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
XCB_CONFIG_WINDOW_STACK_MODE;
/* If there is no fullscreen client, we raise the stack window */
if (container->workspace->fullscreen_client != NULL) {
/* Raise the stack window, but keep it below the first floating client
* and below the fullscreen client (if any) */
Client *first_floating = TAILQ_FIRST(&(container->workspace->floating_clients));
if (first_floating != TAILQ_END(&(container->workspace->floating_clients))) {
mask |= XCB_CONFIG_WINDOW_SIBLING;
values[4] = first_floating->frame;
} else if (container->workspace->fullscreen_client != NULL) {
mask |= XCB_CONFIG_WINDOW_SIBLING;
values[4] = container->workspace->fullscreen_client->frame;
}
@ -344,9 +362,6 @@ void render_container(xcb_connection_t *conn, Container *container) {
xcb_configure_window(conn, stack_win->window, mask, values);
}
/* Reconfigure the currently focused client, if necessary. It is the only visible one */
client = container->currently_focused;
/* Render the decorations of all clients */
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
/* If the client is in fullscreen mode, it does not get reconfigured */
@ -400,24 +415,10 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid
i3Font *font = load_font(conn, config.font);
i3Screen *screen = r_ws->screen;
enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
uint32_t background_color[2],
text_color[2],
border_color[2],
black;
char label[3];
black = get_colorpixel(conn, "#000000");
background_color[SET_NORMAL] = get_colorpixel(conn, "#222222");
text_color[SET_NORMAL] = get_colorpixel(conn, "#888888");
border_color[SET_NORMAL] = get_colorpixel(conn, "#333333");
background_color[SET_FOCUSED] = get_colorpixel(conn, "#285577");
text_color[SET_FOCUSED] = get_colorpixel(conn, "#ffffff");
border_color[SET_FOCUSED] = get_colorpixel(conn, "#4c7899");
/* Fill the whole bar in black */
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, black);
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
xcb_rectangle_t rect = {0, 0, width, height};
xcb_poly_fill_rectangle(conn, screen->bar, screen->bargc, 1, &rect);
@ -429,16 +430,17 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid
if (workspaces[c].screen != screen)
continue;
int set = (screen->current_workspace == c ? SET_FOCUSED : SET_NORMAL);
struct Colortriple *color = (screen->current_workspace == c ? &(config.bar.focused) :
&(config.bar.unfocused));
xcb_draw_rect(conn, screen->bar, screen->bargc, border_color[set],
xcb_draw_rect(conn, screen->bar, screen->bargc, color->border,
drawn * height, 1, height - 2, height - 2);
xcb_draw_rect(conn, screen->bar, screen->bargc, background_color[set],
xcb_draw_rect(conn, screen->bar, screen->bargc, color->background,
drawn * height + 1, 2, height - 4, height - 4);
snprintf(label, sizeof(label), "%d", c+1);
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, text_color[set]);
xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, background_color[set]);
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, color->text);
xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, color->background);
xcb_image_text_8(conn, strlen(label), screen->bar, screen->bargc, drawn * height + 5 /* X */,
font->height + 1 /* Y = baseline of font */, label);
drawn++;

View File

@ -31,6 +31,8 @@
#include <xcb/xcb_icccm.h>
#include <xcb/xinerama.h>
#include <ev.h>
#include "config.h"
#include "data.h"
#include "debug.h"
@ -42,6 +44,7 @@
#include "util.h"
#include "xcb.h"
#include "xinerama.h"
#include "manage.h"
/* This is the path to i3, copied from argv[0] when starting up */
char **start_argv;
@ -52,6 +55,12 @@ Display *xkbdpy;
/* The list of key bindings */
struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
/* The list of exec-lines */
struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts);
/* The list of assignments */
struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments);
/* This is a list of Stack_Windows, global, for easier/faster access on expose events */
struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
@ -63,273 +72,40 @@ xcb_atom_t atoms[NUM_ATOMS];
int num_screens = 0;
/*
* Do some sanity checks and then reparent the window.
* This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
* See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
*
*/
void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
LOG("managing window.\n");
xcb_drawable_t d = { window };
xcb_get_geometry_cookie_t geomc;
xcb_get_geometry_reply_t *geom;
xcb_get_window_attributes_reply_t *attr = 0;
if (wa.tag == TAG_COOKIE) {
/* Check if the window is mapped (it could be not mapped when intializing and
calling manage_window() for every window) */
if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
return;
if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
goto out;
wa.tag = TAG_VALUE;
wa.u.override_redirect = attr->override_redirect;
}
/* Dont 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;
/* Dont 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 fonts 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 /* dont filter for any modifiers */);
/* Get _NET_WM_WINDOW_TYPE (to see if its 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 clients 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 were 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 its 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 dont 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;
static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
xcb_generic_event_t *event;
/* 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);
while ((event = xcb_poll_for_event(evenths.c)) != NULL) {
xcb_event_handle(&evenths, event);
free(event);
}
free(reply);
free(cookies);
}
int main(int argc, char *argv[], char *env[]) {
int i, screens, opt;
char *override_configpath = NULL;
bool autostart = true;
xcb_connection_t *conn;
xcb_property_handlers_t prophs;
xcb_window_t root;
@ -343,8 +119,12 @@ int main(int argc, char *argv[], char *env[]) {
start_argv = argv;
while ((opt = getopt(argc, argv, "c:v")) != -1) {
while ((opt = getopt(argc, argv, "c:va")) != -1) {
switch (opt) {
case 'a':
LOG("Autostart disabled using -a\n");
autostart = false;
break;
case 'c':
override_configpath = sstrdup(optarg);
break;
@ -365,10 +145,13 @@ int main(int argc, char *argv[], char *env[]) {
memset(&evenths, 0, sizeof(xcb_event_handlers_t));
memset(&prophs, 0, sizeof(xcb_property_handlers_t));
load_configuration(override_configpath);
conn = xcb_connect(NULL, &screens);
if (xcb_connection_has_error(conn))
die("Cannot open display\n");
load_configuration(conn, override_configpath);
/* Place requests for the atoms we need as soon as possible */
#define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name);
@ -380,6 +163,10 @@ int main(int argc, char *argv[], char *env[]) {
REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
REQUEST_ATOM(_NET_WM_DESKTOP);
REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DIALOG);
REQUEST_ATOM(_NET_WM_WINDOW_TYPE_UTILITY);
REQUEST_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR);
REQUEST_ATOM(_NET_WM_WINDOW_TYPE_SPLASH);
REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
REQUEST_ATOM(WM_PROTOCOLS);
REQUEST_ATOM(WM_DELETE_WINDOW);
@ -406,6 +193,27 @@ int main(int argc, char *argv[], char *env[]) {
}
/* end of ugliness */
/* Initialize event loop using libev */
struct ev_loop *loop = ev_default_loop(0);
if (loop == NULL)
die("Could not initialize libev. Bad LIBEV_FLAGS?\n");
struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io));
struct ev_check *xcb_check = scalloc(sizeof(struct ev_check));
struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare));
ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
ev_io_start(loop, xcb_watcher);
ev_check_init(xcb_check, xcb_check_cb);
ev_check_start(loop, xcb_check);
ev_prepare_init(xcb_prepare, xcb_prepare_cb);
ev_prepare_start(loop, xcb_prepare);
/* Grab the server to delay any events until we enter the eventloop */
xcb_grab_server(conn);
xcb_event_handlers_init(conn, &evenths);
/* DEBUG: Trap all events and print them */
@ -483,6 +291,10 @@ int main(int argc, char *argv[], char *env[]) {
GET_ATOM(_NET_WM_WINDOW_TYPE);
GET_ATOM(_NET_WM_DESKTOP);
GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
GET_ATOM(_NET_WM_WINDOW_TYPE_DIALOG);
GET_ATOM(_NET_WM_WINDOW_TYPE_UTILITY);
GET_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR);
GET_ATOM(_NET_WM_WINDOW_TYPE_SPLASH);
GET_ATOM(_NET_WM_STRUT_PARTIAL);
GET_ATOM(WM_PROTOCOLS);
GET_ATOM(WM_DELETE_WINDOW);
@ -495,9 +307,15 @@ int main(int argc, char *argv[], char *env[]) {
/* Watch _NET_WM_NAME (= title of the window in UTF-8) property */
xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL);
/* Watch WM_TRANSIENT_FOR property (to which client this popup window belongs) */
xcb_property_set_handler(&prophs, WM_TRANSIENT_FOR, UINT_MAX, handle_transient_for, NULL);
/* Watch WM_NAME (= title of the window in compound text) property for legacy applications */
xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL);
/* Watch WM_CLASS (= class of the window) */
xcb_property_set_handler(&prophs, WM_CLASS, 128, handle_windowclass_change, NULL);
/* Set up the atoms we support */
check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
@ -523,6 +341,15 @@ int main(int argc, char *argv[], char *env[]) {
}
}
/* Autostarting exec-lines */
struct Autostart *exec;
if (autostart) {
TAILQ_FOREACH(exec, &autostarts, autostarts) {
LOG("auto-starting %s\n", exec->command);
start_application(exec->command);
}
}
/* check for Xinerama */
LOG("Checking for Xinerama...\n");
initialize_xinerama(conn);
@ -548,8 +375,12 @@ int main(int argc, char *argv[], char *env[]) {
c_ws = &workspaces[screen->current_workspace];
}
/* Enter xcbs 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 libevs eventloop */
xcb_ungrab_server(conn);
ev_loop(loop, 0);
/* not reached */
return 0;

425
src/manage.c Normal file
View File

@ -0,0 +1,425 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* src/manage.c: Contains all functions for initially managing new windows
* (or existing ones on restart).
*
*/
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
#include "xcb.h"
#include "data.h"
#include "util.h"
#include "i3.h"
#include "table.h"
#include "config.h"
#include "handlers.h"
#include "layout.h"
#include "manage.h"
#include "floating.h"
#include "client.h"
/*
* Go through all existing windows (if the window manager is restarted) and manage them
*
*/
void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
xcb_query_tree_reply_t *reply;
int i, len;
xcb_window_t *children;
xcb_get_window_attributes_cookie_t *cookies;
/* Get the tree of windows whose parent is the root window (= all) */
if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
return;
len = xcb_query_tree_children_length(reply);
cookies = smalloc(len * sizeof(*cookies));
/* Request the window attributes for every window */
children = xcb_query_tree_children(reply);
for(i = 0; i < len; ++i)
cookies[i] = xcb_get_window_attributes(conn, children[i]);
/* Call manage_window with the attributes for every window */
for(i = 0; i < len; ++i) {
window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
manage_window(prophs, conn, children[i], wa);
}
free(reply);
free(cookies);
}
/*
* Do some sanity checks and then reparent the window.
*
*/
void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
LOG("managing window.\n");
xcb_drawable_t d = { window };
xcb_get_geometry_cookie_t geomc;
xcb_get_geometry_reply_t *geom;
xcb_get_window_attributes_reply_t *attr = 0;
if (wa.tag == TAG_COOKIE) {
/* Check if the window is mapped (it could be not mapped when intializing and
calling manage_window() for every window) */
if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
return;
if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
goto out;
wa.tag = TAG_VALUE;
wa.u.override_redirect = attr->override_redirect;
}
/* Dont 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;
/* Dont 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 fonts 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 dont want to drag & drop if we dont.
* 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 /* dont 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 its 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 its a dock we cant 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 clients 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 its not a dock, we can check on which workspace we should put it. */
/* Firstly, we need to get the windows 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 wont 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 were 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 its 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 dont event bother to redraw the layout that would not change
anything anyways */
client_toggle_fullscreen(conn, new);
return;
}
render_layout(conn);
}

View File

@ -24,6 +24,9 @@
#include "xcb.h"
#include "debug.h"
#include "layout.h"
#include "xinerama.h"
#include "config.h"
#include "floating.h"
/*
* Renders the resize window between the first/second container and resizes
@ -33,8 +36,14 @@
int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second,
resize_orientation_t orientation, xcb_button_press_event_t *event) {
int new_position;
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
i3Screen *screen = get_screen_containing(event->root_x, event->root_y);
if (screen == NULL) {
LOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y);
return 1;
}
LOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
/* FIXME: horizontal resizing causes empty spaces to exist */
if (orientation == O_HORIZONTAL) {
@ -57,9 +66,9 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
Rect helprect;
if (orientation == O_VERTICAL) {
helprect.x = event->root_x;
helprect.y = 0;
helprect.y = screen->rect.y;
helprect.width = 2;
helprect.height = root_screen->height_in_pixels;
helprect.height = screen->rect.height;
new_position = event->root_x;
} else {
helprect.x = 0;
@ -70,7 +79,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
}
mask = XCB_CW_BACK_PIXEL;
values[0] = get_colorpixel(conn, "#4c7899");
values[0] = config.client.focused.border;
mask |= XCB_CW_OVERRIDE_REDIRECT;
values[1] = 1;
@ -82,52 +91,32 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin);
xcb_grab_pointer(conn, false, root, XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION,
XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, grabwin, XCB_NONE, XCB_CURRENT_TIME);
xcb_flush(conn);
xcb_generic_event_t *inside_event;
/* Ive 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);

View File

@ -43,6 +43,7 @@ void init_table() {
for (int i = 0; i < 10; i++) {
workspaces[i].screen = NULL;
workspaces[i].num = i;
TAILQ_INIT(&(workspaces[i].floating_clients));
expand_table_cols(&(workspaces[i]));
expand_table_rows(&(workspaces[i]));
}

View File

@ -27,6 +27,7 @@
#include "layout.h"
#include "util.h"
#include "xcb.h"
#include "client.h"
static iconv_t conversion_descriptor = 0;
struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent);
@ -213,34 +214,17 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
if (rc == (size_t)-1) {
perror("Converting to UCS-2 failed");
*real_strlen = 0;
if (real_strlen != NULL)
*real_strlen = 0;
return NULL;
}
*real_strlen = ((buffer_size - output_size) / 2) - 1;
if (real_strlen != NULL)
*real_strlen = ((buffer_size - output_size) / 2) - 1;
return buffer;
}
/*
* Removes the given client from the container, either because it will be inserted into another
* one or because it was unmapped
*
*/
void remove_client_from_container(xcb_connection_t *conn, Client *client, Container *container) {
CIRCLEQ_REMOVE(&(container->clients), client, clients);
SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients);
/* If the container will be empty now and is in stacking mode, we need to
unmap the stack_win */
if (CIRCLEQ_EMPTY(&(container->clients)) && container->mode == MODE_STACK) {
struct Stack_Window *stack_win = &(container->stack_win);
stack_win->rect.height = 0;
xcb_unmap_window(conn, stack_win->window);
}
}
/*
* Returns the client which comes next in focus stack (= was selected before) for
* the given container, optionally excluding the given client.
@ -270,19 +254,43 @@ void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) {
/* Ignore notify events because they would cause focus to be changed */
ignore_enter_notify_forall(conn, u_ws, true);
/* Unmap all clients of the current workspace */
/* Unmap all clients of the given workspace */
int unmapped_clients = 0;
FOR_TABLE(u_ws)
CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) {
LOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child);
xcb_unmap_window(conn, client->frame);
unmapped_clients++;
}
/* If we did not unmap any clients, the workspace is empty and we can destroy it */
if (unmapped_clients == 0)
u_ws->screen = NULL;
/* To find floating clients, we traverse the focus stack */
SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) {
if (!client_is_floating(client))
continue;
/* Unmap the stack windows on the current workspace, if any */
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 given workspace, if any */
SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
if (stack_win->container->workspace == u_ws)
xcb_unmap_window(conn, stack_win->window);
@ -302,7 +310,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
return;
/* Store the old client */
Client *old_client = CUR_CELL->currently_focused;
Client *old_client = SLIST_FIRST(&(c_ws->focus_stack));
/* Check if the focus needs to be changed at all */
if (!set_anyways && (old_client == client)) {
@ -313,47 +321,54 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
/* Store current_row/current_col */
c_ws->current_row = current_row;
c_ws->current_col = current_col;
c_ws = client->container->workspace;
c_ws = client->workspace;
/* Load current_col/current_row if we switch to a client without a container */
current_col = c_ws->current_col;
current_row = c_ws->current_row;
/* Update container */
client->container->currently_focused = client;
if (client->container != NULL) {
client->container->currently_focused = client;
current_col = client->container->col;
current_row = client->container->row;
current_col = client->container->col;
current_row = client->container->row;
}
LOG("set_focus(frame %08x, child %08x, name %s)\n", client->frame, client->child, client->name);
/* Set focus to the entered window, and flush xcb buffer immediately */
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME);
//xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10);
/* Get the client which was last focused in this particular container, it may be a different
one than old_client */
Client *last_focused = get_last_focused_client(conn, client->container, NULL);
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);
/* 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 (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 were 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 dont need to */
redecorate_window(conn, client);
@ -390,7 +405,7 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode)
/* When entering stacking mode, we need to open a window on which we can draw the
title bars of the clients, it has height 1 because we dont bother here with
calculating the correct height - it will be adjusted when rendering anyways. */
Rect rect = {container->x, container->y, container->width, 1 };
Rect rect = {container->x, container->y, container->width, 1};
uint32_t mask = 0;
uint32_t values[2];
@ -429,129 +444,79 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode)
render_layout(conn);
if (container->currently_focused != NULL)
set_focus(conn, container->currently_focused, true);
}
if (container->currently_focused != NULL) {
/* We need to make sure that this client is above *each* of the
* other clients in this container */
Client *last_focused = get_last_focused_client(conn, container, container->currently_focused);
/*
* Warps the pointer into the given client (in the middle of it, to be specific), therefore
* selecting it
*
*/
void warp_pointer_into(xcb_connection_t *conn, Client *client) {
int mid_x = client->rect.width / 2,
mid_y = client->rect.height / 2;
xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
}
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
if (client == container->currently_focused || client == last_focused)
continue;
/*
* Toggles fullscreen mode for the given client. It updates the data structures and
* reconfigures (= resizes/moves) the client and its frame to the full size of the
* screen. When leaving fullscreen, re-rendering the layout is forced.
*
*/
void toggle_fullscreen(xcb_connection_t *conn, Client *client) {
/* clients without a container (docks) cannot be focused */
assert(client->container != NULL);
Workspace *workspace = client->container->workspace;
if (!client->fullscreen) {
if (workspace->fullscreen_client != NULL) {
LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
return;
LOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame);
uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, client->frame,
XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
}
client->fullscreen = true;
workspace->fullscreen_client = client;
LOG("Entering fullscreen mode...\n");
/* We just entered fullscreen mode, lets 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]);
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_configure_window(conn, client->frame, mask, values);
/* Childs 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 havent changed, it would not be
re-configured if we dont 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);
set_focus(conn, container->currently_focused, true);
}
xcb_flush(conn);
}
/*
* Returns true if the client supports the given protocol atom (like WM_DELETE_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.
*
*/
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;
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;
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;
to_class = sstrdup(window_classtitle);
/* Check if the clients 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 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;
/* 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);
}
xcb_client_message_event_t ev;
/* 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;
}
memset(&ev, 0, sizeof(xcb_client_message_event_t));
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;
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;
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;
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);
matching = client;
goto done;
}
}
done:
free(to_class);
FREE(to_title_ucs);
return matching;
}

View File

@ -22,7 +22,6 @@
#include "xcb.h"
TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts);
SLIST_HEAD(colorpixel_head, Colorpixel) colorpixels;
unsigned int xcb_numlock_mask;
/*
@ -74,42 +73,14 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern) {
*
*/
uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
/* Lookup this colorpixel in the cache */
struct Colorpixel *colorpixel;
SLIST_FOREACH(colorpixel, &(colorpixels), colorpixels)
if (strcmp(colorpixel->hex, hex) == 0)
return colorpixel->pixel;
#define RGB_8_TO_16(i) (65535 * ((i) & 0xFF) / 255)
char strgroups[3][3] = {{hex[1], hex[2], '\0'},
{hex[3], hex[4], '\0'},
{hex[5], hex[6], '\0'}};
int rgb16[3] = {RGB_8_TO_16(strtol(strgroups[0], NULL, 16)),
RGB_8_TO_16(strtol(strgroups[1], NULL, 16)),
RGB_8_TO_16(strtol(strgroups[2], NULL, 16))};
uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
(strtol(strgroups[1], NULL, 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);
return pixel;
return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
}
/*
@ -267,10 +238,11 @@ void xcb_get_numlock_mask(xcb_connection_t *conn) {
/* For now, we only use the first keysymbol. */
xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
xcb_keycode_t numlock = *numlock_syms;
free(numlock_syms);
#endif
/* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */
for (mask = 0; mask < sizeof(masks); mask++)
for (mask = 0; mask < 8; mask++)
for (i = 0; i < reply->keycodes_per_modifier; i++)
if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock)
xcb_numlock_mask = masks[mask];
@ -278,3 +250,12 @@ void xcb_get_numlock_mask(xcb_connection_t *conn) {
xcb_key_symbols_free(keysyms);
free(reply);
}
/*
* Raises the given window (typically client->frame) above all other windows
*
*/
void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) {
uint32_t values[] = { XCB_STACK_MODE_ABOVE };
xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values);
}

View File

@ -100,6 +100,31 @@ i3Screen *get_screen_most(direction_t direction) {
return candidate;
}
static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspace *workspace) {
i3Font *font = load_font(conn, config.font);
workspace->screen = screen;
screen->current_workspace = workspace->num;
/* Create a bar for each screen */
Rect bar_rect = {screen->rect.x,
screen->rect.height - (font->height + 6),
screen->rect.x + screen->rect.width,
font->height + 6};
uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS};
screen->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
screen->bargc = xcb_generate_id(conn);
xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0);
SLIST_INIT(&(screen->dock_clients));
/* Copy dimensions */
memcpy(&(workspace->rect), &(screen->rect), sizeof(Rect));
LOG("that is virtual screen at %d x %d with %d x %d\n",
screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
}
/*
* Fills virtual_screens with exactly one screen with width/height of the whole X server.
*
@ -114,6 +139,10 @@ static void disable_xinerama(xcb_connection_t *conn) {
s->rect.width = root_screen->width_in_pixels;
s->rect.height = root_screen->height_in_pixels;
num_screens = 1;
s->num = 0;
initialize_screen(conn, s, &(workspaces[0]));
TAILQ_INSERT_TAIL(virtual_screens, s, screens);
xinerama_enabled = false;
@ -170,31 +199,6 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis
}
}
static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspace *workspace) {
i3Font *font = load_font(conn, config.font);
workspace->screen = screen;
screen->current_workspace = workspace->num;
/* Create a bar for each screen */
Rect bar_rect = {screen->rect.x,
screen->rect.height - (font->height + 6),
screen->rect.x + screen->rect.width,
font->height + 6};
uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS};
screen->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
screen->bargc = xcb_generate_id(conn);
xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0);
SLIST_INIT(&(screen->dock_clients));
/* Copy dimensions */
memcpy(&(workspace->rect), &(screen->rect), sizeof(Rect));
LOG("that is virtual screen at %d x %d with %d x %d\n",
screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
}
/*
* We have just established a connection to the X server and need the initial Xinerama
* information to setup workspaces for each screen.

View File

@ -78,7 +78,6 @@ li {
<li>
<a href="/screenshots/i3-5.png">i3 v3.α-bf2</a>, mc, vim, xosview, mplayer, irssi, gajim, i3status
</li>
</ul>
<h2>Screencasts</h2>