Merge branch 'next' (3.β is stable now)
This commit is contained in:
commit
28c4b045d6
36
CMDMODE
36
CMDMODE
|
@ -1,3 +1,9 @@
|
|||
---------------------
|
||||
- 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>
|
||||
|
@ -7,30 +13,32 @@ 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
|
||||
|
||||
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
|
||||
|
|
6
DEPENDS
6
DEPENDS
|
@ -5,6 +5,7 @@ In that case, please try using the versions mentioned below until a fix is provi
|
|||
* xcb-proto-1.3 (2008-12-10)
|
||||
* libxcb-1.1.93 (2008-12-11)
|
||||
* xcb-util-0.3.3 (2009-01-31)
|
||||
* libev
|
||||
* asciidoc >= 8.3.0 for docs/hacking-howto
|
||||
* asciidoc, xmlto, docbook-xml for man/i3.man
|
||||
* Xlib, the one that comes with your X-Server
|
||||
|
@ -14,9 +15,10 @@ Recommendations:
|
|||
* dmenu for launching applications
|
||||
|
||||
Get the libraries from:
|
||||
http://xcb.freedesktop.org/dist/xcb-proto-1.3.tar.bz2
|
||||
http://xcb.freedesktop.org/dist/xcb-proto-1.5.tar.bz2
|
||||
http://xcb.freedesktop.org/dist/libxcb-1.1.93.tar.bz2
|
||||
http://xcb.freedesktop.org/dist/xcb-util-0.3.3.tar.bz2
|
||||
http://xcb.freedesktop.org/dist/xcb-util-0.3.5.tar.bz2
|
||||
http://libev.schmorp.de/
|
||||
|
||||
http://i3.zekjur.net/i3lock/
|
||||
http://tools.suckless.org/dmenu
|
||||
|
|
1
Makefile
1
Makefile
|
@ -37,6 +37,7 @@ LDFLAGS += -lxcb-aux
|
|||
LDFLAGS += -lxcb-icccm
|
||||
LDFLAGS += -lxcb-xinerama
|
||||
LDFLAGS += -lX11
|
||||
LDFLAGS += -lev
|
||||
LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib
|
||||
|
||||
ifeq ($(UNAME),NetBSD)
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
Release notes for i3 v3.β
|
||||
-----------------------------
|
||||
|
||||
This is the second version (3.β, transcribed 3.b) of i3. It is considered stable.
|
||||
|
||||
The most important change probably is the implementation of floating clients,
|
||||
primarily useful for dialog/toolbar/popup/splash windows. When using i3 for
|
||||
managing floating windows other than the ones mentioned beforehand, please
|
||||
keep in mind that i3 is a tiling window manager in the first place and thus
|
||||
you might better use a "traditional" window manager when having to deal a
|
||||
lot with floating windows.
|
||||
|
||||
Now that you’re warned, let’s have a quick glance at the other new features:
|
||||
* jumping to other windows by specifying their position or window class/title
|
||||
* assigning clients to specific workspaces by window class/title
|
||||
* automatically starting programs (such as i3status + dzen2)
|
||||
* configurable colors
|
||||
* variables in configfile
|
||||
|
||||
Furthermore, we now have a user’s guide which should be the first document
|
||||
you read when new to i3 (apart from the manpage).
|
||||
|
||||
Thanks for this release go out to mist, Atsutane, ch3ka, urs, Moredread,
|
||||
badboy and all other people who reported bugs/made suggestions.
|
||||
|
||||
A list of changes follows:
|
||||
|
||||
* Bugfix: Correctly handle col-/rowspanned containers when setting focus.
|
||||
* Bugfix: Correctly handle col-/rowspanned containers when snapping.
|
||||
* Bugfix: Force reconfiguration of all windows on workspaces which are
|
||||
re-assigned because a screen was detached.
|
||||
* Bugfix: Several bugs in resizing table columns fixed.
|
||||
* Bugfix: Resizing should now work correctly in all cases.
|
||||
* Bugfix: Correctly re-assign dock windows when workspace is destroyed.
|
||||
* Bugfix: Correctly handle Mode_switch modifier.
|
||||
* Bugfix: Don't raise clients in fullscreen mode.
|
||||
* Bugfix: Re-assign dock windows to different workspaces when a workspace
|
||||
is detached.
|
||||
* Bugfix: Fix crash because of workspace-pointer which did not get updated
|
||||
* Bugfix: Correctly initialize screen when Xinerama is disabled.
|
||||
* Bugfix: Fullscreen window movement and focus problems fixed
|
||||
* Implement jumping to other windows by specifying their position or
|
||||
window class/title.
|
||||
* Implement jumping back by using the focus stack.
|
||||
* Implement autostart (exec-command in configuration file).
|
||||
* Implement floating.
|
||||
* Implement automatically assigning clients on specific workspaces.
|
||||
* Implement variables in configfile.
|
||||
* Colors are now configurable.
|
||||
|
||||
-- Michael Stapelberg, 2009-06-21
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -19,6 +19,7 @@ build:
|
|||
# Add here commands to compile the package.
|
||||
$(MAKE)
|
||||
$(MAKE) -C man
|
||||
$(MAKE) -C docs
|
||||
|
||||
touch $@
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
|
@ -0,0 +1,371 @@
|
|||
i3 User’s Guide
|
||||
===============
|
||||
Michael Stapelberg <michael+i3@stapelberg.de>
|
||||
June 2009
|
||||
|
||||
This document contains all information you need to configuring and using the i3
|
||||
window manager. If it does not, please contact me on IRC, Jabber or E-Mail and
|
||||
I’ll help you out.
|
||||
|
||||
For a complete listing of the default keybindings, please see the manpage.
|
||||
|
||||
== Using i3
|
||||
|
||||
=== Creating terminals and moving around
|
||||
|
||||
A very basic operation is to create a new terminal. By default, the keybinding
|
||||
for that is Mod1+Enter, that is Alt+Enter in the default configuration. By
|
||||
pressing Mod1+Enter, a new terminal will be created and it will fill the whole
|
||||
space which is available on your screen.
|
||||
|
||||
image:single_terminal.png[Single terminal]
|
||||
|
||||
It is important to keep in mind that i3 uses a table to manage your windows. At
|
||||
the moment, you have exactly one column and one row which leaves you with one
|
||||
cell. In this cell, there is a container in which your newly opened terminal is.
|
||||
|
||||
If you now open another terminal, you still have only one cell. However, the
|
||||
container has both of your terminals. So, a container is just a group of clients
|
||||
with a specific layout. You can resize containers as they directly resemble
|
||||
columns/rows of the layout table.
|
||||
|
||||
image:two_terminals.png[Two terminals]
|
||||
|
||||
To move the focus between the two terminals, you use the direction keys which
|
||||
you may know from the editor +vi+. However, in i3, your homerow is used for
|
||||
these keys (in +vi+, the keys are shifted to the left by one for compatibility
|
||||
with most keyboard layouts). Therefore, +Mod1+J+ is left, +Mod1+K+ is down, +Mod1+L+
|
||||
is up and `Mod1+;` is right. So, to switch between the terminals, use +Mod1+K+ or
|
||||
+Mod1+L+.
|
||||
|
||||
To create a new row/column, you can simply move a terminal (or any other window)
|
||||
to the direction you want to expand your table. So, let’s expand the table to
|
||||
the right by pressing `Mod1+Shift+;`.
|
||||
|
||||
image:two_columns.png[Two columns]
|
||||
|
||||
=== Changing mode of containers
|
||||
|
||||
A container can be in two modes at the moment (more to be implemented later):
|
||||
+default+ or +stacking+. In default mode, clients are sized so that every client
|
||||
gets an equal amount of space of the container. In stacking mode, only one
|
||||
focused client of the container is displayed and you get a list of windows
|
||||
at the top of the container.
|
||||
|
||||
To switch the mode, press +Mod1+h+ for stacking and +Mod1+e+ for default.
|
||||
|
||||
=== Toggling fullscreen mode for a window
|
||||
|
||||
To display a window fullscreen or to go out of fullscreen mode again, press
|
||||
+Mod1+f+.
|
||||
|
||||
=== Opening other applications
|
||||
|
||||
Aside from opening applicatios from a terminal, you can also use the handy
|
||||
+dmenu+ which is opened by pressing +Mod1+v+ by default. Just type the name
|
||||
(or a part of it) of the application which you want to open. It has to be in
|
||||
your +$PATH+ for that to work.
|
||||
|
||||
Furthermore, if you have applications you open very frequently, you can also
|
||||
create a keybinding for it. See the section "Configuring i3" for details.
|
||||
|
||||
=== Closing windows
|
||||
|
||||
If an application does not provide a mechanism to close (most applications
|
||||
provide a menu, the escape key or a shortcut like +Control+W+ to close), you
|
||||
can press +Mod1+Shift+q+ to kill a window. For applications which support
|
||||
the WM_DELETE protocol, this will correctly close the application (saving
|
||||
any modifications or doing other cleanup). If the application doesn’t support
|
||||
it, your X server will kill the window and the behaviour depends on the
|
||||
application.
|
||||
|
||||
=== Using workspaces
|
||||
|
||||
Workspaces are an easy way to group a set of windows. By default, you are on
|
||||
the first workspace, as the bar on the bottom left indicates. To switch to
|
||||
another workspace, press +Mod1+num+ where +num+ is the number of the workspace
|
||||
you want to use. If the workspace does not exist yet, it will be created.
|
||||
|
||||
A common paradigm is to put the web browser on one workspace, communication
|
||||
applications (+mutt+, +irssi+, ...) on another one and the ones with which you
|
||||
work on the third one. Of course, there is no need to follow this approach.
|
||||
|
||||
If you have multiple screens, a workspace will be created on each screen. If
|
||||
you open a new workspace, it will be bound to the screen you created it on.
|
||||
When you switch to a workspace on another screen, i3 will set focus to this
|
||||
screen.
|
||||
|
||||
=== Moving windows to workspaces
|
||||
|
||||
To move a window to another workspace, simply press +Mod1+Shift+num+ where
|
||||
+num+ is (like when switching workspaces) the number of the target workspace.
|
||||
Similarly to switching workspaces, the target workspace will be created if
|
||||
it does not yet exist.
|
||||
|
||||
=== Resizing columns
|
||||
|
||||
To resize columns just grab the border between the two columns and move it to
|
||||
the wanted size.
|
||||
|
||||
A command for doing this via keyboard will be implemented soon.
|
||||
|
||||
=== Restarting i3 inplace
|
||||
|
||||
To restart i3 inplace (and thus get it into a clean state if it has a bug, to
|
||||
reload your configuration or even to upgrade to a newer version of i3) you
|
||||
can use +Mod1+Shift+r+. Be aware, though, that this kills your current layout
|
||||
and all the windows you have opened will be put in a default container in only
|
||||
one cell. Saving the layout will be implemented in a later version.
|
||||
|
||||
=== Exiting i3
|
||||
|
||||
To cleanly exit i3 without killing your X server, you can use +Mod1+Shift+e+.
|
||||
|
||||
=== Snapping
|
||||
|
||||
Snapping is a mechanism to increase/decrease the colspan/rowspan of a container.
|
||||
Colspan/rowspan is the amount of columns/rows a specific cell of the table
|
||||
consumes. This is easier explained by giving an example, so take the following
|
||||
layout:
|
||||
|
||||
image:snapping.png[Snapping example]
|
||||
|
||||
To use the full size of your screen, you can now snap container 3 downwards
|
||||
by pressing +Mod1+Control+k+ (or snap container 2 rightwards).
|
||||
|
||||
=== Floating
|
||||
|
||||
Floating is the opposite of tiling mode. The position and size of a window
|
||||
are then not managed by i3, but by you. Using this mode violates the tiling
|
||||
paradigm but can be useful for some corner cases like "Save as" dialog
|
||||
windows or toolbar windows (GIMP or similar).
|
||||
|
||||
You can enable floating for a window by pressing +Mod1+Shift+Space+. By
|
||||
dragging the window’s titlebar with your mouse, you can move the window
|
||||
around. By grabbing the borders and moving them you can resize the window.
|
||||
|
||||
Bindings for doing this with your keyboard will follow.
|
||||
|
||||
Floating clients are always on top of tiling clients.
|
||||
|
||||
== Configuring i3
|
||||
|
||||
This is where the real fun begins ;-). Most things are very dependant on your
|
||||
ideal working environment, so we can’t make reasonable defaults for them.
|
||||
|
||||
While not using a programming language for the configuration, i3 stays
|
||||
quite flexible regarding to the things you usually want your window manager
|
||||
to do.
|
||||
|
||||
For example, you can configure bindings to jump to specific windows,
|
||||
you can set specific applications to start on a specific workspace, you can
|
||||
automatically start applications, you can change the colors of i3 or bind
|
||||
your keys to do useful stuff.
|
||||
|
||||
terminal::
|
||||
Specifies the terminal emulator program you prefer. It will be started
|
||||
by default when you press Mod1+Enter, but you can overwrite this. Refer
|
||||
to it as +$terminal+ to keep things modular.
|
||||
font::
|
||||
Specifies the default font you want i3 to use. Use an X core font
|
||||
descriptor here, like
|
||||
+-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1+. You can
|
||||
use +xfontsel(1)+ to pick one.
|
||||
|
||||
=== Keyboard bindings
|
||||
|
||||
You can use each command (see below) using keyboard bindings. At the moment,
|
||||
keyboard bindings require you to specify the keycode (38) of the key, not its key
|
||||
symbol ("a"). This has some advantages (keybindings make sense regardless of
|
||||
the layout you type) and some disadvantages (hard to remember, you have to look
|
||||
them up every time).
|
||||
|
||||
*Syntax*:
|
||||
--------------------------------
|
||||
bind [Modifiers+]keycode command
|
||||
--------------------------------
|
||||
|
||||
*Examples*:
|
||||
--------------------------------
|
||||
# Fullscreen
|
||||
bind Mod1+41 f
|
||||
|
||||
# Restart
|
||||
bind Mod1+Shift+27 restart
|
||||
--------------------------------
|
||||
|
||||
Available Modifiers:
|
||||
|
||||
Mod1-Mod5, Shift, Control::
|
||||
Standard modifiers, see +xmodmap(1)+
|
||||
|
||||
Mode_switch::
|
||||
Unlike other window managers, i3 can use Mode_switch as a modifier. This allows
|
||||
you to remap capslock (for example) to Mode_switch and use it for both: typing
|
||||
umlauts or special characters 'and' having some comfortably reachable key
|
||||
bindings. For example, when typing, capslock+1 or capslock+2 for switching
|
||||
workspaces is totally convenient. Try it :-).
|
||||
|
||||
=== The floating modifier
|
||||
|
||||
To move floating windows with your mouse, you can either grab their titlebar
|
||||
or configure the so called floating modifier which you can then press and
|
||||
click anywhere in the window itself. The most common setup is to configure
|
||||
it as the same one you use for managing windows (Mod1 for example). Afterwards,
|
||||
you can press Mod1, click into a window using your left mouse button and drag
|
||||
it to the position you want it at.
|
||||
|
||||
*Syntax*:
|
||||
--------------------------------
|
||||
floating_modifier <Modifiers>
|
||||
--------------------------------
|
||||
|
||||
*Examples*:
|
||||
--------------------------------
|
||||
floating_modifier Mod1
|
||||
--------------------------------
|
||||
|
||||
|
||||
=== Variables
|
||||
|
||||
As you learned in the previous section about keyboard bindings, you will have
|
||||
to configure lots of bindings containing modifier keys. If you want to save
|
||||
yourself some typing and have the possibility to change the modifier you want
|
||||
to use later, variables can be handy.
|
||||
|
||||
*Syntax*:
|
||||
--------------
|
||||
set name value
|
||||
--------------
|
||||
|
||||
*Examples*:
|
||||
------------------------
|
||||
set $m Mod1
|
||||
bind $m+Shift+27 restart
|
||||
------------------------
|
||||
|
||||
Variables are directly replaced in the file when parsing, there is no fancy
|
||||
handling and there are absolutely no plans to change this. If you need a more
|
||||
dynamic configuration, you should create a little script, like when configuring
|
||||
wmii.
|
||||
|
||||
=== Automatically putting clients on specific workspaces
|
||||
|
||||
It is recommended that you match on window classes whereever possible because
|
||||
some applications first create their window and then care about setting the
|
||||
correct title. Firefox with Vimperator comes to mind, as the window starts up
|
||||
being named Firefox and only when Vimperator is loaded, the title changes. As
|
||||
i3 will get the title as soon as the application maps the window (mapping means
|
||||
actually displaying it on the screen), you’d need to have to match on Firefox
|
||||
in this case.
|
||||
|
||||
You can use the special workspace +~+ to specify that matching clients should
|
||||
be put into floating mode.
|
||||
|
||||
*Syntax*:
|
||||
----------------------------------------------------
|
||||
assign ["]window class[/window title]["] [→] workspace
|
||||
----------------------------------------------------
|
||||
|
||||
*Examples*:
|
||||
----------------------
|
||||
assign urxvt 2
|
||||
assign urxvt → 2
|
||||
assign "urxvt" → 2
|
||||
assign "urxvt/VIM" → 3
|
||||
assign "gecko" → ~
|
||||
----------------------
|
||||
|
||||
=== Automatically starting applications on startup
|
||||
|
||||
By using the +exec+ keyword outside a keybinding, you can configure which
|
||||
commands will be performed by i3 on the first start (not when reloading inplace
|
||||
however). The commands will be run in order.
|
||||
|
||||
*Syntax*:
|
||||
------------
|
||||
exec command
|
||||
------------
|
||||
|
||||
*Examples*:
|
||||
--------------------------------
|
||||
exec sudo i3status | dzen2 -dock
|
||||
--------------------------------
|
||||
|
||||
=== Jumping to specific windows
|
||||
|
||||
Especially when in a multi-monitor environment, you want to quickly jump to a specific
|
||||
window, for example while currently working on workspace 3 you may want to jump to
|
||||
your mailclient to mail your boss that you’ve achieved some important goal. Instead
|
||||
of figuring out how to navigate to your mailclient, it would be more convenient to
|
||||
have a shortcut.
|
||||
|
||||
*Syntax*:
|
||||
----------------------------------------------------
|
||||
jump ["]window class[/window title]["]
|
||||
jump workspace [ column row ]
|
||||
----------------------------------------------------
|
||||
|
||||
You can either use the same matching algorithm as in the +assign+ command (see above)
|
||||
or you can specify the position of the client if you always use the same layout.
|
||||
|
||||
*Examples*:
|
||||
--------------------------------------
|
||||
# Get me to the next open VIM instance
|
||||
bind Mod1+38 jump "urxvt/VIM"
|
||||
--------------------------------------
|
||||
|
||||
=== Traveling the focus stack
|
||||
|
||||
This mechanism can be thought of as the opposite of the +jump+ command. It travels
|
||||
the focus stack and jumps to the window you focused before.
|
||||
|
||||
*Syntax*:
|
||||
--------------
|
||||
focus [number] | floating | tilling | ft
|
||||
--------------
|
||||
|
||||
Where +number+ by default is 1 meaning that the next client in the focus stack will
|
||||
be selected.
|
||||
|
||||
The special values have the following meaning:
|
||||
|
||||
floating::
|
||||
The next floating window is selected.
|
||||
tiling::
|
||||
The next tiling window is selected.
|
||||
ft::
|
||||
If the current window is floating, the next tiling window will be selected
|
||||
and vice-versa.
|
||||
|
||||
=== Changing colors
|
||||
|
||||
You can change all colors which i3 uses to draw the window decorations and the
|
||||
bottom bar.
|
||||
|
||||
*Syntax*:
|
||||
--------------------------------------------
|
||||
colorclass border background text
|
||||
--------------------------------------------
|
||||
|
||||
Where colorclass can be one of:
|
||||
|
||||
client.focused::
|
||||
A client which currently has the focus.
|
||||
client.focused_inactive::
|
||||
A client which is the focused one of its container, but it does not have
|
||||
the focus at the moment.
|
||||
client.unfocused::
|
||||
A client which is not the focused one of its container.
|
||||
bar.focused::
|
||||
The current workspace in the bottom bar.
|
||||
bar.unfocused::
|
||||
All other workspaces in the bottom bar.
|
||||
|
||||
Colors are in HTML hex format, see below.
|
||||
|
||||
*Examples*:
|
||||
--------------------------------------
|
||||
# class border backgr. text
|
||||
client.focused #2F343A #900000 #FFFFFF
|
||||
--------------------------------------
|
13
i3.config
13
i3.config
|
@ -1,11 +1,17 @@
|
|||
# This configuration uses Mod1 and Mod3. Make sure they are mapped properly using xev(1)
|
||||
# and xmodmap(1). Usually, Mod1 is Alt (Alt_L) and Mod3 is Windows (Super_L)
|
||||
|
||||
# Tell i3 about your preferred terminal. You can refer to this as $terminal
|
||||
# later. It is recommended to set this option to allow i3 to open a terminal
|
||||
# containing the introduction on first start.
|
||||
terminal /usr/bin/urxvt
|
||||
|
||||
# ISO 10646 = Unicode
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
# Use Mouse+Mod1 to drag floating windows to their wanted position
|
||||
floating_modifier Mod1
|
||||
|
||||
# Fullscreen (Mod1+f)
|
||||
bind Mod1+41 f
|
||||
|
||||
|
@ -15,6 +21,13 @@ bind Mod1+43 s
|
|||
# Default (Mod1+e)
|
||||
bind Mod1+26 d
|
||||
|
||||
# Toggle tiling/floating of the current window (Mod1+Shift+Space)
|
||||
bind Mod1+Shift+65 t
|
||||
|
||||
# Go into the tiling layer / floating layer, depending on whether
|
||||
# the current window is tiling / floating (Mod1+t)
|
||||
bind Mod1+28 focus ft
|
||||
|
||||
# Focus (Mod1+j/k/l/;)
|
||||
bind Mod1+44 h
|
||||
bind Mod1+45 j
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* (c) 2009 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
*/
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
#include "data.h"
|
||||
|
||||
#ifndef _CLIENT_H
|
||||
#define _CLIENT_H
|
||||
|
||||
/**
|
||||
* Removes the given client from the container, either because it will be inserted into another
|
||||
* one or because it was unmapped
|
||||
*
|
||||
*/
|
||||
void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container, bool remove_from_focusstack);
|
||||
|
||||
/**
|
||||
* Warps the pointer into the given client (in the middle of it, to be specific), therefore
|
||||
* selecting it
|
||||
*
|
||||
*/
|
||||
void client_warp_pointer_into(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
|
||||
*
|
||||
*/
|
||||
void client_kill(xcb_connection_t *conn, Client *window);
|
||||
|
||||
/**
|
||||
* Checks if the given window class and title match the given client
|
||||
* Window title is passed as "normal" string and as UCS-2 converted string for
|
||||
* matching _NET_WM_NAME capable clients as well as those using legacy hints.
|
||||
*
|
||||
*/
|
||||
bool client_matches_class_name(Client *client, char *to_class, char *to_title,
|
||||
char *to_title_ucs, int to_title_ucs_len);
|
||||
|
||||
/**
|
||||
* Enters fullscreen mode for the given client. This is called by toggle_fullscreen
|
||||
* and when moving a fullscreen client to another screen.
|
||||
*
|
||||
*/
|
||||
void client_enter_fullscreen(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Toggles fullscreen mode for the given client. It updates the data structures and
|
||||
* reconfigures (= resizes/moves) the client and its frame to the full size of the
|
||||
* screen. When leaving fullscreen, re-rendering the layout is forced.
|
||||
*
|
||||
*/
|
||||
void client_toggle_fullscreen(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Sets the position of the given client in the X stack to the highest (tiling layer is always
|
||||
* on the same position, so this doesn’t matter) below the first floating client, so that
|
||||
* floating windows are always on top.
|
||||
*
|
||||
*/
|
||||
void client_set_below_floating(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Returns true if the client is floating. Makes the code more beatiful, as floating
|
||||
* is not simply a boolean, but also saves whether the user selected the current state
|
||||
* or whether it was automatically set.
|
||||
*
|
||||
*/
|
||||
bool client_is_floating(Client *client);
|
||||
|
||||
#endif
|
|
@ -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;
|
||||
|
||||
/** 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
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* (c) 2009 Michael Stapelberg and contributors
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -161,12 +161,21 @@ struct Workspace {
|
|||
int current_row;
|
||||
int current_col;
|
||||
|
||||
/* Should clients on this workspace be automatically floating? */
|
||||
bool auto_float;
|
||||
/* Are the floating clients on this workspace currently hidden? */
|
||||
bool floating_hidden;
|
||||
|
||||
Client *fullscreen_client;
|
||||
|
||||
/* The focus stack contains the clients in the correct order of focus so that
|
||||
the focus can be reverted correctly when a client is closed */
|
||||
SLIST_HEAD(focus_stack_head, Client) focus_stack;
|
||||
|
||||
/* This tail queue contains the floating clients in order of when they were first
|
||||
* set to floating (new floating clients are just appended) */
|
||||
TAILQ_HEAD(floating_clients_head, Client) floating_clients;
|
||||
|
||||
/* Backpointer to the screen this workspace is on */
|
||||
i3Screen *screen;
|
||||
|
||||
|
@ -197,6 +206,30 @@ struct Binding {
|
|||
TAILQ_ENTRY(Binding) bindings;
|
||||
};
|
||||
|
||||
/*
|
||||
* Holds a command specified by an exec-line in the config (see src/config.c)
|
||||
*
|
||||
*/
|
||||
struct Autostart {
|
||||
/* Command, like in command mode */
|
||||
char *command;
|
||||
TAILQ_ENTRY(Autostart) autostarts;
|
||||
};
|
||||
|
||||
/*
|
||||
* Holds an assignment for a given window class/title to a specific workspace
|
||||
* (see src/config.c)
|
||||
*
|
||||
*/
|
||||
struct Assignment {
|
||||
char *windowclass_title;
|
||||
/* floating is true if this was an assignment to the special workspace "~".
|
||||
* Matching clients will be put into floating mode automatically. */
|
||||
bool floating;
|
||||
int workspace;
|
||||
TAILQ_ENTRY(Assignment) assignments;
|
||||
};
|
||||
|
||||
/*
|
||||
* Data structure for cached font information:
|
||||
* - font id in X11 (load it once)
|
||||
|
@ -221,6 +254,10 @@ struct Font {
|
|||
*
|
||||
*/
|
||||
struct Client {
|
||||
/* initialized will be set to true if the client was fully initialized by
|
||||
* manage_window() and all functions can be used normally */
|
||||
bool initialized;
|
||||
|
||||
/* if you set a client to floating and set it back to managed, it does remember its old
|
||||
position and *tries* to get back there */
|
||||
Cell old_position;
|
||||
|
@ -232,6 +269,8 @@ struct Client {
|
|||
|
||||
/* x, y, width, height of the frame */
|
||||
Rect rect;
|
||||
/* Position in floating mode and in tiling mode are saved separately */
|
||||
Rect floating_rect;
|
||||
/* x, y, width, height of the child (relative to its frame) */
|
||||
Rect child_rect;
|
||||
|
||||
|
@ -255,9 +294,18 @@ struct Client {
|
|||
legacy window names are ignored. */
|
||||
bool uses_net_wm_name;
|
||||
|
||||
/* Holds the WM_CLASS, useful for matching the client in commands */
|
||||
char *window_class;
|
||||
|
||||
/* fullscreen is pretty obvious */
|
||||
bool fullscreen;
|
||||
|
||||
/* floating? (= not in tiling layout) This cannot be simply a bool because we want to keep track
|
||||
* of whether the status was set by the application (by setting WM_CLASS to tools for example) or
|
||||
* by the user. The user’s choice overwrites automatic mode, of course. The order of the values
|
||||
* is important because we check with >= FLOATING_AUTO_ON if a client is floating. */
|
||||
enum { FLOATING_AUTO_OFF = 0, FLOATING_USER_OFF = 1, FLOATING_AUTO_ON = 2, FLOATING_USER_ON = 3 } floating;
|
||||
|
||||
/* Ensure TITLEBAR_TOP maps to 0 because we use calloc for initialization later */
|
||||
enum { TITLEBAR_TOP = 0, TITLEBAR_LEFT, TITLEBAR_RIGHT, TITLEBAR_BOTTOM, TITLEBAR_OFF } titlebar_position;
|
||||
|
||||
|
@ -283,6 +331,7 @@ struct Client {
|
|||
CIRCLEQ_ENTRY(Client) clients;
|
||||
SLIST_ENTRY(Client) dock_clients;
|
||||
SLIST_ENTRY(Client) focus_clients;
|
||||
TAILQ_ENTRY(Client) floating_clients;
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -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
|
|
@ -87,6 +87,14 @@ 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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
11
include/i3.h
11
include/i3.h
|
@ -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
|
||||
|
|
|
@ -36,6 +36,18 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
|
|||
*/
|
||||
void redecorate_window(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Pushes the client’s x and y coordinates to X11
|
||||
*
|
||||
*/
|
||||
void reposition_client(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Pushes the client’s width/height to X11 and resizes the child window
|
||||
*
|
||||
*/
|
||||
void resize_client(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Renders the given container. Is called by render_layout() or individually (for example
|
||||
* when focus changes in a stacking container)
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
41
man/i3.man
41
man/i3.man
|
@ -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
|
||||
|
|
|
@ -0,0 +1,250 @@
|
|||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
* client.c: holds all client-specific functions
|
||||
*
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_icccm.h>
|
||||
|
||||
#include "data.h"
|
||||
#include "i3.h"
|
||||
#include "xcb.h"
|
||||
#include "util.h"
|
||||
#include "queue.h"
|
||||
#include "layout.h"
|
||||
#include "client.h"
|
||||
|
||||
/*
|
||||
* Removes the given client from the container, either because it will be inserted into another
|
||||
* one or because it was unmapped
|
||||
*
|
||||
*/
|
||||
void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container, bool remove_from_focusstack) {
|
||||
CIRCLEQ_REMOVE(&(container->clients), client, clients);
|
||||
|
||||
if (remove_from_focusstack)
|
||||
SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients);
|
||||
|
||||
/* If the container will be empty now and is in stacking mode, we need to
|
||||
unmap the stack_win */
|
||||
if (CIRCLEQ_EMPTY(&(container->clients)) && container->mode == MODE_STACK) {
|
||||
struct Stack_Window *stack_win = &(container->stack_win);
|
||||
stack_win->rect.height = 0;
|
||||
xcb_unmap_window(conn, stack_win->window);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Warps the pointer into the given client (in the middle of it, to be specific), therefore
|
||||
* selecting it
|
||||
*
|
||||
*/
|
||||
void client_warp_pointer_into(xcb_connection_t *conn, Client *client) {
|
||||
int mid_x = client->rect.width / 2,
|
||||
mid_y = client->rect.height / 2;
|
||||
xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
|
||||
*
|
||||
*/
|
||||
static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
|
||||
xcb_get_property_cookie_t cookie;
|
||||
xcb_get_wm_protocols_reply_t protocols;
|
||||
bool result = false;
|
||||
|
||||
cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
|
||||
if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
|
||||
return false;
|
||||
|
||||
/* Check if the client’s protocols have the requested atom set */
|
||||
for (uint32_t i = 0; i < protocols.atoms_len; i++)
|
||||
if (protocols.atoms[i] == atom)
|
||||
result = true;
|
||||
|
||||
xcb_get_wm_protocols_reply_wipe(&protocols);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
|
||||
*
|
||||
*/
|
||||
void client_kill(xcb_connection_t *conn, Client *window) {
|
||||
/* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */
|
||||
if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) {
|
||||
LOG("Killing window the hard way\n");
|
||||
xcb_kill_client(conn, window->child);
|
||||
return;
|
||||
}
|
||||
|
||||
xcb_client_message_event_t ev;
|
||||
|
||||
memset(&ev, 0, sizeof(xcb_client_message_event_t));
|
||||
|
||||
ev.response_type = XCB_CLIENT_MESSAGE;
|
||||
ev.window = window->child;
|
||||
ev.type = atoms[WM_PROTOCOLS];
|
||||
ev.format = 32;
|
||||
ev.data.data32[0] = atoms[WM_DELETE_WINDOW];
|
||||
ev.data.data32[1] = XCB_CURRENT_TIME;
|
||||
|
||||
LOG("Sending WM_DELETE to the client\n");
|
||||
xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if the given window class and title match the given client
|
||||
* Window title is passed as "normal" string and as UCS-2 converted string for
|
||||
* matching _NET_WM_NAME capable clients as well as those using legacy hints.
|
||||
*
|
||||
*/
|
||||
bool client_matches_class_name(Client *client, char *to_class, char *to_title,
|
||||
char *to_title_ucs, int to_title_ucs_len) {
|
||||
/* Check if the given class is part of the window class */
|
||||
if (client->window_class == NULL || strcasestr(client->window_class, to_class) == NULL)
|
||||
return false;
|
||||
|
||||
/* If no title was given, we’re done */
|
||||
if (to_title == NULL)
|
||||
return true;
|
||||
|
||||
if (client->name_len > -1) {
|
||||
/* UCS-2 converted window titles */
|
||||
if (client->name == NULL || memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL)
|
||||
return false;
|
||||
} else {
|
||||
/* Legacy hints */
|
||||
if (client->name == NULL || strcasestr(client->name, to_title) == NULL)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enters fullscreen mode for the given client. This is called by toggle_fullscreen
|
||||
* and when moving a fullscreen client to another screen.
|
||||
*
|
||||
*/
|
||||
void client_enter_fullscreen(xcb_connection_t *conn, Client *client) {
|
||||
Workspace *workspace = client->workspace;
|
||||
|
||||
if (workspace->fullscreen_client != NULL) {
|
||||
LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
client->fullscreen = true;
|
||||
workspace->fullscreen_client = client;
|
||||
LOG("Entering fullscreen mode...\n");
|
||||
/* We just entered fullscreen mode, let’s configure the window */
|
||||
uint32_t mask = XCB_CONFIG_WINDOW_X |
|
||||
XCB_CONFIG_WINDOW_Y |
|
||||
XCB_CONFIG_WINDOW_WIDTH |
|
||||
XCB_CONFIG_WINDOW_HEIGHT;
|
||||
uint32_t values[4] = {workspace->rect.x,
|
||||
workspace->rect.y,
|
||||
workspace->rect.width,
|
||||
workspace->rect.height};
|
||||
|
||||
LOG("child itself will be at %dx%d with size %dx%d\n",
|
||||
values[0], values[1], values[2], values[3]);
|
||||
|
||||
xcb_configure_window(conn, client->frame, mask, values);
|
||||
|
||||
/* Child’s coordinates are relative to the parent (=frame) */
|
||||
values[0] = 0;
|
||||
values[1] = 0;
|
||||
xcb_configure_window(conn, client->child, mask, values);
|
||||
|
||||
/* Raise the window */
|
||||
values[0] = XCB_STACK_MODE_ABOVE;
|
||||
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
|
||||
Rect child_rect = workspace->rect;
|
||||
child_rect.x = child_rect.y = 0;
|
||||
fake_configure_notify(conn, child_rect, client->child);
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Toggles fullscreen mode for the given client. It updates the data structures and
|
||||
* reconfigures (= resizes/moves) the client and its frame to the full size of the
|
||||
* screen. When leaving fullscreen, re-rendering the layout is forced.
|
||||
*
|
||||
*/
|
||||
void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) {
|
||||
/* dock clients cannot enter fullscreen mode */
|
||||
assert(!client->dock);
|
||||
|
||||
Workspace *workspace = client->workspace;
|
||||
|
||||
if (!client->fullscreen) {
|
||||
client_enter_fullscreen(conn, client);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("leaving fullscreen mode\n");
|
||||
client->fullscreen = false;
|
||||
workspace->fullscreen_client = NULL;
|
||||
if (client_is_floating(client)) {
|
||||
/* For floating clients it’s enough if we just reconfigure that window (in fact,
|
||||
* re-rendering the layout will not update the client.) */
|
||||
reposition_client(conn, client);
|
||||
resize_client(conn, client);
|
||||
/* redecorate_window flushes */
|
||||
redecorate_window(conn, client);
|
||||
} else {
|
||||
client_set_below_floating(conn, client);
|
||||
|
||||
/* Because the coordinates of the window haven’t changed, it would not be
|
||||
re-configured if we don’t set the following flag */
|
||||
client->force_reconfigure = true;
|
||||
/* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */
|
||||
render_layout(conn);
|
||||
}
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the position of the given client in the X stack to the highest (tiling layer is always
|
||||
* on the same position, so this doesn’t matter) below the first floating client, so that
|
||||
* floating windows are always on top.
|
||||
*
|
||||
*/
|
||||
void client_set_below_floating(xcb_connection_t *conn, Client *client) {
|
||||
/* Ensure that it is below all floating clients */
|
||||
Client *first_floating = TAILQ_FIRST(&(client->workspace->floating_clients));
|
||||
if (first_floating != TAILQ_END(&(client->workspace->floating_clients))) {
|
||||
LOG("Setting below floating\n");
|
||||
uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW };
|
||||
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the client is floating. Makes the code more beatiful, as floating
|
||||
* is not simply a boolean, but also saves whether the user selected the current state
|
||||
* or whether it was automatically set.
|
||||
*
|
||||
*/
|
||||
bool client_is_floating(Client *client) {
|
||||
return (client->floating >= FLOATING_AUTO_ON);
|
||||
}
|
423
src/commands.c
423
src/commands.c
|
@ -23,6 +23,9 @@
|
|||
#include "layout.h"
|
||||
#include "i3.h"
|
||||
#include "xinerama.h"
|
||||
#include "client.h"
|
||||
#include "floating.h"
|
||||
#include "xcb.h"
|
||||
|
||||
bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
|
||||
/* If this container is empty, we’re done */
|
||||
|
@ -57,10 +60,17 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
|
|||
|
||||
int new_row = current_row,
|
||||
new_col = current_col;
|
||||
|
||||
Container *container = CUR_CELL;
|
||||
Workspace *t_ws = c_ws;
|
||||
|
||||
/* Makes sure new_col and new_row are within bounds of the new workspace */
|
||||
void check_colrow_boundaries() {
|
||||
if (new_col >= t_ws->cols)
|
||||
new_col = (t_ws->cols - 1);
|
||||
if (new_row >= t_ws->rows)
|
||||
new_row = (t_ws->rows - 1);
|
||||
}
|
||||
|
||||
/* There always is a container. If not, current_col or current_row is wrong */
|
||||
assert(container != NULL);
|
||||
|
||||
|
@ -103,6 +113,21 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
|
|||
t_ws = &(workspaces[screen->current_workspace]);
|
||||
new_row = (direction == D_UP ? (t_ws->rows - 1) : 0);
|
||||
}
|
||||
|
||||
check_colrow_boundaries();
|
||||
|
||||
LOG("new_col = %d, new_row = %d\n", new_col, new_row);
|
||||
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
|
||||
LOG("Cell empty, checking for colspanned client above...\n");
|
||||
for (int cols = 0; cols < new_col; cols += t_ws->table[cols][new_row]->colspan) {
|
||||
if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1)))
|
||||
continue;
|
||||
|
||||
new_col = cols;
|
||||
break;
|
||||
}
|
||||
LOG("Fixed it to new col %d\n", new_col);
|
||||
}
|
||||
} else if (direction == D_LEFT || direction == D_RIGHT) {
|
||||
if (direction == D_RIGHT && cell_exists(current_col+1, current_row))
|
||||
new_col = current_col + t_ws->table[current_col][current_row]->colspan;
|
||||
|
@ -130,16 +155,27 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
|
|||
t_ws = &(workspaces[screen->current_workspace]);
|
||||
new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0);
|
||||
}
|
||||
|
||||
check_colrow_boundaries();
|
||||
|
||||
LOG("new_col = %d, new_row = %d\n", new_col, new_row);
|
||||
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
|
||||
LOG("Cell empty, checking for rowspanned client above...\n");
|
||||
for (int rows = 0; rows < new_row; rows += t_ws->table[new_col][rows]->rowspan) {
|
||||
if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1)))
|
||||
continue;
|
||||
|
||||
new_row = rows;
|
||||
break;
|
||||
}
|
||||
LOG("Fixed it to new row %d\n", new_row);
|
||||
}
|
||||
} else {
|
||||
LOG("direction unhandled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Bounds checking */
|
||||
if (new_col >= t_ws->cols)
|
||||
new_col = (t_ws->cols - 1);
|
||||
if (new_row >= t_ws->rows)
|
||||
new_row = (t_ws->rows - 1);
|
||||
check_colrow_boundaries();
|
||||
|
||||
if (t_ws->table[new_col][new_row]->currently_focused != NULL)
|
||||
set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true);
|
||||
|
@ -237,10 +273,13 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
|
|||
|
||||
new = CUR_TABLE[current_col][++current_row];
|
||||
break;
|
||||
/* To make static analyzers happy: */
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
/* Remove it from the old container and put it into the new one */
|
||||
remove_client_from_container(conn, current_client, container);
|
||||
client_remove_from_container(conn, current_client, container, true);
|
||||
|
||||
if (new->currently_focused != NULL)
|
||||
CIRCLEQ_INSERT_AFTER(&(new->clients), new->currently_focused, current_client, clients);
|
||||
|
@ -261,7 +300,8 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
|
|||
/* Fix colspan/rowspan if it’d overlap */
|
||||
fix_colrowspan(conn, workspace);
|
||||
|
||||
render_layout(conn);
|
||||
render_workspace(conn, workspace->screen, workspace);
|
||||
xcb_flush(conn);
|
||||
|
||||
set_focus(conn, current_client, true);
|
||||
}
|
||||
|
@ -309,6 +349,9 @@ static void move_current_container(xcb_connection_t *conn, direction_t direction
|
|||
|
||||
new = CUR_TABLE[current_col][++current_row];
|
||||
break;
|
||||
/* To make static analyzers happy: */
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row);
|
||||
|
@ -415,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 we’re switching to */
|
||||
Workspace *t_ws = &(workspaces[workspace-1]),
|
||||
*old_ws = client->workspace;
|
||||
|
||||
LOG("moving floating\n");
|
||||
|
||||
if (t_ws->screen == NULL) {
|
||||
LOG("initializing new workspace, setting num to %d\n", workspace-1);
|
||||
t_ws->screen = c_ws->screen;
|
||||
/* Copy the dimensions from the virtual screen */
|
||||
memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
|
||||
} else {
|
||||
/* Check if there is already a fullscreen client on the destination workspace and
|
||||
* stop moving if so. */
|
||||
if (client->fullscreen && (t_ws->fullscreen_client != NULL)) {
|
||||
LOG("Not moving: Fullscreen client already existing on destination workspace.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
floating_assign_to_workspace(client, t_ws);
|
||||
|
||||
bool target_invisible = t_ws->screen->current_workspace != t_ws->num;
|
||||
|
||||
/* If we’re moving it to an invisible screen, we need to unmap it */
|
||||
if (target_invisible) {
|
||||
LOG("This workspace is not visible, unmapping\n");
|
||||
xcb_unmap_window(conn, client->frame);
|
||||
} else {
|
||||
/* If this is not the case, we move the window to a workspace
|
||||
* which is on another screen, so we also need to adjust its
|
||||
* coordinates. */
|
||||
LOG("before x = %d, y = %d\n", client->rect.x, client->rect.y);
|
||||
uint32_t relative_x = client->rect.x - old_ws->rect.x,
|
||||
relative_y = client->rect.y - old_ws->rect.y;
|
||||
LOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y);
|
||||
client->rect.x = t_ws->rect.x + relative_x;
|
||||
client->rect.y = t_ws->rect.y + relative_y;
|
||||
LOG("after x = %d, y = %d\n", client->rect.x, client->rect.y);
|
||||
reposition_client(conn, client);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
LOG("done\n");
|
||||
|
||||
render_layout(conn);
|
||||
|
||||
if (!target_invisible)
|
||||
set_focus(conn, client, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 we’re moving it to an invisible screen, we need to unmap it */
|
||||
if (to_container->workspace->screen->current_workspace != to_container->workspace->num) {
|
||||
if (target_invisible) {
|
||||
LOG("This workspace is not visible, unmapping\n");
|
||||
xcb_unmap_window(conn, current_client->frame);
|
||||
} else {
|
||||
if (current_client->fullscreen) {
|
||||
LOG("Calling client_enter_fullscreen again\n");
|
||||
client_enter_fullscreen(conn, current_client);
|
||||
}
|
||||
}
|
||||
|
||||
/* delete all empty columns/rows */
|
||||
cleanup_table(conn, container->workspace);
|
||||
|
||||
render_layout(conn);
|
||||
|
||||
if (!target_invisible)
|
||||
set_focus(conn, current_client, true);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -539,22 +647,25 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
|
|||
|
||||
/* Check if we need to change something or if we’re already there */
|
||||
if (c_ws->screen->current_workspace == (workspace-1)) {
|
||||
if (CUR_CELL->currently_focused != NULL) {
|
||||
set_focus(conn, CUR_CELL->currently_focused, true);
|
||||
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
|
||||
if (last_focused != SLIST_END(&(c_ws->focus_stack))) {
|
||||
set_focus(conn, last_focused, true);
|
||||
if (need_warp) {
|
||||
warp_pointer_into(conn, CUR_CELL->currently_focused);
|
||||
client_warp_pointer_into(conn, last_focused);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
t_ws->screen->current_workspace = workspace-1;
|
||||
|
||||
/* 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 it’s one of the special values… */
|
||||
if (strcasecmp(arguments, "floating") == 0) {
|
||||
floating_criteria = true;
|
||||
} else if (strcasecmp(arguments, "tiling") == 0) {
|
||||
floating_criteria = false;
|
||||
} else if (strcasecmp(arguments, "ft") == 0) {
|
||||
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
|
||||
if (last_focused == SLIST_END(&(c_ws->focus_stack))) {
|
||||
LOG("Cannot select the next floating/tiling client because there is no client at all\n");
|
||||
return;
|
||||
}
|
||||
|
||||
floating_criteria = !client_is_floating(last_focused);
|
||||
} else {
|
||||
/* …or a number was specified */
|
||||
if (sscanf(arguments, "%u", ×) != 1) {
|
||||
LOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments);
|
||||
times = 1;
|
||||
}
|
||||
|
||||
SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) {
|
||||
if (++count < times) {
|
||||
LOG("Skipping\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG("Focussing\n");
|
||||
set_focus(conn, current, true);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Select the next client matching the criteria parsed above */
|
||||
SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients)
|
||||
if (client_is_floating(current) == floating_criteria) {
|
||||
set_focus(conn, current, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Goes through the list of arguments (for exec()) and checks if the given argument
|
||||
* is present. If not, it copies the arguments (because we cannot realloc it) and
|
||||
* appends the given argument.
|
||||
*
|
||||
*/
|
||||
static char **append_argument(char **original, char *argument) {
|
||||
int num_args;
|
||||
for (num_args = 0; original[num_args] != NULL; num_args++) {
|
||||
LOG("original argument: \"%s\"\n", original[num_args]);
|
||||
/* If the argument is already present we return the original pointer */
|
||||
if (strcmp(original[num_args], argument) == 0)
|
||||
return original;
|
||||
}
|
||||
/* Copy the original array */
|
||||
char **result = smalloc((num_args+2) * sizeof(char*));
|
||||
memcpy(result, original, num_args * sizeof(char*));
|
||||
result[num_args] = argument;
|
||||
result[num_args+1] = NULL;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parses a command, see file CMDMODE for more information
|
||||
*
|
||||
*/
|
||||
void parse_command(xcb_connection_t *conn, const char *command) {
|
||||
LOG("--- parsing command \"%s\" ---\n", command);
|
||||
/* Get the first client from focus stack because floating clients are not
|
||||
* in any container, therefore CUR_CELL is not appropriate. */
|
||||
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
|
||||
if (last_focused == SLIST_END(&(c_ws->focus_stack)))
|
||||
last_focused = NULL;
|
||||
|
||||
/* Hmm, just to be sure */
|
||||
if (command[0] == '\0')
|
||||
return;
|
||||
|
@ -612,37 +878,66 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
/* Is it <restart>? Then restart in place. */
|
||||
if (STARTS_WITH(command, "restart")) {
|
||||
LOG("restarting \"%s\"...\n", start_argv[0]);
|
||||
/* make sure -a is in the argument list or append it */
|
||||
start_argv = append_argument(start_argv, "-a");
|
||||
|
||||
execvp(start_argv[0], start_argv);
|
||||
/* not reached */
|
||||
}
|
||||
|
||||
if (STARTS_WITH(command, "kill")) {
|
||||
if (CUR_CELL->currently_focused == NULL) {
|
||||
if (last_focused == NULL) {
|
||||
LOG("There is no window to kill\n");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("Killing current window\n");
|
||||
kill_window(conn, CUR_CELL->currently_focused);
|
||||
client_kill(conn, last_focused);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Is it a jump to a specified workspace, row, col? */
|
||||
if (STARTS_WITH(command, "jump ")) {
|
||||
const char *arguments = command + strlen("jump ");
|
||||
if (arguments[0] == '"')
|
||||
jump_to_window(conn, arguments);
|
||||
else jump_to_container(conn, arguments);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Should we travel the focus stack? */
|
||||
if (STARTS_WITH(command, "focus")) {
|
||||
const char *arguments = command + strlen("focus ");
|
||||
travel_focus_stack(conn, arguments);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Is it 'f' for fullscreen? */
|
||||
if (command[0] == 'f') {
|
||||
if (CUR_CELL->currently_focused == NULL)
|
||||
if (last_focused == NULL)
|
||||
return;
|
||||
toggle_fullscreen(conn, CUR_CELL->currently_focused);
|
||||
client_toggle_fullscreen(conn, last_focused);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Is it just 's' for stacking or 'd' for default? */
|
||||
if ((command[0] == 's' || command[0] == 'd') && (command[1] == '\0')) {
|
||||
if (last_focused == NULL || client_is_floating(last_focused)) {
|
||||
LOG("not switching, this is a floating client\n");
|
||||
return;
|
||||
}
|
||||
LOG("Switching mode for current container\n");
|
||||
switch_layout_mode(conn, CUR_CELL, (command[0] == 's' ? MODE_STACK : MODE_DEFAULT));
|
||||
return;
|
||||
}
|
||||
|
||||
enum { WITH_WINDOW, WITH_CONTAINER } with = WITH_WINDOW;
|
||||
if (command[0] == 'H') {
|
||||
LOG("Hiding all floating windows\n");
|
||||
floating_toggle_hide(conn, c_ws);
|
||||
return;
|
||||
}
|
||||
|
||||
enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE } with = WITH_WINDOW;
|
||||
|
||||
/* Is it a <with>? */
|
||||
if (command[0] == 'w') {
|
||||
|
@ -651,13 +946,44 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
if (command[0] == 'c') {
|
||||
with = WITH_CONTAINER;
|
||||
command++;
|
||||
} else if (command[0] == 'w') {
|
||||
with = WITH_WORKSPACE;
|
||||
command++;
|
||||
} else {
|
||||
LOG("not yet implemented.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* It's a normal <cmd> */
|
||||
/* Is it 't' for toggle tiling/floating? */
|
||||
if (command[0] == 't') {
|
||||
if (with == WITH_WORKSPACE) {
|
||||
c_ws->auto_float = !c_ws->auto_float;
|
||||
LOG("autofloat is now %d\n", c_ws->auto_float);
|
||||
return;
|
||||
}
|
||||
if (last_focused == NULL) {
|
||||
LOG("Cannot toggle tiling/floating: workspace empty\n");
|
||||
return;
|
||||
}
|
||||
|
||||
toggle_floating_mode(conn, last_focused, false);
|
||||
/* delete all empty columns/rows */
|
||||
cleanup_table(conn, last_focused->workspace);
|
||||
|
||||
/* Fix colspan/rowspan if it’d overlap */
|
||||
fix_colrowspan(conn, last_focused->workspace);
|
||||
|
||||
render_workspace(conn, last_focused->workspace->screen, last_focused->workspace);
|
||||
|
||||
/* Re-focus the client because cleanup_table sets the focus to the last
|
||||
* focused client inside a container only. */
|
||||
set_focus(conn, last_focused, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* It’s a normal <cmd> */
|
||||
char *rest = NULL;
|
||||
enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS;
|
||||
direction_t direction;
|
||||
|
@ -668,7 +994,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
}
|
||||
|
||||
if (*rest == '\0') {
|
||||
/* No rest? This was a tag number, not a times specification */
|
||||
/* No rest? This was a workspace number, not a times specification */
|
||||
show_workspace(conn, times);
|
||||
return;
|
||||
}
|
||||
|
@ -686,7 +1012,20 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
}
|
||||
|
||||
if (*rest == '\0') {
|
||||
move_current_window_to_workspace(conn, workspace);
|
||||
if (last_focused != NULL && client_is_floating(last_focused))
|
||||
move_floating_window_to_workspace(conn, last_focused, workspace);
|
||||
else move_current_window_to_workspace(conn, workspace);
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_focused == NULL) {
|
||||
LOG("Not performing (no window found)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (client_is_floating(last_focused) &&
|
||||
(action != ACTION_FOCUS && action != ACTION_MOVE)) {
|
||||
LOG("Not performing (floating)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -704,18 +1043,32 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
LOG("unknown direction: %c\n", *rest);
|
||||
return;
|
||||
}
|
||||
rest++;
|
||||
|
||||
if (action == ACTION_FOCUS)
|
||||
if (action == ACTION_FOCUS) {
|
||||
if (client_is_floating(last_focused)) {
|
||||
floating_focus_direction(conn, last_focused, direction);
|
||||
continue;
|
||||
}
|
||||
focus_thing(conn, direction, (with == WITH_WINDOW ? THING_WINDOW : THING_CONTAINER));
|
||||
else if (action == ACTION_MOVE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (action == ACTION_MOVE) {
|
||||
if (client_is_floating(last_focused)) {
|
||||
floating_move(conn, last_focused, direction);
|
||||
continue;
|
||||
}
|
||||
if (with == WITH_WINDOW)
|
||||
move_current_window(conn, direction);
|
||||
else move_current_container(conn, direction);
|
||||
continue;
|
||||
}
|
||||
else if (action == ACTION_SNAP)
|
||||
snap_current_container(conn, direction);
|
||||
|
||||
rest++;
|
||||
if (action == ACTION_SNAP) {
|
||||
snap_current_container(conn, direction);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
LOG("--- done ---\n");
|
||||
|
|
189
src/config.c
189
src/config.c
|
@ -17,6 +17,7 @@
|
|||
#include "i3.h"
|
||||
#include "util.h"
|
||||
#include "config.h"
|
||||
#include "xcb.h"
|
||||
|
||||
Config config;
|
||||
|
||||
|
@ -33,6 +34,28 @@ static char *glob_path(const char *path) {
|
|||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function does a very simple replacement of each instance of key with value.
|
||||
*
|
||||
*/
|
||||
static void replace_variable(char *buffer, const char *key, const char *value) {
|
||||
char *pos;
|
||||
/* To prevent endless recursions when the user makes an error configuring,
|
||||
* we stop after 100 replacements. That should be vastly more than enough. */
|
||||
int c = 0;
|
||||
LOG("Replacing %s with %s\n", key, value);
|
||||
while ((pos = strcasestr(buffer, key)) != NULL && c++ < 100) {
|
||||
LOG("replacing variable %s in \"%s\" with \"%s\"\n", key, buffer, value);
|
||||
char *rest = pos + strlen(key);
|
||||
*pos = '\0';
|
||||
char *replaced;
|
||||
asprintf(&replaced, "%s%s%s", buffer, value, rest);
|
||||
/* Hm, this is a bit ugly, but sizeof(buffer) = 4, as it’s just a pointer.
|
||||
* So we need to hard-code the dimensions here. */
|
||||
strncpy(buffer, replaced, 1026);
|
||||
free(replaced);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
|
||||
|
@ -41,7 +64,9 @@ static char *glob_path(const char *path) {
|
|||
* configuration file.
|
||||
*
|
||||
*/
|
||||
void load_configuration(const char *override_configpath) {
|
||||
void load_configuration(xcb_connection_t *conn, const char *override_configpath) {
|
||||
SLIST_HEAD(variables_head, Variable) variables;
|
||||
|
||||
#define OPTION_STRING(name) \
|
||||
if (strcasecmp(key, #name) == 0) { \
|
||||
config.name = sstrdup(value); \
|
||||
|
@ -52,9 +77,51 @@ void load_configuration(const char *override_configpath) {
|
|||
if (config.name == NULL) \
|
||||
die("You did not specify required configuration option " #name "\n");
|
||||
|
||||
#define OPTION_COLORTRIPLE(opt, name) \
|
||||
if (strcasecmp(key, opt) == 0) { \
|
||||
char border[8], background[8], text[8]; \
|
||||
memset(border, 0, sizeof(border)); \
|
||||
memset(background, 0, sizeof(background)); \
|
||||
memset(text, 0, sizeof(text)); \
|
||||
border[0] = background[0] = text[0] = '#'; \
|
||||
if (sscanf(value, "#%06[0-9a-fA-F] #%06[0-9a-fA-F] #%06[0-9a-fA-F]", \
|
||||
border + 1, background + 1, text + 1) != 3 || \
|
||||
strlen(border) != 7 || \
|
||||
strlen(background) != 7 || \
|
||||
strlen(text) != 7) \
|
||||
die("invalid color code line: %s\n", value); \
|
||||
config.name.border = get_colorpixel(conn, border); \
|
||||
config.name.background = get_colorpixel(conn, background); \
|
||||
config.name.text = get_colorpixel(conn, text); \
|
||||
continue; \
|
||||
}
|
||||
|
||||
/* Clear the old config or initialize the data structure */
|
||||
memset(&config, 0, sizeof(config));
|
||||
|
||||
SLIST_INIT(&variables);
|
||||
|
||||
/* Initialize default colors */
|
||||
config.client.focused.border = get_colorpixel(conn, "#4c7899");
|
||||
config.client.focused.background = get_colorpixel(conn, "#285577");
|
||||
config.client.focused.text = get_colorpixel(conn, "#ffffff");
|
||||
|
||||
config.client.focused_inactive.border = get_colorpixel(conn, "#4c7899");
|
||||
config.client.focused_inactive.background = get_colorpixel(conn, "#555555");
|
||||
config.client.focused_inactive.text = get_colorpixel(conn, "#ffffff");
|
||||
|
||||
config.client.unfocused.border = get_colorpixel(conn, "#333333");
|
||||
config.client.unfocused.background = get_colorpixel(conn, "#222222");
|
||||
config.client.unfocused.text = get_colorpixel(conn, "#888888");
|
||||
|
||||
config.bar.focused.border = get_colorpixel(conn, "#4c7899");
|
||||
config.bar.focused.background = get_colorpixel(conn, "#285577");
|
||||
config.bar.focused.text = get_colorpixel(conn, "#ffffff");
|
||||
|
||||
config.bar.unfocused.border = get_colorpixel(conn, "#333333");
|
||||
config.bar.unfocused.background = get_colorpixel(conn, "#222222");
|
||||
config.bar.unfocused.text = get_colorpixel(conn, "#888888");
|
||||
|
||||
FILE *handle;
|
||||
if (override_configpath != NULL) {
|
||||
if ((handle = fopen(override_configpath, "r")) == NULL)
|
||||
|
@ -77,6 +144,14 @@ void load_configuration(const char *override_configpath) {
|
|||
die("Could not read configuration file\n");
|
||||
}
|
||||
|
||||
if (config.terminal != NULL)
|
||||
replace_variable(buffer, "$terminal", config.terminal);
|
||||
|
||||
/* Replace all custom variables */
|
||||
struct Variable *current;
|
||||
SLIST_FOREACH(current, &variables, variables)
|
||||
replace_variable(buffer, current->key, current->value);
|
||||
|
||||
/* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
|
||||
if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
|
||||
key[0] == '#' || strlen(key) < 3)
|
||||
|
@ -85,6 +160,22 @@ void load_configuration(const char *override_configpath) {
|
|||
OPTION_STRING(terminal);
|
||||
OPTION_STRING(font);
|
||||
|
||||
/* Colors */
|
||||
OPTION_COLORTRIPLE("client.focused", client.focused);
|
||||
OPTION_COLORTRIPLE("client.focused_inactive", client.focused_inactive);
|
||||
OPTION_COLORTRIPLE("client.unfocused", client.unfocused);
|
||||
OPTION_COLORTRIPLE("bar.focused", bar.focused);
|
||||
OPTION_COLORTRIPLE("bar.unfocused", bar.unfocused);
|
||||
|
||||
/* exec-lines (autostart) */
|
||||
if (strcasecmp(key, "exec") == 0) {
|
||||
struct Autostart *new = smalloc(sizeof(struct Autostart));
|
||||
new->command = sstrdup(value);
|
||||
TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* key bindings */
|
||||
if (strcasecmp(key, "bind") == 0) {
|
||||
#define CHECK_MODIFIER(name) \
|
||||
if (strncasecmp(walk, #name, strlen(#name)) == 0) { \
|
||||
|
@ -124,13 +215,105 @@ void load_configuration(const char *override_configpath) {
|
|||
continue;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Unknown configfile option: %s\n", key);
|
||||
exit(1);
|
||||
if (strcasecmp(key, "floating_modifier") == 0) {
|
||||
char *walk = value;
|
||||
uint32_t modifiers = 0;
|
||||
|
||||
while (*walk != '\0') {
|
||||
/* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */
|
||||
CHECK_MODIFIER(SHIFT);
|
||||
CHECK_MODIFIER(CONTROL);
|
||||
CHECK_MODIFIER(MODE_SWITCH);
|
||||
CHECK_MODIFIER(MOD1);
|
||||
CHECK_MODIFIER(MOD2);
|
||||
CHECK_MODIFIER(MOD3);
|
||||
CHECK_MODIFIER(MOD4);
|
||||
CHECK_MODIFIER(MOD5);
|
||||
|
||||
/* No modifier found? Then we’re done with this step */
|
||||
break;
|
||||
}
|
||||
|
||||
LOG("Floating modifiers = %d\n", modifiers);
|
||||
config.floating_modifier = modifiers;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* assign window class[/window title] → workspace */
|
||||
if (strcasecmp(key, "assign") == 0) {
|
||||
LOG("assign: \"%s\"\n", value);
|
||||
char *class_title = sstrdup(value);
|
||||
char *target;
|
||||
|
||||
/* If the window class/title is quoted we skip quotes */
|
||||
if (class_title[0] == '"') {
|
||||
class_title++;
|
||||
char *end = strchr(class_title, '"');
|
||||
if (end == NULL)
|
||||
die("Malformed assignment, couldn't find terminating quote\n");
|
||||
*end = '\0';
|
||||
} else {
|
||||
/* If it is not quoted, we terminate it at the first space */
|
||||
char *end = strchr(class_title, ' ');
|
||||
if (end == NULL)
|
||||
die("Malformed assignment, couldn't find terminating space\n");
|
||||
*end = '\0';
|
||||
}
|
||||
|
||||
/* The target is the last argument separated by a space */
|
||||
if ((target = strrchr(value, ' ')) == NULL)
|
||||
die("Malformed assignment, couldn't find target\n");
|
||||
target++;
|
||||
|
||||
if (*target != '~' && (atoi(target) < 1 || atoi(target) > 10))
|
||||
die("Malformed assignment, invalid workspace number\n");
|
||||
|
||||
LOG("assignment parsed: \"%s\" to \"%s\"\n", class_title, target);
|
||||
|
||||
struct Assignment *new = scalloc(sizeof(struct Assignment));
|
||||
new->windowclass_title = class_title;
|
||||
if (*target == '~')
|
||||
new->floating = true;
|
||||
else new->workspace = atoi(target);
|
||||
TAILQ_INSERT_TAIL(&assignments, new, assignments);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* set a custom variable */
|
||||
if (strcasecmp(key, "set") == 0) {
|
||||
if (value[0] != '$')
|
||||
die("Malformed variable assignment, name has to start with $\n");
|
||||
|
||||
/* get key/value for this variable */
|
||||
char *v_key = value, *v_value;
|
||||
if ((v_value = strstr(value, " ")) == NULL)
|
||||
die("Malformed variable assignment, need a value\n");
|
||||
|
||||
*(v_value++) = '\0';
|
||||
|
||||
struct Variable *new = scalloc(sizeof(struct Variable));
|
||||
new->key = sstrdup(v_key);
|
||||
new->value = sstrdup(v_value);
|
||||
SLIST_INSERT_HEAD(&variables, new, variables);
|
||||
LOG("Got new variable %s = %s\n", v_key, v_value);
|
||||
continue;
|
||||
}
|
||||
|
||||
die("Unknown configfile option: %s\n", key);
|
||||
}
|
||||
fclose(handle);
|
||||
|
||||
REQUIRED_OPTION(terminal);
|
||||
REQUIRED_OPTION(font);
|
||||
|
||||
|
||||
while (!SLIST_EMPTY(&variables)) {
|
||||
struct Variable *v = SLIST_FIRST(&variables);
|
||||
SLIST_REMOVE_HEAD(&variables, variables);
|
||||
free(v->key);
|
||||
free(v->value);
|
||||
free(v);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,433 @@
|
|||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
* src/floating.c: contains all functions for handling floating clients
|
||||
*
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_event.h>
|
||||
|
||||
#include "i3.h"
|
||||
#include "config.h"
|
||||
#include "data.h"
|
||||
#include "util.h"
|
||||
#include "xcb.h"
|
||||
#include "debug.h"
|
||||
#include "layout.h"
|
||||
#include "client.h"
|
||||
#include "floating.h"
|
||||
|
||||
/*
|
||||
* Toggles floating mode for the given client.
|
||||
* Correctly takes care of the position/size (separately stored for tiling/floating mode)
|
||||
* and repositions/resizes/redecorates the client.
|
||||
*
|
||||
* If the automatic flag is set to true, this was an automatic update by a change of the
|
||||
* window class from the application which can be overwritten by the user.
|
||||
*
|
||||
*/
|
||||
void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic) {
|
||||
Container *con = client->container;
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
|
||||
if (con == NULL) {
|
||||
LOG("This client is already in floating (container == NULL), re-inserting\n");
|
||||
Client *next_tiling;
|
||||
SLIST_FOREACH(next_tiling, &(client->workspace->focus_stack), focus_clients)
|
||||
if (!client_is_floating(next_tiling))
|
||||
break;
|
||||
/* If there are no tiling clients on this workspace, there can only be one
|
||||
* container: the first one */
|
||||
if (next_tiling == TAILQ_END(&(client->workspace->focus_stack)))
|
||||
con = client->workspace->table[0][0];
|
||||
else con = next_tiling->container;
|
||||
|
||||
/* Remove the client from the list of floating clients */
|
||||
TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
|
||||
|
||||
LOG("destination container = %p\n", con);
|
||||
Client *old_focused = con->currently_focused;
|
||||
/* Preserve position/size */
|
||||
memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect));
|
||||
|
||||
client->floating = FLOATING_USER_OFF;
|
||||
client->container = con;
|
||||
|
||||
if (old_focused != NULL && !old_focused->dock)
|
||||
CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients);
|
||||
else CIRCLEQ_INSERT_TAIL(&(con->clients), client, clients);
|
||||
|
||||
LOG("Re-inserted the client into the matrix.\n");
|
||||
con->currently_focused = client;
|
||||
|
||||
client_set_below_floating(conn, client);
|
||||
|
||||
render_container(conn, con);
|
||||
xcb_flush(conn);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("Entering floating for client %08x\n", client->child);
|
||||
|
||||
/* Remove the client of its container */
|
||||
client_remove_from_container(conn, client, con, false);
|
||||
client->container = NULL;
|
||||
|
||||
/* Add the client to the list of floating clients for its workspace */
|
||||
TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
|
||||
|
||||
if (con->currently_focused == client) {
|
||||
LOG("Need to re-adjust currently_focused\n");
|
||||
/* Get the next client in the focus stack for this particular container */
|
||||
con->currently_focused = get_last_focused_client(conn, con, NULL);
|
||||
}
|
||||
|
||||
if (automatic)
|
||||
client->floating = FLOATING_AUTO_ON;
|
||||
else client->floating = FLOATING_USER_ON;
|
||||
|
||||
/* Initialize the floating position from the position in tiling mode, if this
|
||||
* client never was floating (x == -1) */
|
||||
if (client->floating_rect.x == -1) {
|
||||
/* Copy over the position */
|
||||
client->floating_rect.x = client->rect.x;
|
||||
client->floating_rect.y = client->rect.y;
|
||||
|
||||
/* Copy size the other direction */
|
||||
client->child_rect.width = client->floating_rect.width;
|
||||
client->child_rect.height = client->floating_rect.height;
|
||||
|
||||
client->rect.width = client->child_rect.width + 2 + 2;
|
||||
client->rect.height = client->child_rect.height + (font->height + 2 + 2) + 2;
|
||||
|
||||
LOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
|
||||
client->floating_rect.width, client->floating_rect.height);
|
||||
} else {
|
||||
/* If the client was already in floating before we restore the old position / size */
|
||||
LOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
|
||||
client->floating_rect.width, client->floating_rect.height);
|
||||
memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect));
|
||||
}
|
||||
|
||||
/* Raise the client */
|
||||
xcb_raise_window(conn, client->frame);
|
||||
reposition_client(conn, client);
|
||||
resize_client(conn, client);
|
||||
/* redecorate_window flushes */
|
||||
redecorate_window(conn, client);
|
||||
|
||||
/* Re-render the tiling layout of this container */
|
||||
render_container(conn, con);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes the floating client from its workspace and attaches it to the new workspace.
|
||||
* This is centralized here because it may happen if you move it via keyboard and
|
||||
* if you move it using your mouse.
|
||||
*
|
||||
*/
|
||||
void floating_assign_to_workspace(Client *client, Workspace *new_workspace) {
|
||||
/* Remove from focus stack and list of floating clients */
|
||||
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
|
||||
TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
|
||||
|
||||
if (client->workspace->fullscreen_client == client)
|
||||
client->workspace->fullscreen_client = NULL;
|
||||
|
||||
/* Insert into destination focus stack and list of floating clients */
|
||||
client->workspace = new_workspace;
|
||||
SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
|
||||
TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
|
||||
if (client->fullscreen)
|
||||
client->workspace->fullscreen_client = client;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Called whenever the user clicks on a border (not the titlebar!) of a floating window.
|
||||
* Determines on which border the user clicked and launches the drag_pointer function
|
||||
* with the resize_callback.
|
||||
*
|
||||
*/
|
||||
int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
|
||||
|
||||
LOG("floating border click\n");
|
||||
|
||||
border_t border;
|
||||
|
||||
void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
|
||||
switch (border) {
|
||||
case BORDER_RIGHT: {
|
||||
int new_width = old_rect->width + (new_x - event->root_x);
|
||||
if ((new_width < 0) ||
|
||||
(new_width < 50 && client->rect.width >= new_width))
|
||||
return;
|
||||
client->rect.width = new_width;
|
||||
break;
|
||||
}
|
||||
|
||||
case BORDER_BOTTOM: {
|
||||
int new_height = old_rect->height + (new_y - event->root_y);
|
||||
if ((new_height < 0) ||
|
||||
(new_height < 20 && client->rect.height >= new_height))
|
||||
return;
|
||||
client->rect.height = old_rect->height + (new_y - event->root_y);
|
||||
break;
|
||||
}
|
||||
|
||||
case BORDER_TOP: {
|
||||
int new_height = old_rect->height + (event->root_y - new_y);
|
||||
if ((new_height < 0) ||
|
||||
(new_height < 20 && client->rect.height >= new_height))
|
||||
return;
|
||||
|
||||
client->rect.y = old_rect->y + (new_y - event->root_y);
|
||||
client->rect.height = new_height;
|
||||
break;
|
||||
}
|
||||
|
||||
case BORDER_LEFT: {
|
||||
int new_width = old_rect->width + (event->root_x - new_x);
|
||||
if ((new_width < 0) ||
|
||||
(new_width < 50 && client->rect.width >= new_width))
|
||||
return;
|
||||
client->rect.x = old_rect->x + (new_x - event->root_x);
|
||||
client->rect.width = new_width;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Push the new position/size to X11 */
|
||||
reposition_client(conn, client);
|
||||
resize_client(conn, client);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
if (event->event_y < 2)
|
||||
border = BORDER_TOP;
|
||||
else if (event->event_y >= (client->rect.height - 2))
|
||||
border = BORDER_BOTTOM;
|
||||
else if (event->event_x <= 2)
|
||||
border = BORDER_LEFT;
|
||||
else if (event->event_x >= (client->rect.width - 2))
|
||||
border = BORDER_RIGHT;
|
||||
else {
|
||||
LOG("Not on any border, not doing anything.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
LOG("border = %d\n", border);
|
||||
|
||||
drag_pointer(conn, client, event, XCB_NONE, border, resize_callback);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Called when the user clicked on the titlebar of a floating window.
|
||||
* Calls the drag_pointer function with the drag_window callback
|
||||
*
|
||||
*/
|
||||
void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
|
||||
LOG("floating_drag_window\n");
|
||||
|
||||
void drag_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
|
||||
/* Reposition the client correctly while moving */
|
||||
client->rect.x = old_rect->x + (new_x - event->root_x);
|
||||
client->rect.y = old_rect->y + (new_y - event->root_y);
|
||||
reposition_client(conn, client);
|
||||
/* Because reposition_client does not send a faked configure event (only resize does),
|
||||
* we need to initiate that on our own */
|
||||
fake_absolute_configure_notify(conn, client);
|
||||
/* fake_absolute_configure_notify flushes */
|
||||
}
|
||||
|
||||
|
||||
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function grabs your pointer and lets you drag stuff around (borders).
|
||||
* Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
|
||||
* and the given callback will be called with the parameters specified (client,
|
||||
* border on which the click originally was), the original rect of the client,
|
||||
* the event and the new coordinates (x, y).
|
||||
*
|
||||
*/
|
||||
void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
|
||||
xcb_window_t confine_to, border_t border, callback_t callback) {
|
||||
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
|
||||
uint32_t new_x, new_y;
|
||||
Rect old_rect;
|
||||
if (client != NULL)
|
||||
memcpy(&old_rect, &(client->rect), sizeof(Rect));
|
||||
|
||||
/* Grab the pointer */
|
||||
/* TODO: returncode */
|
||||
xcb_grab_pointer(conn,
|
||||
false, /* get all pointer events specified by the following mask */
|
||||
root, /* grab the root window */
|
||||
XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */
|
||||
XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
|
||||
XCB_GRAB_MODE_ASYNC, /* keyboard mode */
|
||||
confine_to, /* confine_to = in which window should the cursor stay */
|
||||
XCB_NONE, /* don’t display a special cursor */
|
||||
XCB_CURRENT_TIME);
|
||||
|
||||
/* Go into our own event loop */
|
||||
xcb_flush(conn);
|
||||
|
||||
xcb_generic_event_t *inside_event, *last_motion_notify = NULL;
|
||||
/* I’ve always wanted to have my own eventhandler… */
|
||||
while ((inside_event = xcb_wait_for_event(conn))) {
|
||||
/* We now handle all events we can get using xcb_poll_for_event */
|
||||
do {
|
||||
/* Same as get_event_handler in xcb */
|
||||
int nr = inside_event->response_type;
|
||||
if (nr == 0) {
|
||||
/* An error occured */
|
||||
handle_event(NULL, conn, inside_event);
|
||||
free(inside_event);
|
||||
continue;
|
||||
}
|
||||
assert(nr < 256);
|
||||
nr &= XCB_EVENT_RESPONSE_TYPE_MASK;
|
||||
assert(nr >= 2);
|
||||
|
||||
switch (nr) {
|
||||
case XCB_BUTTON_RELEASE:
|
||||
goto done;
|
||||
|
||||
case XCB_MOTION_NOTIFY:
|
||||
/* motion_notify events are saved for later */
|
||||
FREE(last_motion_notify);
|
||||
last_motion_notify = inside_event;
|
||||
|
||||
break;
|
||||
default:
|
||||
LOG("Passing to original handler\n");
|
||||
/* Use original handler */
|
||||
xcb_event_handle(&evenths, inside_event);
|
||||
break;
|
||||
}
|
||||
if (last_motion_notify != inside_event)
|
||||
free(inside_event);
|
||||
} while ((inside_event = xcb_poll_for_event(conn)) != NULL);
|
||||
|
||||
if (last_motion_notify == NULL)
|
||||
continue;
|
||||
|
||||
new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x;
|
||||
new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y;
|
||||
|
||||
callback(&old_rect, new_x, new_y);
|
||||
FREE(last_motion_notify);
|
||||
}
|
||||
done:
|
||||
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Changes focus in the given direction for floating clients.
|
||||
*
|
||||
* Changing to the left/right means going to the previous/next floating client,
|
||||
* changing to top/bottom means cycling through the Z-index.
|
||||
*
|
||||
*/
|
||||
void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
|
||||
LOG("floating focus\n");
|
||||
|
||||
if (direction == D_LEFT || direction == D_RIGHT) {
|
||||
/* Go to the next/previous floating client */
|
||||
Client *client;
|
||||
|
||||
while ((client = (direction == D_LEFT ? TAILQ_PREV(currently_focused, floating_clients_head, floating_clients) :
|
||||
TAILQ_NEXT(currently_focused, floating_clients))) !=
|
||||
TAILQ_END(&(currently_focused->workspace->floating_clients))) {
|
||||
if (!client->floating)
|
||||
continue;
|
||||
set_focus(conn, client, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Moves the client 10px to the specified direction.
|
||||
*
|
||||
*/
|
||||
void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
|
||||
LOG("floating move\n");
|
||||
|
||||
switch (direction) {
|
||||
case D_LEFT:
|
||||
if (currently_focused->rect.x < 10)
|
||||
return;
|
||||
currently_focused->rect.x -= 10;
|
||||
break;
|
||||
case D_RIGHT:
|
||||
currently_focused->rect.x += 10;
|
||||
break;
|
||||
case D_UP:
|
||||
if (currently_focused->rect.y < 10)
|
||||
return;
|
||||
currently_focused->rect.y -= 10;
|
||||
break;
|
||||
case D_DOWN:
|
||||
currently_focused->rect.y += 10;
|
||||
break;
|
||||
/* to make static analyzers happy */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
reposition_client(conn, currently_focused);
|
||||
|
||||
/* Because reposition_client does not send a faked configure event (only resize does),
|
||||
* we need to initiate that on our own */
|
||||
fake_absolute_configure_notify(conn, currently_focused);
|
||||
/* fake_absolute_configure_notify flushes */
|
||||
}
|
||||
|
||||
/*
|
||||
* Hides all floating clients (or show them if they are currently hidden) on
|
||||
* the specified workspace.
|
||||
*
|
||||
*/
|
||||
void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) {
|
||||
Client *client;
|
||||
|
||||
workspace->floating_hidden = !workspace->floating_hidden;
|
||||
LOG("floating_hidden is now: %d\n", workspace->floating_hidden);
|
||||
TAILQ_FOREACH(client, &(workspace->floating_clients), floating_clients) {
|
||||
if (workspace->floating_hidden)
|
||||
xcb_unmap_window(conn, client->frame);
|
||||
else xcb_map_window(conn, client->frame);
|
||||
}
|
||||
|
||||
/* If we just unmapped all floating windows we should ensure that the focus
|
||||
* is set correctly, that ist, to the first non-floating client in stack */
|
||||
if (workspace->floating_hidden)
|
||||
SLIST_FOREACH(client, &(workspace->focus_stack), focus_clients) {
|
||||
if (client_is_floating(client))
|
||||
continue;
|
||||
set_focus(conn, client, true);
|
||||
return;
|
||||
}
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
257
src/handlers.c
257
src/handlers.c
|
@ -32,6 +32,9 @@
|
|||
#include "config.h"
|
||||
#include "queue.h"
|
||||
#include "resize.h"
|
||||
#include "client.h"
|
||||
#include "manage.h"
|
||||
#include "floating.h"
|
||||
|
||||
/* After mapping/unmapping windows, a notify event is generated. However, we don’t want it,
|
||||
since it’d trigger an infinite loop of switching between the different windows when
|
||||
|
@ -149,7 +152,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
|
|||
return 1;
|
||||
}
|
||||
/* Some events are not interesting, because they were not generated actively by the
|
||||
user, but be reconfiguration of windows */
|
||||
user, but by reconfiguration of windows */
|
||||
if (event_is_ignored(event->sequence))
|
||||
return 1;
|
||||
|
||||
|
@ -196,6 +199,14 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (client->workspace != c_ws && client->workspace->screen == c_ws->screen) {
|
||||
/* This can happen when a client gets assigned to a different workspace than
|
||||
* the current one (see src/mainx.c:reparent_window). Shortly after it was created,
|
||||
* an enter_notify will follow. */
|
||||
LOG("enter_notify for a client on a different workspace but the same screen, ignoring\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
set_focus(conn, client, false);
|
||||
|
||||
return 1;
|
||||
|
@ -283,6 +294,7 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e
|
|||
|
||||
int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) {
|
||||
LOG("button press!\n");
|
||||
LOG("state = %d\n", event->state);
|
||||
/* This was either a focus for a client’s parent (= titlebar)… */
|
||||
Client *client = table_get(&by_child, event->event);
|
||||
bool border_click = false;
|
||||
|
@ -290,6 +302,21 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
|
|||
client = table_get(&by_parent, event->event);
|
||||
border_click = true;
|
||||
}
|
||||
/* See if this was a click with the configured modifier. If so, we need
|
||||
* to move around the client if it was floating. if not, we just process
|
||||
* as usual. */
|
||||
if (config.floating_modifier != 0 &&
|
||||
(event->state & config.floating_modifier) != 0) {
|
||||
if (client == NULL) {
|
||||
LOG("Not handling, Mod1 was pressed and no client found\n");
|
||||
return 1;
|
||||
}
|
||||
if (client_is_floating(client)) {
|
||||
floating_drag_window(conn, client, event);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (client == NULL) {
|
||||
/* The client was neither on a client’s titlebar nor on a client itself, maybe on a stack_window? */
|
||||
if (button_press_stackwin(conn, event))
|
||||
|
@ -312,7 +339,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
|
|||
Container *con = client->container;
|
||||
int first, second;
|
||||
|
||||
if (con == NULL) {
|
||||
if (client->dock) {
|
||||
LOG("dock. done.\n");
|
||||
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
|
||||
xcb_flush(conn);
|
||||
|
@ -324,6 +351,9 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
|
|||
if (!border_click) {
|
||||
LOG("client. done.\n");
|
||||
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
|
||||
/* Floating clients should be raised on click */
|
||||
if (client_is_floating(client))
|
||||
xcb_raise_window(conn, client->frame);
|
||||
xcb_flush(conn);
|
||||
return 1;
|
||||
}
|
||||
|
@ -332,9 +362,21 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
|
|||
i3Font *font = load_font(conn, config.font);
|
||||
if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) {
|
||||
LOG("click on titlebar\n");
|
||||
|
||||
/* Floating clients can be dragged by grabbing their titlebar */
|
||||
if (client_is_floating(client)) {
|
||||
/* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */
|
||||
xcb_raise_window(conn, client->frame);
|
||||
xcb_flush(conn);
|
||||
|
||||
floating_drag_window(conn, client, event);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (client_is_floating(client))
|
||||
return floating_border_click(conn, client, event);
|
||||
|
||||
if (event->event_y < 2) {
|
||||
/* This was a press on the top border */
|
||||
if (con->row == 0)
|
||||
|
@ -412,8 +454,61 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
|
|||
Client *client = table_get(&by_child, event->window);
|
||||
if (client == NULL) {
|
||||
LOG("This client is not mapped, so we don't care and just tell the client that he will get its size\n");
|
||||
Rect rect = {event->x, event->y, event->width, event->height};
|
||||
fake_configure_notify(conn, rect, event->window);
|
||||
uint32_t mask = 0;
|
||||
uint32_t values[7];
|
||||
int c = 0;
|
||||
#define COPY_MASK_MEMBER(mask_member, event_member) do { \
|
||||
if (event->value_mask & mask_member) { \
|
||||
mask |= mask_member; \
|
||||
values[c++] = event->event_member; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_X, x);
|
||||
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_Y, y);
|
||||
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_WIDTH, width);
|
||||
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_HEIGHT, height);
|
||||
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_BORDER_WIDTH, border_width);
|
||||
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_SIBLING, sibling);
|
||||
COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_STACK_MODE, stack_mode);
|
||||
|
||||
xcb_configure_window(conn, event->window, mask, values);
|
||||
xcb_flush(conn);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Floating clients can be reconfigured */
|
||||
if (client_is_floating(client)) {
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
|
||||
if (event->value_mask & XCB_CONFIG_WINDOW_X)
|
||||
client->rect.x = event->x;
|
||||
if (event->value_mask & XCB_CONFIG_WINDOW_Y)
|
||||
client->rect.y = event->y;
|
||||
if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH)
|
||||
client->rect.width = event->width + 2 + 2;
|
||||
if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)
|
||||
client->rect.height = event->height + (font->height + 2 + 2) + 2;
|
||||
|
||||
LOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n",
|
||||
client->rect.x, client->rect.y, client->rect.width, client->rect.height);
|
||||
|
||||
/* Push the new position/size to X11 */
|
||||
reposition_client(conn, client);
|
||||
resize_client(conn, client);
|
||||
xcb_flush(conn);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (client->fullscreen) {
|
||||
LOG("Client is in fullscreen mode\n");
|
||||
|
||||
Rect child_rect = client->container->workspace->rect;
|
||||
child_rect.x = child_rect.y = 0;
|
||||
fake_configure_notify(conn, child_rect, client->child);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -495,18 +590,16 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
|
|||
|
||||
client = table_remove(&by_child, event->window);
|
||||
|
||||
if (client->name != NULL)
|
||||
free(client->name);
|
||||
/* If this was the fullscreen client, we need to unset it */
|
||||
if (client->fullscreen)
|
||||
client->workspace->fullscreen_client = NULL;
|
||||
|
||||
/* Clients without a container are either floating or dock windows */
|
||||
if (client->container != NULL) {
|
||||
Container *con = client->container;
|
||||
|
||||
/* If this was the fullscreen client, we need to unset it */
|
||||
if (client->fullscreen)
|
||||
con->workspace->fullscreen_client = NULL;
|
||||
|
||||
/* Remove the client from the list of clients */
|
||||
remove_client_from_container(conn, client, con);
|
||||
client_remove_from_container(conn, client, con, true);
|
||||
|
||||
/* Set focus to the last focused client in this container */
|
||||
con->currently_focused = get_last_focused_client(conn, con, NULL);
|
||||
|
@ -514,6 +607,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
|
|||
/* Only if this is the active container, we need to really change focus */
|
||||
if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen))
|
||||
set_focus(conn, con->currently_focused, true);
|
||||
} else if (client_is_floating(client)) {
|
||||
LOG("Removing from floating clients\n");
|
||||
TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
|
||||
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
|
||||
}
|
||||
|
||||
if (client->dock) {
|
||||
|
@ -528,32 +625,41 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
|
|||
table_remove(&by_parent, client->frame);
|
||||
|
||||
if (client->container != NULL) {
|
||||
cleanup_table(conn, client->container->workspace);
|
||||
fix_colrowspan(conn, client->container->workspace);
|
||||
Workspace *workspace = client->container->workspace;
|
||||
cleanup_table(conn, workspace);
|
||||
fix_colrowspan(conn, workspace);
|
||||
}
|
||||
|
||||
/* Let’s see how many clients there are left on the workspace to delete it if it’s empty */
|
||||
bool workspace_empty = true;
|
||||
FOR_TABLE(client->workspace)
|
||||
if (!CIRCLEQ_EMPTY(&(client->workspace->table[cols][rows]->clients))) {
|
||||
workspace_empty = false;
|
||||
break;
|
||||
}
|
||||
bool workspace_empty = SLIST_EMPTY(&(client->workspace->focus_stack));
|
||||
bool workspace_active = false;
|
||||
Client *to_focus = (!workspace_empty ? SLIST_FIRST(&(client->workspace->focus_stack)) : NULL);
|
||||
|
||||
/* If this workspace is currently active, we don’t delete it */
|
||||
i3Screen *screen;
|
||||
TAILQ_FOREACH(screen, virtual_screens, screens)
|
||||
if (screen->current_workspace == client->workspace->num) {
|
||||
workspace_active = true;
|
||||
workspace_empty = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (workspace_empty)
|
||||
if (workspace_empty) {
|
||||
LOG("setting ws to NULL for workspace %d (%p)\n", client->workspace->num,
|
||||
client->workspace);
|
||||
client->workspace->screen = NULL;
|
||||
}
|
||||
|
||||
FREE(client->window_class);
|
||||
FREE(client->name);
|
||||
free(client);
|
||||
|
||||
render_layout(conn);
|
||||
|
||||
/* Ensure the focus is set to the next client in the focus stack */
|
||||
if (workspace_active && to_focus != NULL)
|
||||
set_focus(conn, to_focus, true);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -598,14 +704,13 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
|
|||
client->name_len = new_len;
|
||||
client->uses_net_wm_name = true;
|
||||
|
||||
if (old_name != NULL)
|
||||
free(old_name);
|
||||
FREE(old_name);
|
||||
|
||||
/* If the client is a dock window, we don’t need to render anything */
|
||||
if (client->dock)
|
||||
return 1;
|
||||
|
||||
if (client->container->mode == MODE_STACK)
|
||||
if (client->container != NULL && client->container->mode == MODE_STACK)
|
||||
render_container(conn, client->container);
|
||||
else decorate_window(conn, client, client->frame, client->titlegc, 0);
|
||||
xcb_flush(conn);
|
||||
|
@ -673,7 +778,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
|
|||
if (client->dock)
|
||||
return 1;
|
||||
|
||||
if (client->container->mode == MODE_STACK)
|
||||
if (client->container != NULL && client->container->mode == MODE_STACK)
|
||||
render_container(conn, client->container);
|
||||
else decorate_window(conn, client, client->frame, client->titlegc, 0);
|
||||
xcb_flush(conn);
|
||||
|
@ -681,6 +786,46 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
|
|||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates the client’s WM_CLASS property
|
||||
*
|
||||
*/
|
||||
int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
|
||||
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
|
||||
LOG("window class changed\n");
|
||||
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||
LOG("prop == NULL\n");
|
||||
return 1;
|
||||
}
|
||||
Client *client = table_get(&by_child, window);
|
||||
if (client == NULL)
|
||||
return 1;
|
||||
char *new_class;
|
||||
if (asprintf(&new_class, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) {
|
||||
perror("Could not get window class");
|
||||
LOG("Could not get window class\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
LOG("changed to %s\n", new_class);
|
||||
char *old_class = client->window_class;
|
||||
client->window_class = new_class;
|
||||
FREE(old_class);
|
||||
|
||||
if (!client->initialized) {
|
||||
LOG("Client is not yet initialized, not putting it to floating\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(new_class, "tools") == 0 || strcmp(new_class, "Dialog") == 0) {
|
||||
LOG("tool/dialog window, should we put it floating?\n");
|
||||
if (client->floating == FLOATING_AUTO_OFF)
|
||||
toggle_floating_mode(conn, client, true);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Expose event means we should redraw our windows (= title bar)
|
||||
*
|
||||
|
@ -717,15 +862,15 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (client->container->mode != MODE_STACK)
|
||||
if (client->container == NULL || client->container->mode != MODE_STACK)
|
||||
decorate_window(conn, client, client->frame, client->titlegc, 0);
|
||||
else {
|
||||
uint32_t background_color;
|
||||
/* Distinguish if the window is currently focused… */
|
||||
if (CUR_CELL->currently_focused == client)
|
||||
background_color = get_colorpixel(conn, "#285577");
|
||||
background_color = config.client.focused.background;
|
||||
/* …or if it is the focused window in a not focused container */
|
||||
else background_color = get_colorpixel(conn, "#555555");
|
||||
else background_color = config.client.focused_inactive.background;
|
||||
|
||||
/* Set foreground color to current focused color, line width to 2 */
|
||||
uint32_t values[] = {background_color, 2};
|
||||
|
@ -771,7 +916,7 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message
|
|||
(!client->fullscreen &&
|
||||
(event->data.data32[0] == _NET_WM_STATE_ADD ||
|
||||
event->data.data32[0] == _NET_WM_STATE_TOGGLE)))
|
||||
toggle_fullscreen(conn, client);
|
||||
client_toggle_fullscreen(conn, client);
|
||||
} else {
|
||||
LOG("unhandled clientmessage\n");
|
||||
return 0;
|
||||
|
@ -804,6 +949,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
|
|||
return 1;
|
||||
}
|
||||
xcb_size_hints_t size_hints;
|
||||
LOG("client is %08x / child %08x\n", client->frame, client->child);
|
||||
|
||||
/* If the hints were already in this event, use them, if not, request them */
|
||||
if (reply != NULL)
|
||||
|
@ -811,6 +957,11 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
|
|||
else
|
||||
xcb_get_wm_normal_hints_reply(conn, xcb_get_wm_normal_hints_unchecked(conn, client->child), &size_hints, NULL);
|
||||
|
||||
if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) {
|
||||
LOG("min size set\n");
|
||||
LOG("gots min_width = %d, min_height = %d\n", size_hints.min_width, size_hints.min_height);
|
||||
}
|
||||
|
||||
/* If no aspect ratio was set or if it was invalid, we ignore the hints */
|
||||
if (!(size_hints.flags & XCB_SIZE_HINT_P_ASPECT) ||
|
||||
(size_hints.min_aspect_num <= 0) ||
|
||||
|
@ -821,8 +972,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
|
|||
|
||||
LOG("window is %08x / %s\n", client->child, client->name);
|
||||
|
||||
int base_width = 0, base_height = 0,
|
||||
min_width = 0, min_height = 0;
|
||||
int base_width = 0, base_height = 0;
|
||||
|
||||
/* base_width/height are the desired size of the window.
|
||||
We check if either the program-specified size or the program-specified
|
||||
|
@ -835,14 +985,6 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
|
|||
base_height = size_hints.min_height;
|
||||
}
|
||||
|
||||
if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) {
|
||||
min_width = size_hints.min_width;
|
||||
min_height = size_hints.min_height;
|
||||
} else if (size_hints.flags & XCB_SIZE_HINT_P_SIZE) {
|
||||
min_width = size_hints.base_width;
|
||||
min_height = size_hints.base_height;
|
||||
}
|
||||
|
||||
double width = client->rect.width - base_width;
|
||||
double height = client->rect.height - base_height;
|
||||
/* Convert numerator/denominator to a double */
|
||||
|
@ -874,3 +1016,42 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
|
|||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handles the transient for hints set by a window, signalizing that this window is a popup window
|
||||
* for some other window.
|
||||
*
|
||||
* See ICCCM 4.1.2.6 for more details
|
||||
*
|
||||
*/
|
||||
int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
|
||||
xcb_atom_t name, xcb_get_property_reply_t *reply) {
|
||||
LOG("Transient hint!\n");
|
||||
Client *client = table_get(&by_child, window);
|
||||
if (client == NULL) {
|
||||
LOG("No such client\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
xcb_window_t transient_for;
|
||||
|
||||
if (reply != NULL) {
|
||||
if (!xcb_get_wm_transient_for_from_reply(&transient_for, reply)) {
|
||||
LOG("Not transient for any window\n");
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if (!xcb_get_wm_transient_for_reply(conn, xcb_get_wm_transient_for_unchecked(conn, window),
|
||||
&transient_for, NULL)) {
|
||||
LOG("Not transient for any window\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (client->floating == FLOATING_AUTO_OFF) {
|
||||
LOG("This is a popup window, putting into floating\n");
|
||||
toggle_floating_mode(conn, client, true);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
112
src/layout.c
112
src/layout.c
|
@ -24,6 +24,8 @@
|
|||
#include "util.h"
|
||||
#include "xinerama.h"
|
||||
#include "layout.h"
|
||||
#include "client.h"
|
||||
#include "floating.h"
|
||||
|
||||
/*
|
||||
* Updates *destination with new_value and returns true if it was changed or false
|
||||
|
@ -83,7 +85,7 @@ int get_unoccupied_y(Workspace *workspace, int col) {
|
|||
*
|
||||
*/
|
||||
void redecorate_window(xcb_connection_t *conn, Client *client) {
|
||||
if (client->container->mode == MODE_STACK) {
|
||||
if (client->container != NULL && client->container->mode == MODE_STACK) {
|
||||
render_container(conn, client->container);
|
||||
/* We clear the frame to generate exposure events, because the color used
|
||||
in drawing may be different */
|
||||
|
@ -100,47 +102,39 @@ void redecorate_window(xcb_connection_t *conn, Client *client) {
|
|||
void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable, xcb_gcontext_t gc, int offset) {
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
int decoration_height = font->height + 2 + 2;
|
||||
uint32_t background_color,
|
||||
text_color,
|
||||
border_color;
|
||||
struct Colortriple *color;
|
||||
|
||||
/* Clients without a container (docks) won’t get decorated */
|
||||
if (client->container == NULL)
|
||||
if (client->dock)
|
||||
return;
|
||||
|
||||
if (client->container->currently_focused == client) {
|
||||
LOG("redecorating child %08x\n", client->child);
|
||||
if (client_is_floating(client) || client->container->currently_focused == client) {
|
||||
/* Distinguish if the window is currently focused… */
|
||||
if (CUR_CELL->currently_focused == client)
|
||||
background_color = get_colorpixel(conn, "#285577");
|
||||
if (client_is_floating(client) || CUR_CELL->currently_focused == client)
|
||||
color = &(config.client.focused);
|
||||
/* …or if it is the focused window in a not focused container */
|
||||
else background_color = get_colorpixel(conn, "#555555");
|
||||
|
||||
text_color = get_colorpixel(conn, "#ffffff");
|
||||
border_color = get_colorpixel(conn, "#4c7899");
|
||||
} else {
|
||||
background_color = get_colorpixel(conn, "#222222");
|
||||
text_color = get_colorpixel(conn, "#888888");
|
||||
border_color = get_colorpixel(conn, "#333333");
|
||||
}
|
||||
else color = &(config.client.focused_inactive);
|
||||
} else color = &(config.client.unfocused);
|
||||
|
||||
/* Our plan is the following:
|
||||
- Draw a rect around the whole client in background_color
|
||||
- Draw a rect around the whole client in color->background
|
||||
- Draw two lines in a lighter color
|
||||
- Draw the window’s title
|
||||
*/
|
||||
|
||||
/* Draw a rectangle in background color around the window */
|
||||
xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, background_color);
|
||||
xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background);
|
||||
|
||||
/* In stacking mode, we only render the rect for this specific decoration */
|
||||
if (client->container->mode == MODE_STACK) {
|
||||
xcb_rectangle_t rect = {0, offset, client->container->width, offset + decoration_height };
|
||||
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
|
||||
} else {
|
||||
if (client->container != NULL && client->container->mode == MODE_STACK) {
|
||||
/* We need to use the container’s width because it is the more recent value - when
|
||||
in stacking mode, clients get reconfigured only on demand (the not active client
|
||||
is not reconfigured), so the client’s rect.width would be wrong */
|
||||
xcb_rectangle_t rect = {0, 0, client->container->width, client->rect.height};
|
||||
xcb_rectangle_t rect = {0, offset, client->container->width, offset + decoration_height };
|
||||
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
|
||||
} else {
|
||||
xcb_rectangle_t rect = {0, 0, client->rect.width, client->rect.height};
|
||||
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
|
||||
|
||||
/* Draw the inner background to have a black frame around clients (such as mplayer)
|
||||
|
@ -152,15 +146,15 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
|
|||
}
|
||||
|
||||
/* Draw the lines */
|
||||
xcb_draw_line(conn, drawable, gc, border_color, 2, offset, client->rect.width, offset);
|
||||
xcb_draw_line(conn, drawable, gc, border_color, 2, offset + font->height + 3,
|
||||
2 + client->rect.width, offset + font->height + 3);
|
||||
xcb_draw_line(conn, drawable, gc, color->border, 0, offset, client->rect.width, offset);
|
||||
xcb_draw_line(conn, drawable, gc, color->border, 2, offset + font->height + 3,
|
||||
client->rect.width - 3, offset + font->height + 3);
|
||||
|
||||
/* If the client has a title, we draw it */
|
||||
if (client->name != NULL) {
|
||||
/* Draw the font */
|
||||
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
|
||||
uint32_t values[] = { text_color, background_color, font->id };
|
||||
uint32_t values[] = { color->text, color->background, font->id };
|
||||
xcb_change_gc(conn, gc, mask, values);
|
||||
|
||||
/* name_len == -1 means this is a legacy application which does not specify _NET_WM_NAME,
|
||||
|
@ -179,18 +173,37 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
|
|||
* Pushes the client’s x and y coordinates to X11
|
||||
*
|
||||
*/
|
||||
static void reposition_client(xcb_connection_t *conn, Client *client) {
|
||||
void reposition_client(xcb_connection_t *conn, Client *client) {
|
||||
i3Screen *screen;
|
||||
|
||||
LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
|
||||
/* Note: We can use a pointer to client->x like an array of uint32_ts
|
||||
because it is followed by client->y by definition */
|
||||
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
|
||||
|
||||
if (!client_is_floating(client))
|
||||
return;
|
||||
|
||||
/* If the client is floating, we need to check if we moved it to a different workspace */
|
||||
if (client->workspace->screen == (screen = get_screen_containing(client->rect.x, client->rect.y)))
|
||||
return;
|
||||
|
||||
if (screen == NULL) {
|
||||
LOG("Boundary checking disabled, no screen found for (%d, %d)\n", client->rect.x, client->rect.y);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("Client is on workspace %p with screen %p\n", client->workspace, client->workspace->screen);
|
||||
LOG("but screen at %d, %d is %p\n", client->rect.x, client->rect.y, screen);
|
||||
floating_assign_to_workspace(client, &workspaces[screen->current_workspace]);
|
||||
LOG("fixed that\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Pushes the client’s width/height to X11 and resizes the child window
|
||||
*
|
||||
*/
|
||||
static void resize_client(xcb_connection_t *conn, Client *client) {
|
||||
void resize_client(xcb_connection_t *conn, Client *client) {
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
|
||||
LOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
|
||||
|
@ -335,8 +348,13 @@ void render_container(xcb_connection_t *conn, Container *container) {
|
|||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
|
||||
XCB_CONFIG_WINDOW_STACK_MODE;
|
||||
|
||||
/* If there is no fullscreen client, we raise the stack window */
|
||||
if (container->workspace->fullscreen_client != NULL) {
|
||||
/* Raise the stack window, but keep it below the first floating client
|
||||
* and below the fullscreen client (if any) */
|
||||
Client *first_floating = TAILQ_FIRST(&(container->workspace->floating_clients));
|
||||
if (first_floating != TAILQ_END(&(container->workspace->floating_clients))) {
|
||||
mask |= XCB_CONFIG_WINDOW_SIBLING;
|
||||
values[4] = first_floating->frame;
|
||||
} else if (container->workspace->fullscreen_client != NULL) {
|
||||
mask |= XCB_CONFIG_WINDOW_SIBLING;
|
||||
values[4] = container->workspace->fullscreen_client->frame;
|
||||
}
|
||||
|
@ -344,9 +362,6 @@ void render_container(xcb_connection_t *conn, Container *container) {
|
|||
xcb_configure_window(conn, stack_win->window, mask, values);
|
||||
}
|
||||
|
||||
/* Reconfigure the currently focused client, if necessary. It is the only visible one */
|
||||
client = container->currently_focused;
|
||||
|
||||
/* Render the decorations of all clients */
|
||||
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
|
||||
/* If the client is in fullscreen mode, it does not get reconfigured */
|
||||
|
@ -400,24 +415,10 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid
|
|||
i3Font *font = load_font(conn, config.font);
|
||||
i3Screen *screen = r_ws->screen;
|
||||
enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
|
||||
uint32_t background_color[2],
|
||||
text_color[2],
|
||||
border_color[2],
|
||||
black;
|
||||
char label[3];
|
||||
|
||||
black = get_colorpixel(conn, "#000000");
|
||||
|
||||
background_color[SET_NORMAL] = get_colorpixel(conn, "#222222");
|
||||
text_color[SET_NORMAL] = get_colorpixel(conn, "#888888");
|
||||
border_color[SET_NORMAL] = get_colorpixel(conn, "#333333");
|
||||
|
||||
background_color[SET_FOCUSED] = get_colorpixel(conn, "#285577");
|
||||
text_color[SET_FOCUSED] = get_colorpixel(conn, "#ffffff");
|
||||
border_color[SET_FOCUSED] = get_colorpixel(conn, "#4c7899");
|
||||
|
||||
/* Fill the whole bar in black */
|
||||
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, black);
|
||||
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
|
||||
xcb_rectangle_t rect = {0, 0, width, height};
|
||||
xcb_poly_fill_rectangle(conn, screen->bar, screen->bargc, 1, &rect);
|
||||
|
||||
|
@ -429,16 +430,17 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid
|
|||
if (workspaces[c].screen != screen)
|
||||
continue;
|
||||
|
||||
int set = (screen->current_workspace == c ? SET_FOCUSED : SET_NORMAL);
|
||||
struct Colortriple *color = (screen->current_workspace == c ? &(config.bar.focused) :
|
||||
&(config.bar.unfocused));
|
||||
|
||||
xcb_draw_rect(conn, screen->bar, screen->bargc, border_color[set],
|
||||
xcb_draw_rect(conn, screen->bar, screen->bargc, color->border,
|
||||
drawn * height, 1, height - 2, height - 2);
|
||||
xcb_draw_rect(conn, screen->bar, screen->bargc, background_color[set],
|
||||
xcb_draw_rect(conn, screen->bar, screen->bargc, color->background,
|
||||
drawn * height + 1, 2, height - 4, height - 4);
|
||||
|
||||
snprintf(label, sizeof(label), "%d", c+1);
|
||||
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, text_color[set]);
|
||||
xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, background_color[set]);
|
||||
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, color->text);
|
||||
xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, color->background);
|
||||
xcb_image_text_8(conn, strlen(label), screen->bar, screen->bargc, drawn * height + 5 /* X */,
|
||||
font->height + 1 /* Y = baseline of font */, label);
|
||||
drawn++;
|
||||
|
|
337
src/mainx.c
337
src/mainx.c
|
@ -31,6 +31,8 @@
|
|||
#include <xcb/xcb_icccm.h>
|
||||
#include <xcb/xinerama.h>
|
||||
|
||||
#include <ev.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "data.h"
|
||||
#include "debug.h"
|
||||
|
@ -42,6 +44,7 @@
|
|||
#include "util.h"
|
||||
#include "xcb.h"
|
||||
#include "xinerama.h"
|
||||
#include "manage.h"
|
||||
|
||||
/* This is the path to i3, copied from argv[0] when starting up */
|
||||
char **start_argv;
|
||||
|
@ -52,6 +55,12 @@ Display *xkbdpy;
|
|||
/* The list of key bindings */
|
||||
struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
|
||||
|
||||
/* The list of exec-lines */
|
||||
struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts);
|
||||
|
||||
/* The list of assignments */
|
||||
struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments);
|
||||
|
||||
/* This is a list of Stack_Windows, global, for easier/faster access on expose events */
|
||||
struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
|
||||
|
||||
|
@ -63,273 +72,40 @@ xcb_atom_t atoms[NUM_ATOMS];
|
|||
int num_screens = 0;
|
||||
|
||||
/*
|
||||
* Do some sanity checks and then reparent the window.
|
||||
* This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
|
||||
* See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
|
||||
*
|
||||
*/
|
||||
void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
|
||||
LOG("managing window.\n");
|
||||
xcb_drawable_t d = { window };
|
||||
xcb_get_geometry_cookie_t geomc;
|
||||
xcb_get_geometry_reply_t *geom;
|
||||
xcb_get_window_attributes_reply_t *attr = 0;
|
||||
|
||||
if (wa.tag == TAG_COOKIE) {
|
||||
/* Check if the window is mapped (it could be not mapped when intializing and
|
||||
calling manage_window() for every window) */
|
||||
if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
|
||||
return;
|
||||
|
||||
if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
|
||||
goto out;
|
||||
|
||||
wa.tag = TAG_VALUE;
|
||||
wa.u.override_redirect = attr->override_redirect;
|
||||
}
|
||||
|
||||
/* Don’t manage clients with the override_redirect flag */
|
||||
if (wa.u.override_redirect)
|
||||
goto out;
|
||||
|
||||
/* Check if the window is already managed */
|
||||
if (table_get(&by_child, window))
|
||||
goto out;
|
||||
|
||||
/* Get the initial geometry (position, size, …) */
|
||||
geomc = xcb_get_geometry(conn, d);
|
||||
if (!attr) {
|
||||
wa.tag = TAG_COOKIE;
|
||||
wa.u.cookie = xcb_get_window_attributes(conn, window);
|
||||
attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0);
|
||||
}
|
||||
geom = xcb_get_geometry_reply(conn, geomc, 0);
|
||||
if (attr && geom) {
|
||||
reparent_window(conn, window, attr->visual, geom->root, geom->depth,
|
||||
geom->x, geom->y, geom->width, geom->height);
|
||||
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
|
||||
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
|
||||
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
|
||||
}
|
||||
|
||||
free(geom);
|
||||
out:
|
||||
free(attr);
|
||||
return;
|
||||
static void xcb_got_event(EV_P_ struct ev_io *w, int revents) {
|
||||
/* empty, because xcb_prepare_cb and xcb_check_cb are used */
|
||||
}
|
||||
|
||||
/*
|
||||
* reparent_window() gets called when a new window was opened and becomes a child of the root
|
||||
* window, or it gets called by us when we manage the already existing windows at startup.
|
||||
*
|
||||
* Essentially, this is the point where we take over control.
|
||||
* Flush before blocking (and waiting for new events)
|
||||
*
|
||||
*/
|
||||
void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
||||
xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
|
||||
int16_t x, int16_t y, uint16_t width, uint16_t height) {
|
||||
|
||||
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie;
|
||||
uint32_t mask = 0;
|
||||
uint32_t values[3];
|
||||
|
||||
/* We are interested in property changes */
|
||||
mask = XCB_CW_EVENT_MASK;
|
||||
values[0] = CHILD_EVENT_MASK;
|
||||
xcb_change_window_attributes(conn, child, mask, values);
|
||||
|
||||
/* Map the window first to avoid flickering */
|
||||
xcb_map_window(conn, child);
|
||||
|
||||
/* Place requests for properties ASAP */
|
||||
wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
|
||||
strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
|
||||
state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
|
||||
|
||||
Client *new = table_get(&by_child, child);
|
||||
|
||||
/* Events for already managed windows should already be filtered in manage_window() */
|
||||
assert(new == NULL);
|
||||
|
||||
LOG("reparenting new client\n");
|
||||
new = calloc(sizeof(Client), 1);
|
||||
new->force_reconfigure = true;
|
||||
|
||||
/* Update the data structures */
|
||||
Client *old_focused = CUR_CELL->currently_focused;
|
||||
|
||||
new->container = CUR_CELL;
|
||||
new->workspace = new->container->workspace;
|
||||
|
||||
new->frame = xcb_generate_id(conn);
|
||||
new->child = child;
|
||||
new->rect.width = width;
|
||||
new->rect.height = height;
|
||||
|
||||
mask = 0;
|
||||
|
||||
/* Don’t generate events for our new window, it should *not* be managed */
|
||||
mask |= XCB_CW_OVERRIDE_REDIRECT;
|
||||
values[0] = 1;
|
||||
|
||||
/* We want to know when… */
|
||||
mask |= XCB_CW_EVENT_MASK;
|
||||
values[1] = FRAME_EVENT_MASK;
|
||||
|
||||
LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
|
||||
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
width = min(width, c_ws->rect.x + c_ws->rect.width);
|
||||
height = min(height, c_ws->rect.y + c_ws->rect.height);
|
||||
|
||||
Rect framerect = {x, y,
|
||||
width + 2 + 2, /* 2 px border at each side */
|
||||
height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
|
||||
|
||||
/* Yo dawg, I heard you like windows, so I create a window around your window… */
|
||||
new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
|
||||
|
||||
long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
|
||||
|
||||
/* Put the client inside the save set. Upon termination (whether killed or normal exit
|
||||
does not matter) of the window manager, these clients will be correctly reparented
|
||||
to their most closest living ancestor (= cleanup) */
|
||||
xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
|
||||
|
||||
/* Generate a graphics context for the titlebar */
|
||||
new->titlegc = xcb_generate_id(conn);
|
||||
xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
|
||||
|
||||
/* Moves the original window into the new frame we've created for it */
|
||||
new->awaiting_useless_unmap = true;
|
||||
xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
|
||||
if (xcb_request_check(conn, cookie) != NULL) {
|
||||
LOG("Could not reparent the window, aborting\n");
|
||||
xcb_destroy_window(conn, new->frame);
|
||||
free(new);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Put our data structure (Client) into the table */
|
||||
table_put(&by_parent, new->frame, new);
|
||||
table_put(&by_child, child, new);
|
||||
|
||||
/* We need to grab the mouse buttons for click to focus */
|
||||
xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
|
||||
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
|
||||
1 /* left mouse button */,
|
||||
XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
|
||||
|
||||
/* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
|
||||
xcb_atom_t *atom;
|
||||
xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
|
||||
if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
|
||||
for (int i = 0; i < xcb_get_property_value_length(preply); i++)
|
||||
if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
|
||||
LOG("Window is a dock.\n");
|
||||
new->dock = true;
|
||||
new->titlebar_position = TITLEBAR_OFF;
|
||||
new->force_reconfigure = true;
|
||||
new->container = NULL;
|
||||
SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
|
||||
}
|
||||
}
|
||||
|
||||
if (new->dock) {
|
||||
/* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
|
||||
uint32_t *strut;
|
||||
preply = xcb_get_property_reply(conn, strut_cookie, NULL);
|
||||
if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
|
||||
/* We only use a subset of the provided values, namely the reserved space at the top/bottom
|
||||
of the screen. This is because the only possibility for bars is at to be at the top/bottom
|
||||
with maximum horizontal size.
|
||||
TODO: bars at the top */
|
||||
new->desired_height = strut[3];
|
||||
if (new->desired_height == 0) {
|
||||
LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height);
|
||||
new->desired_height = height;
|
||||
}
|
||||
LOG("the client wants to be %d pixels high\n", new->desired_height);
|
||||
} else {
|
||||
LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height);
|
||||
new->desired_height = height;
|
||||
}
|
||||
}
|
||||
|
||||
/* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
|
||||
if (CUR_CELL->workspace->fullscreen_client == NULL) {
|
||||
if (!new->dock) {
|
||||
CUR_CELL->currently_focused = new;
|
||||
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
|
||||
}
|
||||
} else {
|
||||
/* If we are in fullscreen, we should lower the window to not be annoying */
|
||||
uint32_t values[] = { XCB_STACK_MODE_BELOW };
|
||||
xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
}
|
||||
|
||||
/* Insert into the currently active container, if it’s not a dock window */
|
||||
if (!new->dock) {
|
||||
/* Insert after the old active client, if existing. If it does not exist, the
|
||||
container is empty and it does not matter, where we insert it */
|
||||
if (old_focused != NULL && !old_focused->dock)
|
||||
CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
|
||||
else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
|
||||
|
||||
SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
|
||||
}
|
||||
|
||||
/* Check if the window already got the fullscreen hint set */
|
||||
xcb_atom_t *state;
|
||||
if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
|
||||
(state = xcb_get_property_value(preply)) != NULL)
|
||||
/* Check all set _NET_WM_STATEs */
|
||||
for (int i = 0; i < xcb_get_property_value_length(preply); i++)
|
||||
if (state[i] == atoms[_NET_WM_STATE_FULLSCREEN]) {
|
||||
/* If the window got the fullscreen state, we just toggle fullscreen
|
||||
and don’t event bother to redraw the layout – that would not change
|
||||
anything anyways */
|
||||
toggle_fullscreen(conn, new);
|
||||
return;
|
||||
}
|
||||
|
||||
render_layout(conn);
|
||||
static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
|
||||
xcb_flush(evenths.c);
|
||||
}
|
||||
|
||||
/*
|
||||
* Go through all existing windows (if the window manager is restarted) and manage them
|
||||
* Instead of polling the X connection socket we leave this to
|
||||
* xcb_poll_for_event() which knows better than we can ever know.
|
||||
*
|
||||
*/
|
||||
void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
|
||||
xcb_query_tree_reply_t *reply;
|
||||
int i, len;
|
||||
xcb_window_t *children;
|
||||
xcb_get_window_attributes_cookie_t *cookies;
|
||||
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 xcb’s event handler */
|
||||
xcb_event_wait_for_event_loop(&evenths);
|
||||
/* Handle the events which arrived until now */
|
||||
xcb_check_cb(NULL, NULL, 0);
|
||||
|
||||
/* Ungrab the server to receive events and enter libev’s eventloop */
|
||||
xcb_ungrab_server(conn);
|
||||
ev_loop(loop, 0);
|
||||
|
||||
/* not reached */
|
||||
return 0;
|
||||
|
|
|
@ -0,0 +1,425 @@
|
|||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
* src/manage.c: Contains all functions for initially managing new windows
|
||||
* (or existing ones on restart).
|
||||
*
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_icccm.h>
|
||||
|
||||
#include "xcb.h"
|
||||
#include "data.h"
|
||||
#include "util.h"
|
||||
#include "i3.h"
|
||||
#include "table.h"
|
||||
#include "config.h"
|
||||
#include "handlers.h"
|
||||
#include "layout.h"
|
||||
#include "manage.h"
|
||||
#include "floating.h"
|
||||
#include "client.h"
|
||||
|
||||
/*
|
||||
* Go through all existing windows (if the window manager is restarted) and manage them
|
||||
*
|
||||
*/
|
||||
void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
|
||||
xcb_query_tree_reply_t *reply;
|
||||
int i, len;
|
||||
xcb_window_t *children;
|
||||
xcb_get_window_attributes_cookie_t *cookies;
|
||||
|
||||
/* Get the tree of windows whose parent is the root window (= all) */
|
||||
if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
|
||||
return;
|
||||
|
||||
len = xcb_query_tree_children_length(reply);
|
||||
cookies = smalloc(len * sizeof(*cookies));
|
||||
|
||||
/* Request the window attributes for every window */
|
||||
children = xcb_query_tree_children(reply);
|
||||
for(i = 0; i < len; ++i)
|
||||
cookies[i] = xcb_get_window_attributes(conn, children[i]);
|
||||
|
||||
/* Call manage_window with the attributes for every window */
|
||||
for(i = 0; i < len; ++i) {
|
||||
window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
|
||||
manage_window(prophs, conn, children[i], wa);
|
||||
}
|
||||
|
||||
free(reply);
|
||||
free(cookies);
|
||||
}
|
||||
|
||||
/*
|
||||
* Do some sanity checks and then reparent the window.
|
||||
*
|
||||
*/
|
||||
void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
|
||||
LOG("managing window.\n");
|
||||
xcb_drawable_t d = { window };
|
||||
xcb_get_geometry_cookie_t geomc;
|
||||
xcb_get_geometry_reply_t *geom;
|
||||
xcb_get_window_attributes_reply_t *attr = 0;
|
||||
|
||||
if (wa.tag == TAG_COOKIE) {
|
||||
/* Check if the window is mapped (it could be not mapped when intializing and
|
||||
calling manage_window() for every window) */
|
||||
if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
|
||||
return;
|
||||
|
||||
if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
|
||||
goto out;
|
||||
|
||||
wa.tag = TAG_VALUE;
|
||||
wa.u.override_redirect = attr->override_redirect;
|
||||
}
|
||||
|
||||
/* Don’t manage clients with the override_redirect flag */
|
||||
if (wa.u.override_redirect)
|
||||
goto out;
|
||||
|
||||
/* Check if the window is already managed */
|
||||
if (table_get(&by_child, window))
|
||||
goto out;
|
||||
|
||||
/* Get the initial geometry (position, size, …) */
|
||||
geomc = xcb_get_geometry(conn, d);
|
||||
if (!attr) {
|
||||
wa.tag = TAG_COOKIE;
|
||||
wa.u.cookie = xcb_get_window_attributes(conn, window);
|
||||
if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
|
||||
return;
|
||||
}
|
||||
if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
|
||||
goto out;
|
||||
|
||||
/* Reparent the window and add it to our list of managed windows */
|
||||
reparent_window(conn, window, attr->visual, geom->root, geom->depth,
|
||||
geom->x, geom->y, geom->width, geom->height);
|
||||
|
||||
/* Generate callback events for every property we watch */
|
||||
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
|
||||
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
|
||||
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
|
||||
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR);
|
||||
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
|
||||
|
||||
free(geom);
|
||||
out:
|
||||
free(attr);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* reparent_window() gets called when a new window was opened and becomes a child of the root
|
||||
* window, or it gets called by us when we manage the already existing windows at startup.
|
||||
*
|
||||
* Essentially, this is the point where we take over control.
|
||||
*
|
||||
*/
|
||||
void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
||||
xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
|
||||
int16_t x, int16_t y, uint16_t width, uint16_t height) {
|
||||
|
||||
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
|
||||
utf8_title_cookie, title_cookie, class_cookie;
|
||||
uint32_t mask = 0;
|
||||
uint32_t values[3];
|
||||
uint16_t original_height = height;
|
||||
|
||||
/* We are interested in property changes */
|
||||
mask = XCB_CW_EVENT_MASK;
|
||||
values[0] = CHILD_EVENT_MASK;
|
||||
xcb_change_window_attributes(conn, child, mask, values);
|
||||
|
||||
/* Map the window first to avoid flickering */
|
||||
xcb_map_window(conn, child);
|
||||
|
||||
/* Place requests for properties ASAP */
|
||||
wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
|
||||
strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
|
||||
state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
|
||||
utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
|
||||
title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
|
||||
class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
|
||||
|
||||
Client *new = table_get(&by_child, child);
|
||||
|
||||
/* Events for already managed windows should already be filtered in manage_window() */
|
||||
assert(new == NULL);
|
||||
|
||||
LOG("reparenting new client\n");
|
||||
LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
|
||||
new = calloc(sizeof(Client), 1);
|
||||
new->force_reconfigure = true;
|
||||
|
||||
/* Update the data structures */
|
||||
Client *old_focused = CUR_CELL->currently_focused;
|
||||
|
||||
new->container = CUR_CELL;
|
||||
new->workspace = new->container->workspace;
|
||||
|
||||
/* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
|
||||
width = max(width, 75);
|
||||
height = max(height, 50);
|
||||
|
||||
new->frame = xcb_generate_id(conn);
|
||||
new->child = child;
|
||||
new->rect.width = width;
|
||||
new->rect.height = height;
|
||||
/* Pre-initialize the values for floating */
|
||||
new->floating_rect.x = -1;
|
||||
new->floating_rect.width = width;
|
||||
new->floating_rect.height = height;
|
||||
|
||||
mask = 0;
|
||||
|
||||
/* Don’t generate events for our new window, it should *not* be managed */
|
||||
mask |= XCB_CW_OVERRIDE_REDIRECT;
|
||||
values[0] = 1;
|
||||
|
||||
/* We want to know when… */
|
||||
mask |= XCB_CW_EVENT_MASK;
|
||||
values[1] = FRAME_EVENT_MASK;
|
||||
|
||||
LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
|
||||
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
width = min(width, c_ws->rect.x + c_ws->rect.width);
|
||||
height = min(height, c_ws->rect.y + c_ws->rect.height);
|
||||
|
||||
Rect framerect = {x, y,
|
||||
width + 2 + 2, /* 2 px border at each side */
|
||||
height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
|
||||
|
||||
/* Yo dawg, I heard you like windows, so I create a window around your window… */
|
||||
new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
|
||||
|
||||
/* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t.
|
||||
* Also, xprop(1) needs that to work. */
|
||||
long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
|
||||
|
||||
/* Put the client inside the save set. Upon termination (whether killed or normal exit
|
||||
does not matter) of the window manager, these clients will be correctly reparented
|
||||
to their most closest living ancestor (= cleanup) */
|
||||
xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
|
||||
|
||||
/* Generate a graphics context for the titlebar */
|
||||
new->titlegc = xcb_generate_id(conn);
|
||||
xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
|
||||
|
||||
/* Moves the original window into the new frame we've created for it */
|
||||
new->awaiting_useless_unmap = true;
|
||||
xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
|
||||
if (xcb_request_check(conn, cookie) != NULL) {
|
||||
LOG("Could not reparent the window, aborting\n");
|
||||
xcb_destroy_window(conn, new->frame);
|
||||
free(new);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Put our data structure (Client) into the table */
|
||||
table_put(&by_parent, new->frame, new);
|
||||
table_put(&by_child, child, new);
|
||||
|
||||
/* We need to grab the mouse buttons for click to focus */
|
||||
xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
|
||||
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
|
||||
1 /* left mouse button */,
|
||||
XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
|
||||
|
||||
xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
|
||||
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
|
||||
1 /* left mouse button */, XCB_MOD_MASK_1);
|
||||
|
||||
/* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
|
||||
xcb_atom_t *atom;
|
||||
xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
|
||||
if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
|
||||
for (int i = 0; i < xcb_get_property_value_length(preply); i++)
|
||||
if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
|
||||
LOG("Window is a dock.\n");
|
||||
new->dock = true;
|
||||
new->titlebar_position = TITLEBAR_OFF;
|
||||
new->force_reconfigure = true;
|
||||
new->container = NULL;
|
||||
SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
|
||||
/* If it’s a dock we can’t make it float, so we break */
|
||||
break;
|
||||
} else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
|
||||
atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
|
||||
atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
|
||||
atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
|
||||
/* Set the dialog window to automatically floating, will be used below */
|
||||
new->floating = FLOATING_AUTO_ON;
|
||||
LOG("dialog/utility/toolbar/splash window, automatically floating\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (new->workspace->auto_float) {
|
||||
new->floating = FLOATING_AUTO_ON;
|
||||
LOG("workspace is in autofloat mode, setting floating\n");
|
||||
}
|
||||
|
||||
if (new->dock) {
|
||||
/* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
|
||||
uint32_t *strut;
|
||||
preply = xcb_get_property_reply(conn, strut_cookie, NULL);
|
||||
if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
|
||||
/* We only use a subset of the provided values, namely the reserved space at the top/bottom
|
||||
of the screen. This is because the only possibility for bars is at to be at the top/bottom
|
||||
with maximum horizontal size.
|
||||
TODO: bars at the top */
|
||||
new->desired_height = strut[3];
|
||||
if (new->desired_height == 0) {
|
||||
LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
|
||||
new->desired_height = original_height;
|
||||
}
|
||||
LOG("the client wants to be %d pixels high\n", new->desired_height);
|
||||
} else {
|
||||
LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
|
||||
new->desired_height = original_height;
|
||||
}
|
||||
} else {
|
||||
/* If it’s not a dock, we can check on which workspace we should put it. */
|
||||
|
||||
/* Firstly, we need to get the window’s class / title. We asked for the properties at the
|
||||
* top of this function, get them now and pass them to our callback function for window class / title
|
||||
* changes. It is important that the client was already inserted into the by_child table,
|
||||
* because the callbacks won’t work otherwise. */
|
||||
preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
|
||||
handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
|
||||
|
||||
preply = xcb_get_property_reply(conn, title_cookie, NULL);
|
||||
handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
|
||||
|
||||
preply = xcb_get_property_reply(conn, class_cookie, NULL);
|
||||
handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
|
||||
|
||||
LOG("DEBUG: should have all infos now\n");
|
||||
struct Assignment *assign;
|
||||
TAILQ_FOREACH(assign, &assignments, assignments) {
|
||||
if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
|
||||
continue;
|
||||
|
||||
if (assign->floating) {
|
||||
new->floating = FLOATING_AUTO_ON;
|
||||
LOG("Assignment matches, putting client into floating mode\n");
|
||||
break;
|
||||
}
|
||||
|
||||
LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
|
||||
assign->windowclass_title, assign->workspace);
|
||||
|
||||
if (c_ws->screen->current_workspace == (assign->workspace-1)) {
|
||||
LOG("We are already there, no need to do anything\n");
|
||||
break;
|
||||
}
|
||||
|
||||
LOG("Changin container/workspace and unmapping the client\n");
|
||||
Workspace *t_ws = &(workspaces[assign->workspace-1]);
|
||||
if (t_ws->screen == NULL) {
|
||||
LOG("initializing new workspace, setting num to %d\n", assign->workspace);
|
||||
t_ws->screen = c_ws->screen;
|
||||
/* Copy the dimensions from the virtual screen */
|
||||
memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
|
||||
}
|
||||
|
||||
new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
|
||||
new->workspace = t_ws;
|
||||
old_focused = new->container->currently_focused;
|
||||
|
||||
xcb_unmap_window(conn, new->frame);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (CUR_CELL->workspace->fullscreen_client != NULL) {
|
||||
if (new->container == CUR_CELL) {
|
||||
/* If we are in fullscreen, we should lower the window to not be annoying */
|
||||
uint32_t values[] = { XCB_STACK_MODE_BELOW };
|
||||
xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
}
|
||||
} else if (!new->dock) {
|
||||
/* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
|
||||
if (new->container->workspace->fullscreen_client == NULL) {
|
||||
if (!client_is_floating(new))
|
||||
new->container->currently_focused = new;
|
||||
if (new->container == CUR_CELL)
|
||||
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
/* Insert into the currently active container, if it’s not a dock window */
|
||||
if (!new->dock && !client_is_floating(new)) {
|
||||
/* Insert after the old active client, if existing. If it does not exist, the
|
||||
container is empty and it does not matter, where we insert it */
|
||||
if (old_focused != NULL && !old_focused->dock)
|
||||
CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
|
||||
else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
|
||||
|
||||
if (new->container->workspace->fullscreen_client != NULL)
|
||||
SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
|
||||
else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
|
||||
|
||||
client_set_below_floating(conn, new);
|
||||
}
|
||||
|
||||
if (client_is_floating(new)) {
|
||||
SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
|
||||
|
||||
/* Add the client to the list of floating clients for its workspace */
|
||||
TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
|
||||
|
||||
new->container = NULL;
|
||||
|
||||
new->floating_rect.x = new->rect.x = x;
|
||||
new->floating_rect.y = new->rect.y = y;
|
||||
new->rect.width = new->floating_rect.width + 2 + 2;
|
||||
new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
|
||||
LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
|
||||
new->floating_rect.x, new->floating_rect.y,
|
||||
new->floating_rect.width, new->floating_rect.height);
|
||||
LOG("outer rect (%d, %d) size (%d, %d)\n",
|
||||
new->rect.x, new->rect.y, new->rect.width, new->rect.height);
|
||||
|
||||
/* Make sure it is on top of the other windows */
|
||||
xcb_raise_window(conn, new->frame);
|
||||
reposition_client(conn, new);
|
||||
resize_client(conn, new);
|
||||
/* redecorate_window flushes */
|
||||
redecorate_window(conn, new);
|
||||
}
|
||||
|
||||
new->initialized = true;
|
||||
|
||||
/* Check if the window already got the fullscreen hint set */
|
||||
xcb_atom_t *state;
|
||||
if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
|
||||
(state = xcb_get_property_value(preply)) != NULL)
|
||||
/* Check all set _NET_WM_STATEs */
|
||||
for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
|
||||
if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
|
||||
continue;
|
||||
/* If the window got the fullscreen state, we just toggle fullscreen
|
||||
and don’t event bother to redraw the layout – that would not change
|
||||
anything anyways */
|
||||
client_toggle_fullscreen(conn, new);
|
||||
return;
|
||||
}
|
||||
|
||||
render_layout(conn);
|
||||
}
|
67
src/resize.c
67
src/resize.c
|
@ -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;
|
||||
/* I’ve always wanted to have my own eventhandler… */
|
||||
while ((inside_event = xcb_wait_for_event(conn))) {
|
||||
/* Same as get_event_handler in xcb */
|
||||
int nr = inside_event->response_type;
|
||||
if (nr == 0) {
|
||||
/* An error occured */
|
||||
handle_event(NULL, conn, inside_event);
|
||||
free(inside_event);
|
||||
continue;
|
||||
}
|
||||
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:
|
||||
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) {
|
||||
values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_x;
|
||||
/* 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 {
|
||||
values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_y;
|
||||
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);
|
||||
}
|
||||
|
||||
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_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);
|
||||
|
|
|
@ -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]));
|
||||
}
|
||||
|
|
249
src/util.c
249
src/util.c
|
@ -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");
|
||||
if (real_strlen != NULL)
|
||||
*real_strlen = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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,19 +321,25 @@ 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 */
|
||||
if (client->container != NULL) {
|
||||
client->container->currently_focused = client;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
@ -346,14 +360,15 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
|
|||
/* If it is the same one as old_client, we save us the unnecessary redecorate */
|
||||
if ((last_focused != NULL) && (last_focused != old_client))
|
||||
redecorate_window(conn, last_focused);
|
||||
}
|
||||
|
||||
/* If we’re in stacking mode, this renders the container to update changes in the title
|
||||
bars and to raise the focused client */
|
||||
if ((old_client != NULL) && (old_client != client) && !old_client->dock)
|
||||
redecorate_window(conn, old_client);
|
||||
|
||||
SLIST_REMOVE(&(client->container->workspace->focus_stack), client, Client, focus_clients);
|
||||
SLIST_INSERT_HEAD(&(client->container->workspace->focus_stack), client, focus_clients);
|
||||
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
|
||||
SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
|
||||
|
||||
/* redecorate_window flushes, so we don’t need to */
|
||||
redecorate_window(conn, client);
|
||||
|
@ -390,7 +405,7 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode)
|
|||
/* When entering stacking mode, we need to open a window on which we can draw the
|
||||
title bars of the clients, it has height 1 because we don’t bother here with
|
||||
calculating the correct height - it will be adjusted when rendering anyways. */
|
||||
Rect rect = {container->x, container->y, container->width, 1 };
|
||||
Rect rect = {container->x, container->y, container->width, 1};
|
||||
|
||||
uint32_t mask = 0;
|
||||
uint32_t values[2];
|
||||
|
@ -429,129 +444,79 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode)
|
|||
|
||||
render_layout(conn);
|
||||
|
||||
if (container->currently_focused != NULL)
|
||||
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);
|
||||
|
||||
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
|
||||
if (client == container->currently_focused || client == last_focused)
|
||||
continue;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
set_focus(conn, container->currently_focused, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
client->fullscreen = true;
|
||||
workspace->fullscreen_client = client;
|
||||
LOG("Entering fullscreen mode...\n");
|
||||
/* We just entered fullscreen mode, let’s configure the window */
|
||||
uint32_t mask = XCB_CONFIG_WINDOW_X |
|
||||
XCB_CONFIG_WINDOW_Y |
|
||||
XCB_CONFIG_WINDOW_WIDTH |
|
||||
XCB_CONFIG_WINDOW_HEIGHT;
|
||||
uint32_t values[4] = {workspace->rect.x,
|
||||
workspace->rect.y,
|
||||
workspace->rect.width,
|
||||
workspace->rect.height};
|
||||
}
|
||||
|
||||
LOG("child itself will be at %dx%d with size %dx%d\n",
|
||||
values[0], values[1], values[2], values[3]);
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
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;
|
||||
|
||||
xcb_configure_window(conn, client->frame, mask, values);
|
||||
to_class = sstrdup(window_classtitle);
|
||||
|
||||
/* Child’s coordinates are relative to the parent (=frame) */
|
||||
values[0] = 0;
|
||||
values[1] = 0;
|
||||
xcb_configure_window(conn, client->child, mask, values);
|
||||
|
||||
/* Raise the window */
|
||||
values[0] = XCB_STACK_MODE_ABOVE;
|
||||
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
|
||||
Rect child_rect = workspace->rect;
|
||||
child_rect.x = child_rect.y = 0;
|
||||
fake_configure_notify(conn, child_rect, client->child);
|
||||
} else {
|
||||
LOG("leaving fullscreen mode\n");
|
||||
client->fullscreen = false;
|
||||
workspace->fullscreen_client = NULL;
|
||||
/* Because the coordinates of the window haven’t changed, it would not be
|
||||
re-configured if we don’t set the following flag */
|
||||
client->force_reconfigure = true;
|
||||
/* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */
|
||||
render_layout(conn);
|
||||
/* 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_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
|
||||
*
|
||||
*/
|
||||
static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
|
||||
xcb_get_property_cookie_t cookie;
|
||||
xcb_get_wm_protocols_reply_t protocols;
|
||||
bool result = false;
|
||||
|
||||
cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
|
||||
if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
|
||||
return false;
|
||||
|
||||
/* Check if the client’s protocols have the requested atom set */
|
||||
for (uint32_t i = 0; i < protocols.atoms_len; i++)
|
||||
if (protocols.atoms[i] == atom)
|
||||
result = true;
|
||||
|
||||
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 we were given a specific client we only check if that one matches */
|
||||
if (specific != NULL) {
|
||||
if (client_matches_class_name(specific, to_class, to_title, to_title_ucs, to_title_ucs_len))
|
||||
matching = specific;
|
||||
goto done;
|
||||
}
|
||||
|
||||
xcb_client_message_event_t ev;
|
||||
LOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title);
|
||||
for (int workspace = 0; workspace < 10; workspace++) {
|
||||
if (workspaces[workspace].screen == NULL)
|
||||
continue;
|
||||
|
||||
memset(&ev, 0, sizeof(xcb_client_message_event_t));
|
||||
Client *client;
|
||||
SLIST_FOREACH(client, &(workspaces[workspace].focus_stack), focus_clients) {
|
||||
LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name);
|
||||
if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len))
|
||||
continue;
|
||||
|
||||
ev.response_type = XCB_CLIENT_MESSAGE;
|
||||
ev.window = window->child;
|
||||
ev.type = atoms[WM_PROTOCOLS];
|
||||
ev.format = 32;
|
||||
ev.data.data32[0] = atoms[WM_DELETE_WINDOW];
|
||||
ev.data.data32[1] = XCB_CURRENT_TIME;
|
||||
matching = client;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
LOG("Sending WM_DELETE to the client\n");
|
||||
xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
|
||||
xcb_flush(conn);
|
||||
done:
|
||||
free(to_class);
|
||||
FREE(to_title_ucs);
|
||||
return matching;
|
||||
}
|
||||
|
|
49
src/xcb.c
49
src/xcb.c
|
@ -22,7 +22,6 @@
|
|||
#include "xcb.h"
|
||||
|
||||
TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts);
|
||||
SLIST_HEAD(colorpixel_head, Colorpixel) colorpixels;
|
||||
unsigned int xcb_numlock_mask;
|
||||
|
||||
/*
|
||||
|
@ -74,42 +73,14 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern) {
|
|||
*
|
||||
*/
|
||||
uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
|
||||
/* Lookup this colorpixel in the cache */
|
||||
struct Colorpixel *colorpixel;
|
||||
SLIST_FOREACH(colorpixel, &(colorpixels), colorpixels)
|
||||
if (strcmp(colorpixel->hex, hex) == 0)
|
||||
return colorpixel->pixel;
|
||||
|
||||
#define RGB_8_TO_16(i) (65535 * ((i) & 0xFF) / 255)
|
||||
char strgroups[3][3] = {{hex[1], hex[2], '\0'},
|
||||
{hex[3], hex[4], '\0'},
|
||||
{hex[5], hex[6], '\0'}};
|
||||
int rgb16[3] = {RGB_8_TO_16(strtol(strgroups[0], NULL, 16)),
|
||||
RGB_8_TO_16(strtol(strgroups[1], NULL, 16)),
|
||||
RGB_8_TO_16(strtol(strgroups[2], NULL, 16))};
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue