diff --git a/CMDMODE b/CMDMODE index 6b8315e2..7d8f6f23 100644 --- a/CMDMODE +++ b/CMDMODE @@ -1,36 +1,44 @@ -left := | +--------------------- +- Command mode +--------------------- + +This is the grammar for the command mode (your configuration file uses these commands, too). + +left := | right := | -up := | -down := | +up := | +down := | where := | -move := -snap := - -Eingabe ist entweder - -cmd := [ ] [ | ] - -oder - -with := { [ ] }+ - -oder +move := +snap := +cmd := [ ] [ | ] +with := { [ ] }+ +jump := [ "[/]" | [ ] ] +focus := focus [ | 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 | kill | exit | restart ] -an jeder Stelle kann mit escape abgebrochen werden +input := [ | | | | ] -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 diff --git a/DEPENDS b/DEPENDS index 6d56e132..0e1de719 100644 --- a/DEPENDS +++ b/DEPENDS @@ -5,6 +5,7 @@ In that case, please try using the versions mentioned below until a fix is provi * xcb-proto-1.3 (2008-12-10) * libxcb-1.1.93 (2008-12-11) * xcb-util-0.3.3 (2009-01-31) + * libev * asciidoc >= 8.3.0 for docs/hacking-howto * asciidoc, xmlto, docbook-xml for man/i3.man * Xlib, the one that comes with your X-Server @@ -14,9 +15,10 @@ Recommendations: * dmenu for launching applications Get the libraries from: -http://xcb.freedesktop.org/dist/xcb-proto-1.3.tar.bz2 +http://xcb.freedesktop.org/dist/xcb-proto-1.5.tar.bz2 http://xcb.freedesktop.org/dist/libxcb-1.1.93.tar.bz2 -http://xcb.freedesktop.org/dist/xcb-util-0.3.3.tar.bz2 +http://xcb.freedesktop.org/dist/xcb-util-0.3.5.tar.bz2 +http://libev.schmorp.de/ http://i3.zekjur.net/i3lock/ http://tools.suckless.org/dmenu diff --git a/Makefile b/Makefile index 9f82b1a9..a42099d4 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,7 @@ LDFLAGS += -lxcb-aux LDFLAGS += -lxcb-icccm LDFLAGS += -lxcb-xinerama LDFLAGS += -lX11 +LDFLAGS += -lev LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib ifeq ($(UNAME),NetBSD) diff --git a/RELEASE-NOTES-3.b b/RELEASE-NOTES-3.b new file mode 100644 index 00000000..43220a4d --- /dev/null +++ b/RELEASE-NOTES-3.b @@ -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 diff --git a/debian/changelog b/debian/changelog index f698b32c..17ece2ba 100644 --- a/debian/changelog +++ b/debian/changelog @@ -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 Fri, 26 Jun 2009 04:42:23 +0200 + i3-wm (3.a-bf2-1) unstable; urgency=low * Bugfix: Don't crash when setting focus diff --git a/debian/control b/debian/control index e0807987..c8834dee 100644 --- a/debian/control +++ b/debian/control @@ -1,26 +1,28 @@ Source: i3-wm Section: utils -Priority: optional +Priority: extra Maintainer: Michael Stapelberg DM-Upload-Allowed: yes -Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4-1), xmlto, docbook-xml, pkg-config -Standards-Version: 3.8.0 +Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4-1), xmlto, docbook-xml, pkg-config, libev-dev +Standards-Version: 3.8.2 Homepage: http://i3.zekjur.net/ Package: i3 Architecture: any -Priority: optional +Priority: extra Section: x11 Depends: i3-wm, ${misc:Depends} -Recommends: i3lock, dwm-tools -Description: metapackage (i3 window manager, i3lock (screen locker), dwm-tools) +Recommends: i3lock, dwm-tools, i3status +Description: metapackage (i3 window manager, screen locker, menu, statusbar) This metapackage installs the i3 window manager (i3-wm), the i3lock screen - locker (slightly improved version of slock) and dwm-tools which contains dmenu. - These are all the tools you need to use the i3 window manager efficiently. + locker (slightly improved version of slock), dwm-tools which contains dmenu + and i3status, which displays useful information about your system in + combination with dzen2. These are all the tools you need to use the i3 window + manager efficiently. Package: i3-wm Architecture: any -Priority: optional +Priority: extra Section: x11 Depends: ${shlibs:Depends}, ${misc:Depends} Provides: x-window-manager diff --git a/debian/i3-wm.docs b/debian/i3-wm.docs new file mode 100644 index 00000000..6372ffe9 --- /dev/null +++ b/debian/i3-wm.docs @@ -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 diff --git a/debian/rules b/debian/rules index 78eba656..1fee800a 100755 --- a/debian/rules +++ b/debian/rules @@ -19,6 +19,7 @@ build: # Add here commands to compile the package. $(MAKE) $(MAKE) -C man + $(MAKE) -C docs touch $@ diff --git a/docs/Makefile b/docs/Makefile index e69aefc5..123f839f 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -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} diff --git a/docs/debugging b/docs/debugging index d33f6467..d32329d4 100644 --- a/docs/debugging +++ b/docs/debugging @@ -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 diff --git a/docs/hacking-howto b/docs/hacking-howto index 72796b0c..22649c38 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -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 diff --git a/docs/single_terminal.png b/docs/single_terminal.png new file mode 100644 index 00000000..4fe918cd Binary files /dev/null and b/docs/single_terminal.png differ diff --git a/docs/snapping.png b/docs/snapping.png new file mode 100644 index 00000000..65fe6e4b Binary files /dev/null and b/docs/snapping.png differ diff --git a/docs/two_columns.png b/docs/two_columns.png new file mode 100644 index 00000000..6dc8c40c Binary files /dev/null and b/docs/two_columns.png differ diff --git a/docs/two_terminals.png b/docs/two_terminals.png new file mode 100644 index 00000000..20b45acf Binary files /dev/null and b/docs/two_terminals.png differ diff --git a/docs/userguide b/docs/userguide new file mode 100644 index 00000000..36c30d62 --- /dev/null +++ b/docs/userguide @@ -0,0 +1,371 @@ +i3 User’s Guide +=============== +Michael Stapelberg +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 +-------------------------------- + +*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 +-------------------------------------- diff --git a/i3.config b/i3.config index d2cf13a0..816b364c 100644 --- a/i3.config +++ b/i3.config @@ -1,11 +1,17 @@ # This configuration uses Mod1 and Mod3. Make sure they are mapped properly using xev(1) # and xmodmap(1). Usually, Mod1 is Alt (Alt_L) and Mod3 is Windows (Super_L) +# Tell i3 about your preferred terminal. You can refer to this as $terminal +# later. It is recommended to set this option to allow i3 to open a terminal +# containing the introduction on first start. terminal /usr/bin/urxvt # ISO 10646 = Unicode font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 +# Use Mouse+Mod1 to drag floating windows to their wanted position +floating_modifier Mod1 + # Fullscreen (Mod1+f) bind Mod1+41 f @@ -15,6 +21,13 @@ bind Mod1+43 s # Default (Mod1+e) bind Mod1+26 d +# Toggle tiling/floating of the current window (Mod1+Shift+Space) +bind Mod1+Shift+65 t + +# Go into the tiling layer / floating layer, depending on whether +# the current window is tiling / floating (Mod1+t) +bind Mod1+28 focus ft + # Focus (Mod1+j/k/l/;) bind Mod1+44 h bind Mod1+45 j diff --git a/include/client.h b/include/client.h new file mode 100644 index 00000000..85a41531 --- /dev/null +++ b/include/client.h @@ -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 + +#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 diff --git a/include/config.h b/include/config.h index 59c6866f..1e85d471 100644 --- a/include/config.h +++ b/include/config.h @@ -1,12 +1,56 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * include/config.h: Contains all structs/variables for + * the configurable part of i3 + * + */ + #ifndef _CONFIG_H #define _CONFIG_H +#include "queue.h" + typedef struct Config Config; extern Config config; +struct Colortriple { + uint32_t border; + uint32_t background; + uint32_t text; +}; + +struct Variable { + char *key; + char *value; + + SLIST_ENTRY(Variable) variables; +}; + struct Config { - const char *terminal; - const char *font; + const char *terminal; + const char *font; + + /** The modifier which needs to be pressed in combination with your mouse + * buttons to do things with floating windows (move, resize) */ + uint32_t floating_modifier; + + /* Color codes are stored here */ + struct config_client { + struct Colortriple focused; + struct Colortriple focused_inactive; + struct Colortriple unfocused; + } client; + struct config_bar { + struct Colortriple focused; + struct Colortriple unfocused; + } bar; }; /** @@ -16,6 +60,6 @@ struct Config { * configuration file. * */ -void load_configuration(const char *override_configfile); +void load_configuration(xcb_connection_t *conn, const char *override_configfile); #endif diff --git a/include/data.h b/include/data.h index 2d2f0522..b468c1f7 100644 --- a/include/data.h +++ b/include/data.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. * @@ -161,12 +161,21 @@ struct Workspace { int current_row; int current_col; + /* Should clients on this workspace be automatically floating? */ + bool auto_float; + /* Are the floating clients on this workspace currently hidden? */ + bool floating_hidden; + Client *fullscreen_client; /* The focus stack contains the clients in the correct order of focus so that the focus can be reverted correctly when a client is closed */ SLIST_HEAD(focus_stack_head, Client) focus_stack; + /* This tail queue contains the floating clients in order of when they were first + * set to floating (new floating clients are just appended) */ + TAILQ_HEAD(floating_clients_head, Client) floating_clients; + /* Backpointer to the screen this workspace is on */ i3Screen *screen; @@ -197,6 +206,30 @@ struct Binding { TAILQ_ENTRY(Binding) bindings; }; +/* + * Holds a command specified by an exec-line in the config (see src/config.c) + * + */ +struct Autostart { + /* Command, like in command mode */ + char *command; + TAILQ_ENTRY(Autostart) autostarts; +}; + +/* + * Holds an assignment for a given window class/title to a specific workspace + * (see src/config.c) + * + */ +struct Assignment { + char *windowclass_title; + /* floating is true if this was an assignment to the special workspace "~". + * Matching clients will be put into floating mode automatically. */ + bool floating; + int workspace; + TAILQ_ENTRY(Assignment) assignments; +}; + /* * Data structure for cached font information: * - font id in X11 (load it once) @@ -221,6 +254,10 @@ struct Font { * */ struct Client { + /* initialized will be set to true if the client was fully initialized by + * manage_window() and all functions can be used normally */ + bool initialized; + /* if you set a client to floating and set it back to managed, it does remember its old position and *tries* to get back there */ Cell old_position; @@ -232,6 +269,8 @@ struct Client { /* x, y, width, height of the frame */ Rect rect; + /* Position in floating mode and in tiling mode are saved separately */ + Rect floating_rect; /* x, y, width, height of the child (relative to its frame) */ Rect child_rect; @@ -255,9 +294,18 @@ struct Client { legacy window names are ignored. */ bool uses_net_wm_name; + /* Holds the WM_CLASS, useful for matching the client in commands */ + char *window_class; + /* fullscreen is pretty obvious */ bool fullscreen; + /* floating? (= not in tiling layout) This cannot be simply a bool because we want to keep track + * of whether the status was set by the application (by setting WM_CLASS to tools for example) or + * by the user. The user’s choice overwrites automatic mode, of course. The order of the values + * is important because we check with >= FLOATING_AUTO_ON if a client is floating. */ + enum { FLOATING_AUTO_OFF = 0, FLOATING_USER_OFF = 1, FLOATING_AUTO_ON = 2, FLOATING_USER_ON = 3 } floating; + /* Ensure TITLEBAR_TOP maps to 0 because we use calloc for initialization later */ enum { TITLEBAR_TOP = 0, TITLEBAR_LEFT, TITLEBAR_RIGHT, TITLEBAR_BOTTOM, TITLEBAR_OFF } titlebar_position; @@ -283,6 +331,7 @@ struct Client { CIRCLEQ_ENTRY(Client) clients; SLIST_ENTRY(Client) dock_clients; SLIST_ENTRY(Client) focus_clients; + TAILQ_ENTRY(Client) floating_clients; }; /* diff --git a/include/floating.h b/include/floating.h new file mode 100644 index 00000000..b0c0b7cc --- /dev/null +++ b/include/floating.h @@ -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 diff --git a/include/handlers.h b/include/handlers.h index b2c0de94..b4b2c99a 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -85,7 +85,15 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, * */ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state, - xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop); + xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop); + +/** + * Store the window classes for jumping to them later. + * + */ +int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, + xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop); + /** * Expose event means we should redraw our windows (= title bar) @@ -116,4 +124,14 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *reply); +/** + * Handles the transient for hints set by a window, signalizing that this window is a popup window + * for some other window. + * + * See ICCCM 4.1.2.6 for more details + * + */ +int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, + xcb_atom_t name, xcb_get_property_reply_t *reply); + #endif diff --git a/include/i3.h b/include/i3.h index 02df9996..ccf7a495 100644 --- a/include/i3.h +++ b/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 diff --git a/include/layout.h b/include/layout.h index 19a40c5d..53dfbb9d 100644 --- a/include/layout.h +++ b/include/layout.h @@ -36,6 +36,18 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw */ void redecorate_window(xcb_connection_t *conn, Client *client); +/** + * Pushes the client’s x and y coordinates to X11 + * + */ +void reposition_client(xcb_connection_t *conn, Client *client); + +/** + * Pushes the client’s width/height to X11 and resizes the child window + * + */ +void resize_client(xcb_connection_t *conn, Client *client); + /** * Renders the given container. Is called by render_layout() or individually (for example * when focus changes in a stacking container) diff --git a/include/manage.h b/include/manage.h new file mode 100644 index 00000000..52816e37 --- /dev/null +++ b/include/manage.h @@ -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 + +#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 diff --git a/include/util.h b/include/util.h index 889dcf12..3fd6b18a 100644 --- a/include/util.h +++ b/include/util.h @@ -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 diff --git a/include/xcb.h b/include/xcb.h index c6bb70b2..a8a75876 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -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 diff --git a/man/asciidoc.conf b/man/asciidoc.conf index 3854debd..cd411ec7 100644 --- a/man/asciidoc.conf +++ b/man/asciidoc.conf @@ -7,7 +7,7 @@ template::[header-declarations] {mantitle} {manvolnum} i3 -alpha +beta i3 Manual diff --git a/man/i3.man b/man/i3.man index 4f0c3fe4..cf592c62 100644 --- a/man/i3.man +++ b/man/i3.man @@ -1,7 +1,7 @@ i3(1) ===== Michael Stapelberg -v3.alpha-bf1, May 2009 +v3.beta, May 2009 == NAME @@ -9,7 +9,15 @@ i3 - an improved dynamic, tiling window manager == SYNOPSIS -i3 [-c configfile] +i3 [-c configfile] [-a] + +== OPTIONS + +-c:: +Specifies an alternate configuration file path + +-a:: +Disables autostart. == DESCRIPTION @@ -98,8 +106,17 @@ Enable stacking layout for the current container. Mod1+e:: Enable default layout for the current container. +Mod1+Shift+Space:: +Toggle tiling/floating for the current window. + +Mod1+t:: +Select the first tiling window if the current window is floating and vice-versa. + Mod1+Shift+q:: -Kills the current client. +Kills the current window. This is equivalent to "clicking on the close button", meaning a polite +request to the application to close this window. For example, Firefox will save its session +upon such a request. If the application does not support that, the window will be killed and +it depends on the application what happens. Mod1+Shift+r:: Restarts i3 in place (without losing any windows, but the layout). @@ -157,6 +174,13 @@ bind Mod1+43 s # Default (Mod1+e) bind Mod1+26 d +# Toggle tiling/floating of the current window (Mod1+Shift+Space) +bind Mod1+Shift+65 t + +# Go into the tiling layer / floating layer, depending on whether +# the current window is tiling / floating (Mod1+t) +bind Mod1+28 focus ft + # Focus (Mod1+j/k/l/;) bind Mod1+44 h bind Mod1+45 j @@ -225,6 +249,9 @@ export LC_TELEPHONE=de_DE.UTF-8 export LC_MEASUREMENT=de_DE.UTF-8 export LC_IDENTIFICATION=de_DE.UTF-8 +# Set background color +xsetroot -solid "#333333" + # Enable core dumps in case something goes wrong ulimit -c unlimited @@ -238,6 +265,14 @@ exec /usr/bin/i3 >> ~/.i3/logfile There is still lot of work to do. Please check our bugtracker for up-to-date information about tasks which are still not finished. +== SEE ALSO + +You should have a copy of the userguide (featuring nice screenshots/graphics which is why this +is not integrated into this manpage), the debugging guide and the "how to hack" guide. If you +are building from source, run +make -C docs+. + +You can also access these documents online at http://i3.zekjur.net/ + == AUTHOR Michael Stapelberg and contributors diff --git a/src/client.c b/src/client.c new file mode 100644 index 00000000..c3a80c36 --- /dev/null +++ b/src/client.c @@ -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 +#include +#include + +#include +#include + +#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); +} diff --git a/src/commands.c b/src/commands.c index c0f2192d..275f8f96 100644 --- a/src/commands.c +++ b/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 ? 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 ? */ 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 */ + /* 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 */ 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"); diff --git a/src/config.c b/src/config.c index 73d39021..d55c2a75 100644 --- a/src/config.c +++ b/src/config.c @@ -17,6 +17,7 @@ #include "i3.h" #include "util.h" #include "config.h" +#include "xcb.h" Config config; @@ -25,14 +26,36 @@ Config config; * */ static char *glob_path(const char *path) { - static glob_t globbuf; - if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) - die("glob() failed"); - char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path); - globfree(&globbuf); - return result; + static glob_t globbuf; + if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) + die("glob() failed"); + char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path); + globfree(&globbuf); + return result; } +/* + * This function does a very simple replacement of each instance of key with value. + * + */ +static void replace_variable(char *buffer, const char *key, const char *value) { + char *pos; + /* To prevent endless recursions when the user makes an error configuring, + * we stop after 100 replacements. That should be vastly more than enough. */ + int c = 0; + LOG("Replacing %s with %s\n", key, value); + while ((pos = strcasestr(buffer, key)) != NULL && c++ < 100) { + LOG("replacing variable %s in \"%s\" with \"%s\"\n", key, buffer, value); + char *rest = pos + strlen(key); + *pos = '\0'; + char *replaced; + asprintf(&replaced, "%s%s%s", buffer, value, rest); + /* Hm, this is a bit ugly, but sizeof(buffer) = 4, as it’s just a pointer. + * So we need to hard-code the dimensions here. */ + strncpy(buffer, replaced, 1026); + free(replaced); + } +} /* * Reads the configuration from ~/.i3/config or /etc/i3/config if not found. @@ -41,7 +64,9 @@ static char *glob_path(const char *path) { * configuration file. * */ -void load_configuration(const char *override_configpath) { +void load_configuration(xcb_connection_t *conn, const char *override_configpath) { + SLIST_HEAD(variables_head, Variable) variables; + #define OPTION_STRING(name) \ if (strcasecmp(key, #name) == 0) { \ config.name = sstrdup(value); \ @@ -52,9 +77,51 @@ void load_configuration(const char *override_configpath) { if (config.name == NULL) \ die("You did not specify required configuration option " #name "\n"); +#define OPTION_COLORTRIPLE(opt, name) \ + if (strcasecmp(key, opt) == 0) { \ + char border[8], background[8], text[8]; \ + memset(border, 0, sizeof(border)); \ + memset(background, 0, sizeof(background)); \ + memset(text, 0, sizeof(text)); \ + border[0] = background[0] = text[0] = '#'; \ + if (sscanf(value, "#%06[0-9a-fA-F] #%06[0-9a-fA-F] #%06[0-9a-fA-F]", \ + border + 1, background + 1, text + 1) != 3 || \ + strlen(border) != 7 || \ + strlen(background) != 7 || \ + strlen(text) != 7) \ + die("invalid color code line: %s\n", value); \ + config.name.border = get_colorpixel(conn, border); \ + config.name.background = get_colorpixel(conn, background); \ + config.name.text = get_colorpixel(conn, text); \ + continue; \ + } + /* Clear the old config or initialize the data structure */ memset(&config, 0, sizeof(config)); + SLIST_INIT(&variables); + + /* Initialize default colors */ + config.client.focused.border = get_colorpixel(conn, "#4c7899"); + config.client.focused.background = get_colorpixel(conn, "#285577"); + config.client.focused.text = get_colorpixel(conn, "#ffffff"); + + config.client.focused_inactive.border = get_colorpixel(conn, "#4c7899"); + config.client.focused_inactive.background = get_colorpixel(conn, "#555555"); + config.client.focused_inactive.text = get_colorpixel(conn, "#ffffff"); + + config.client.unfocused.border = get_colorpixel(conn, "#333333"); + config.client.unfocused.background = get_colorpixel(conn, "#222222"); + config.client.unfocused.text = get_colorpixel(conn, "#888888"); + + config.bar.focused.border = get_colorpixel(conn, "#4c7899"); + config.bar.focused.background = get_colorpixel(conn, "#285577"); + config.bar.focused.text = get_colorpixel(conn, "#ffffff"); + + config.bar.unfocused.border = get_colorpixel(conn, "#333333"); + config.bar.unfocused.background = get_colorpixel(conn, "#222222"); + config.bar.unfocused.text = get_colorpixel(conn, "#888888"); + FILE *handle; if (override_configpath != NULL) { if ((handle = fopen(override_configpath, "r")) == NULL) @@ -77,6 +144,14 @@ void load_configuration(const char *override_configpath) { die("Could not read configuration file\n"); } + if (config.terminal != NULL) + replace_variable(buffer, "$terminal", config.terminal); + + /* Replace all custom variables */ + struct Variable *current; + SLIST_FOREACH(current, &variables, variables) + replace_variable(buffer, current->key, current->value); + /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */ if (sscanf(buffer, "%s %[^\n]", key, value) < 1 || key[0] == '#' || strlen(key) < 3) @@ -85,6 +160,22 @@ void load_configuration(const char *override_configpath) { OPTION_STRING(terminal); OPTION_STRING(font); + /* Colors */ + OPTION_COLORTRIPLE("client.focused", client.focused); + OPTION_COLORTRIPLE("client.focused_inactive", client.focused_inactive); + OPTION_COLORTRIPLE("client.unfocused", client.unfocused); + OPTION_COLORTRIPLE("bar.focused", bar.focused); + OPTION_COLORTRIPLE("bar.unfocused", bar.unfocused); + + /* exec-lines (autostart) */ + if (strcasecmp(key, "exec") == 0) { + struct Autostart *new = smalloc(sizeof(struct Autostart)); + new->command = sstrdup(value); + TAILQ_INSERT_TAIL(&autostarts, new, autostarts); + continue; + } + + /* key bindings */ if (strcasecmp(key, "bind") == 0) { #define CHECK_MODIFIER(name) \ if (strncasecmp(walk, #name, strlen(#name)) == 0) { \ @@ -124,13 +215,105 @@ void load_configuration(const char *override_configpath) { continue; } - fprintf(stderr, "Unknown configfile option: %s\n", key); - exit(1); + if (strcasecmp(key, "floating_modifier") == 0) { + char *walk = value; + uint32_t modifiers = 0; + + while (*walk != '\0') { + /* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */ + CHECK_MODIFIER(SHIFT); + CHECK_MODIFIER(CONTROL); + CHECK_MODIFIER(MODE_SWITCH); + CHECK_MODIFIER(MOD1); + CHECK_MODIFIER(MOD2); + CHECK_MODIFIER(MOD3); + CHECK_MODIFIER(MOD4); + CHECK_MODIFIER(MOD5); + + /* No modifier found? Then we’re done with this step */ + break; + } + + LOG("Floating modifiers = %d\n", modifiers); + config.floating_modifier = modifiers; + continue; + } + + /* assign window class[/window title] → workspace */ + if (strcasecmp(key, "assign") == 0) { + LOG("assign: \"%s\"\n", value); + char *class_title = sstrdup(value); + char *target; + + /* If the window class/title is quoted we skip quotes */ + if (class_title[0] == '"') { + class_title++; + char *end = strchr(class_title, '"'); + if (end == NULL) + die("Malformed assignment, couldn't find terminating quote\n"); + *end = '\0'; + } else { + /* If it is not quoted, we terminate it at the first space */ + char *end = strchr(class_title, ' '); + if (end == NULL) + die("Malformed assignment, couldn't find terminating space\n"); + *end = '\0'; + } + + /* The target is the last argument separated by a space */ + if ((target = strrchr(value, ' ')) == NULL) + die("Malformed assignment, couldn't find target\n"); + target++; + + if (*target != '~' && (atoi(target) < 1 || atoi(target) > 10)) + die("Malformed assignment, invalid workspace number\n"); + + LOG("assignment parsed: \"%s\" to \"%s\"\n", class_title, target); + + struct Assignment *new = scalloc(sizeof(struct Assignment)); + new->windowclass_title = class_title; + if (*target == '~') + new->floating = true; + else new->workspace = atoi(target); + TAILQ_INSERT_TAIL(&assignments, new, assignments); + continue; + } + + /* set a custom variable */ + if (strcasecmp(key, "set") == 0) { + if (value[0] != '$') + die("Malformed variable assignment, name has to start with $\n"); + + /* get key/value for this variable */ + char *v_key = value, *v_value; + if ((v_value = strstr(value, " ")) == NULL) + die("Malformed variable assignment, need a value\n"); + + *(v_value++) = '\0'; + + struct Variable *new = scalloc(sizeof(struct Variable)); + new->key = sstrdup(v_key); + new->value = sstrdup(v_value); + SLIST_INSERT_HEAD(&variables, new, variables); + LOG("Got new variable %s = %s\n", v_key, v_value); + continue; + } + + die("Unknown configfile option: %s\n", key); } fclose(handle); REQUIRED_OPTION(terminal); REQUIRED_OPTION(font); + + while (!SLIST_EMPTY(&variables)) { + struct Variable *v = SLIST_FIRST(&variables); + SLIST_REMOVE_HEAD(&variables, variables); + free(v->key); + free(v->value); + free(v); + } + return; } diff --git a/src/floating.c b/src/floating.c new file mode 100644 index 00000000..70112b57 --- /dev/null +++ b/src/floating.c @@ -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 +#include +#include + +#include +#include + +#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); +} diff --git a/src/handlers.c b/src/handlers.c index 4c40765f..015e716a 100644 --- a/src/handlers.c +++ b/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; +} diff --git a/src/layout.c b/src/layout.c index ea9ff4f2..4f132aea 100644 --- a/src/layout.c +++ b/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++; diff --git a/src/mainx.c b/src/mainx.c index 38ae2511..d95ca465 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -31,6 +31,8 @@ #include #include +#include + #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; diff --git a/src/manage.c b/src/manage.c new file mode 100644 index 00000000..d4641dbc --- /dev/null +++ b/src/manage.c @@ -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 +#include +#include + +#include +#include + +#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); +} diff --git a/src/resize.c b/src/resize.c index 9c6cbab4..3d634420 100644 --- a/src/resize.c +++ b/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; + void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { + LOG("new x = %d, y = %d\n", new_x, new_y); + if (orientation == O_VERTICAL) { + /* Check if the new coordinates are within screen boundaries */ + if (new_x > (screen->rect.x + screen->rect.width - 25) || + new_x < (screen->rect.x + 25)) + return; + + values[0] = new_position = new_x; + xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values); + } else { + if (new_y > (screen->rect.y + screen->rect.height - 25) || + new_y < (screen->rect.y + 25)) + return; + + values[0] = new_position = new_y; + xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values); } - assert(nr < 256); - nr &= XCB_EVENT_RESPONSE_TYPE_MASK; - assert(nr >= 2); - /* Check if we need to escape this loop */ - if (nr == XCB_BUTTON_RELEASE) - break; - - switch (nr) { - case XCB_MOTION_NOTIFY: - if (orientation == O_VERTICAL) { - values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_x; - xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values); - } else { - values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_y; - xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values); - } - - xcb_flush(conn); - break; - default: - LOG("Passing to original handler\n"); - /* Use original handler */ - xcb_event_handle(&evenths, inside_event); - break; - } - free(inside_event); + xcb_flush(conn); } - xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); + drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback); + xcb_destroy_window(conn, helpwin); xcb_destroy_window(conn, grabwin); xcb_flush(conn); diff --git a/src/table.c b/src/table.c index 20b88cec..dab4b9ac 100644 --- a/src/table.c +++ b/src/table.c @@ -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])); } diff --git a/src/util.c b/src/util.c index 47d8c542..bccb9941 100644 --- a/src/util.c +++ b/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"); - *real_strlen = 0; + if (real_strlen != NULL) + *real_strlen = 0; return NULL; } - *real_strlen = ((buffer_size - output_size) / 2) - 1; + if (real_strlen != NULL) + *real_strlen = ((buffer_size - output_size) / 2) - 1; return buffer; } -/* - * Removes the given client from the container, either because it will be inserted into another - * one or because it was unmapped - * - */ -void remove_client_from_container(xcb_connection_t *conn, Client *client, Container *container) { - CIRCLEQ_REMOVE(&(container->clients), client, clients); - - SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients); - - /* If the container will be empty now and is in stacking mode, we need to - unmap the stack_win */ - if (CIRCLEQ_EMPTY(&(container->clients)) && container->mode == MODE_STACK) { - struct Stack_Window *stack_win = &(container->stack_win); - stack_win->rect.height = 0; - xcb_unmap_window(conn, stack_win->window); - } -} - /* * Returns the client which comes next in focus stack (= was selected before) for * the given container, optionally excluding the given client. @@ -270,19 +254,43 @@ void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) { /* Ignore notify events because they would cause focus to be changed */ ignore_enter_notify_forall(conn, u_ws, true); - /* Unmap all clients of the current workspace */ + /* Unmap all clients of the given workspace */ int unmapped_clients = 0; FOR_TABLE(u_ws) CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) { + LOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child); xcb_unmap_window(conn, client->frame); unmapped_clients++; } - /* If we did not unmap any clients, the workspace is empty and we can destroy it */ - if (unmapped_clients == 0) - u_ws->screen = NULL; + /* To find floating clients, we traverse the focus stack */ + SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) { + if (!client_is_floating(client)) + continue; - /* Unmap the stack windows on the current workspace, if any */ + LOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child); + + xcb_unmap_window(conn, client->frame); + unmapped_clients++; + } + + /* If we did not unmap any clients, the workspace is empty and we can destroy it, at least + * if it is not the current workspace. */ + if (unmapped_clients == 0 && u_ws != c_ws) { + /* Re-assign the workspace of all dock clients which use this workspace */ + Client *dock; + LOG("workspace %p is empty\n", u_ws); + SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) { + if (dock->workspace != u_ws) + continue; + + LOG("Re-assigning dock client to c_ws (%p)\n", c_ws); + dock->workspace = c_ws; + } + u_ws->screen = NULL; + } + + /* Unmap the stack windows on the given workspace, if any */ SLIST_FOREACH(stack_win, &stack_wins, stack_windows) if (stack_win->container->workspace == u_ws) xcb_unmap_window(conn, stack_win->window); @@ -302,7 +310,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { return; /* Store the old client */ - Client *old_client = CUR_CELL->currently_focused; + Client *old_client = SLIST_FIRST(&(c_ws->focus_stack)); /* Check if the focus needs to be changed at all */ if (!set_anyways && (old_client == client)) { @@ -313,47 +321,54 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { /* Store current_row/current_col */ c_ws->current_row = current_row; c_ws->current_col = current_col; - c_ws = client->container->workspace; + c_ws = client->workspace; + /* Load current_col/current_row if we switch to a client without a container */ + current_col = c_ws->current_col; + current_row = c_ws->current_row; /* Update container */ - client->container->currently_focused = client; + if (client->container != NULL) { + client->container->currently_focused = client; - current_col = client->container->col; - current_row = client->container->row; + current_col = client->container->col; + current_row = client->container->row; + } LOG("set_focus(frame %08x, child %08x, name %s)\n", client->frame, client->child, client->name); /* Set focus to the entered window, and flush xcb buffer immediately */ xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME); //xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10); - /* Get the client which was last focused in this particular container, it may be a different - one than old_client */ - Client *last_focused = get_last_focused_client(conn, client->container, NULL); + if (client->container != NULL) { + /* Get the client which was last focused in this particular container, it may be a different + one than old_client */ + Client *last_focused = get_last_focused_client(conn, client->container, NULL); - /* In stacking containers, raise the client in respect to the one which was focused before */ - if (client->container->mode == MODE_STACK && client->container->workspace->fullscreen_client == NULL) { - /* We need to get the client again, this time excluding the current client, because - * we might have just gone into stacking mode and need to raise */ - Client *last_focused = get_last_focused_client(conn, client->container, client); + /* In stacking containers, raise the client in respect to the one which was focused before */ + if (client->container->mode == MODE_STACK && client->container->workspace->fullscreen_client == NULL) { + /* We need to get the client again, this time excluding the current client, because + * we might have just gone into stacking mode and need to raise */ + Client *last_focused = get_last_focused_client(conn, client->container, client); - if (last_focused != NULL) { - LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child); - uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE }; - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + if (last_focused != NULL) { + LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child); + uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE }; + xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + } } - } - /* If it is the same one as old_client, we save us the unnecessary redecorate */ - if ((last_focused != NULL) && (last_focused != old_client)) - redecorate_window(conn, last_focused); + /* If it is the same one as old_client, we save us the unnecessary redecorate */ + if ((last_focused != NULL) && (last_focused != old_client)) + redecorate_window(conn, last_focused); + } /* If we’re in stacking mode, this renders the container to update changes in the title bars and to raise the focused client */ if ((old_client != NULL) && (old_client != client) && !old_client->dock) redecorate_window(conn, old_client); - SLIST_REMOVE(&(client->container->workspace->focus_stack), client, Client, focus_clients); - SLIST_INSERT_HEAD(&(client->container->workspace->focus_stack), client, focus_clients); + SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); + SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients); /* redecorate_window flushes, so we don’t need to */ redecorate_window(conn, client); @@ -390,7 +405,7 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) /* When entering stacking mode, we need to open a window on which we can draw the title bars of the clients, it has height 1 because we don’t bother here with calculating the correct height - it will be adjusted when rendering anyways. */ - Rect rect = {container->x, container->y, container->width, 1 }; + Rect rect = {container->x, container->y, container->width, 1}; uint32_t mask = 0; uint32_t values[2]; @@ -429,129 +444,79 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) render_layout(conn); - if (container->currently_focused != NULL) - set_focus(conn, container->currently_focused, true); -} + if (container->currently_focused != NULL) { + /* We need to make sure that this client is above *each* of the + * other clients in this container */ + Client *last_focused = get_last_focused_client(conn, container, container->currently_focused); -/* - * Warps the pointer into the given client (in the middle of it, to be specific), therefore - * selecting it - * - */ -void warp_pointer_into(xcb_connection_t *conn, Client *client) { - int mid_x = client->rect.width / 2, - mid_y = client->rect.height / 2; - xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y); -} + CIRCLEQ_FOREACH(client, &(container->clients), clients) { + if (client == container->currently_focused || client == last_focused) + continue; -/* - * Toggles fullscreen mode for the given client. It updates the data structures and - * reconfigures (= resizes/moves) the client and its frame to the full size of the - * screen. When leaving fullscreen, re-rendering the layout is forced. - * - */ -void toggle_fullscreen(xcb_connection_t *conn, Client *client) { - /* clients without a container (docks) cannot be focused */ - assert(client->container != NULL); - - Workspace *workspace = client->container->workspace; - - if (!client->fullscreen) { - if (workspace->fullscreen_client != NULL) { - LOG("Not entering fullscreen mode, there already is a fullscreen client.\n"); - return; + LOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame); + uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW }; + xcb_configure_window(conn, client->frame, + XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); } - client->fullscreen = true; - workspace->fullscreen_client = client; - LOG("Entering fullscreen mode...\n"); - /* We just entered fullscreen mode, let’s configure the window */ - uint32_t mask = XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT; - uint32_t values[4] = {workspace->rect.x, - workspace->rect.y, - workspace->rect.width, - workspace->rect.height}; - LOG("child itself will be at %dx%d with size %dx%d\n", - values[0], values[1], values[2], values[3]); + if (last_focused != NULL) { + LOG("Putting last_focused directly underneath the currently focused\n"); + uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW }; + xcb_configure_window(conn, last_focused->frame, + XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + } - xcb_configure_window(conn, client->frame, mask, values); - /* 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); + set_focus(conn, container->currently_focused, true); } - - xcb_flush(conn); } /* - * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW) + * Gets the first matching client for the given window class/window title. + * If the paramater specific is set to a specific client, only this one + * will be checked. * */ -static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) { - xcb_get_property_cookie_t cookie; - xcb_get_wm_protocols_reply_t protocols; - bool result = false; +Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle, + Client *specific) { + char *to_class, *to_title, *to_title_ucs = NULL; + int to_title_ucs_len = 0; + Client *matching = NULL; - cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]); - if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1) - return false; + to_class = sstrdup(window_classtitle); - /* Check if the 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 a title was specified, split both strings at the slash */ + if ((to_title = strstr(to_class, "/")) != NULL) { + *(to_title++) = '\0'; + /* Convert to UCS-2 */ + to_title_ucs = convert_utf8_to_ucs2(to_title, &to_title_ucs_len); } - xcb_client_message_event_t ev; + /* If we were given a specific client we only check if that one matches */ + if (specific != NULL) { + if (client_matches_class_name(specific, to_class, to_title, to_title_ucs, to_title_ucs_len)) + matching = specific; + goto done; + } - memset(&ev, 0, sizeof(xcb_client_message_event_t)); + LOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title); + for (int workspace = 0; workspace < 10; workspace++) { + if (workspaces[workspace].screen == NULL) + continue; - ev.response_type = XCB_CLIENT_MESSAGE; - ev.window = window->child; - ev.type = atoms[WM_PROTOCOLS]; - ev.format = 32; - ev.data.data32[0] = atoms[WM_DELETE_WINDOW]; - ev.data.data32[1] = XCB_CURRENT_TIME; + Client *client; + SLIST_FOREACH(client, &(workspaces[workspace].focus_stack), focus_clients) { + LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name); + if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) + continue; - LOG("Sending WM_DELETE to the client\n"); - xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev); - xcb_flush(conn); + matching = client; + goto done; + } + } + +done: + free(to_class); + FREE(to_title_ucs); + return matching; } diff --git a/src/xcb.c b/src/xcb.c index 10608ac4..4062f648 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -22,7 +22,6 @@ #include "xcb.h" TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts); -SLIST_HEAD(colorpixel_head, Colorpixel) colorpixels; unsigned int xcb_numlock_mask; /* @@ -74,42 +73,14 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern) { * */ uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) { - /* Lookup this colorpixel in the cache */ - struct Colorpixel *colorpixel; - SLIST_FOREACH(colorpixel, &(colorpixels), colorpixels) - if (strcmp(colorpixel->hex, hex) == 0) - return colorpixel->pixel; - - #define RGB_8_TO_16(i) (65535 * ((i) & 0xFF) / 255) char strgroups[3][3] = {{hex[1], hex[2], '\0'}, {hex[3], hex[4], '\0'}, {hex[5], hex[6], '\0'}}; - int rgb16[3] = {RGB_8_TO_16(strtol(strgroups[0], NULL, 16)), - RGB_8_TO_16(strtol(strgroups[1], NULL, 16)), - RGB_8_TO_16(strtol(strgroups[2], NULL, 16))}; + 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); +} diff --git a/src/xinerama.c b/src/xinerama.c index 818df00a..f1becf8c 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -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. diff --git a/website/screenshots/index.html b/website/screenshots/index.html index a7f95852..a46ea8eb 100644 --- a/website/screenshots/index.html +++ b/website/screenshots/index.html @@ -78,7 +78,6 @@ li {
  • i3 v3.α-bf2, mc, vim, xosview, mplayer, irssi, gajim, i3status
  • -

    Screencasts