Merge branch 'next'

This commit is contained in:
Michael Stapelberg 2015-02-28 15:02:31 +01:00
commit fe482cf193
153 changed files with 4613 additions and 4169 deletions

View File

@ -7,5 +7,4 @@ AlwaysBreakBeforeMultilineStrings: false
IndentWidth: 4 IndentWidth: 4
PointerBindsToType: false PointerBindsToType: false
ColumnLimit: 0 ColumnLimit: 0
ForEachMacros: [ TAILQ_FOREACH, TAILQ_FOREACH_REVERSE, SLIST_FOREACH, CIRCLEQ_FOREACH, CIRCLEQ_FOREACH_REVERSE, NODES_FOREACH, NODES_FOREACH_REVERSE ]
SpaceBeforeParens: ControlStatements SpaceBeforeParens: ControlStatements

20
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,20 @@
# i3status/i3lock bugreports/feature requests
Note that i3status and i3lock related bugreports and feature requests should be
filed in the corresponding repositories, i.e. https://github.com/i3/i3status
and https://github.com/i3/i3lock
# i3 bugreports/feature requests
1. Read http://i3wm.org/docs/debugging.html
2. Make sure you include a link to your logfile in your report (section 3).
3. Make sure you include the i3 version number in your report (section 1).
# Pull requests
* Before sending a pull request for new features, please check with us that the
feature is something we want to see in i3 by opening an issue which has
“feature request” or “enhancement” in its title.
* Use the `next` branch for developing and sending your pull request.
* Use `clang-format` to format your code.
* Run the testsuite, see http://i3wm.org/docs/testsuite.html

18
DEPENDS
View File

@ -4,12 +4,14 @@
"min" means minimum required version "min" means minimum required version
"lkgv" means last known good version "lkgv" means last known good version
┌─────────────┬────────┬────────┬────────────────────────────────────────┐ ┌─────────────┬────────┬────────┬────────────────────────────────────────┐
│ dependency │ min. │ lkgv │ URL │ │ dependency │ min. │ lkgv │ URL │
├─────────────┼────────┼────────┼────────────────────────────────────────┤ ├─────────────┼────────┼────────┼────────────────────────────────────────┤
│ pkg-config │ 0.25 │ 0.26 │ http://pkgconfig.freedesktop.org/ │ │ pkg-config │ 0.25 │ 0.26 │ http://pkgconfig.freedesktop.org/ │
│ libxcb │ 1.1.93 │ 1.7 │ http://xcb.freedesktop.org/dist/ │ │ libxcb │ 1.1.93 │ 1.10 │ http://xcb.freedesktop.org/dist/ │
│ xcb-util │ 0.3.3 │ 0.3.8 │ http://xcb.freedesktop.org/dist/ │ │ xcb-util │ 0.3.3 │ 0.4.1 │ http://xcb.freedesktop.org/dist/ │
│ xkbcommon │ 0.4.0 │ 0.4.0 │ http://xkbcommon.org/ │
│ xkbcommon-x11│ 0.4.0 │ 0.4.0 │ http://xkbcommon.org/ │
│ util-cursor³ │ 0.0.99 │ 0.0.99 │ http://xcb.freedesktop.org/dist/ │ │ util-cursor³ │ 0.0.99 │ 0.0.99 │ http://xcb.freedesktop.org/dist/ │
│ libev │ 4.0 │ 4.11 │ http://libev.schmorp.de/ │ │ libev │ 4.0 │ 4.11 │ http://libev.schmorp.de/ │
│ yajl │ 2.0.1 │ 2.0.4 │ http://lloyd.github.com/yajl/ │ │ yajl │ 2.0.1 │ 2.0.4 │ http://lloyd.github.com/yajl/ │
@ -22,7 +24,7 @@
│ libsn¹ │ 0.10 │ 0.12 │ http://freedesktop.org/wiki/Software/startup-notification │ libsn¹ │ 0.10 │ 0.12 │ http://freedesktop.org/wiki/Software/startup-notification
│ pango │ 1.30.0 | 1.30.0 │ http://www.pango.org/ │ │ pango │ 1.30.0 | 1.30.0 │ http://www.pango.org/ │
│ cairo │ 1.12.2 │ 1.12.2 │ http://cairographics.org/ │ │ cairo │ 1.12.2 │ 1.12.2 │ http://cairographics.org/ │
└─────────────┴────────┴────────┴────────────────────────────────────────┘ └─────────────┴────────┴────────┴────────────────────────────────────────┘
¹ libsn = libstartup-notification ¹ libsn = libstartup-notification
² Pod::Simple is a Perl module required for converting the testsuite ² Pod::Simple is a Perl module required for converting the testsuite
documentation to HTML. See http://michael.stapelberg.de/cpan/#Pod::Simple documentation to HTML. See http://michael.stapelberg.de/cpan/#Pod::Simple
@ -35,6 +37,6 @@
i3-migrate-config-to-v4 and i3-dmenu-desktop are implemented in Perl, but have i3-migrate-config-to-v4 and i3-dmenu-desktop are implemented in Perl, but have
no dependencies besides Perl 5.10. no dependencies besides Perl 5.10.
i3-save-tree is also implemented in Perl and needs AnyEvent::I3 and JSON::XS. i3-save-tree is also implemented in Perl and needs AnyEvent::I3 (>= 0.12) and
While i3-save-tree is not required for running i3 itself, it is strongly JSON::XS. While i3-save-tree is not required for running i3 itself, it is
recommended to provide it in distribution packages. strongly recommended to provide it in distribution packages.

View File

@ -1,138 +0,0 @@
┌──────────────────────────────┐
│ Release notes for i3 v4.8 │
└──────────────────────────────┘
This is i3 v4.8. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
The biggest new feature certainly is layout saving/restoring. See
http://i3wm.org/docs/layout-saving.html for more details. tl;dr: export your
current layout as JSON file, load it into new i3 sessions, get placeholder
windows that will be replaced by the actual apps once you start them.
Also very important for owners of HiDPI/“retina” displays is that i3 will now
respect your configured DPI and scale up its UI elements accordingly. Use
“xrandr --dpi 184” to set your dpi to 184, in case your setup does not figure
it out automatically. To get properly scaling fonts, we also changed the
default font from a bitmap font to a pango font (“DejaVu Sans Mono 8”).
Multiple changes improve the compatibility of i3 with other software, e.g.
java-based software (focus handling, once again) or external pagers (we now
provide _NET_CLIENT_LIST and let pager applications change workspaces).
For packagers, another change is that yajl ≥ 2.0 is now required for compiling
i3. This should not be a problem for anyone, as that version is pretty old by
now.
For contributors, note that we have starting formatting the source code with
clang-format-3.5. This means that there will no longer be a need to argue about
coding style when discussing patches :).
┌────────────────────────────┐
│ Changes in v4.8 │
└────────────────────────────┘
• docs/ipc: reformat/update list of ipc libraries
• docs/ipc: fix current_workspace outputs reply member
• docs/ipc: update ipc COMMAND reply docs
• docs/userguide: fix multiple typos
• docs/debugging: use bzip2
• docs/debugging: explain how to enable logging on the fly
• docs/debugging: merge the debug symbols/backtrace section
• docs/debugging: recommend i3 --moreversion
• man/i3-nagbar.man: update manpage to document all options
• i3bar: Amend status line error 127 message
• i3bar: dont kill watcher on EOF, leads to better error messages
• i3bar: send mouse wheel events to child too
• i3bar: do click handling and tray padding retina-correctly
• i3bar: render separators render-correctly
• i3bar: reinit colors on barconfig update
• i3bar: Don't start child unless status_command
• i3bar: implement custom workspace numbers config
• resize floating windows when right-clicking the decoration
• enable shmlog when invoked as i3-with-shmlog
• Disable pointer warps when focus_follows_mouse is disabled
• Movement into a branch considers movement direction
• set ewmh desktop properties on startup
• handle ButtonPress events with child != XCB_NONE
• implement layout restoring
• only LOG() the DPI when it changes, DLOG() it otherwise
• send IPC window events for focus and title changes
• these types of windows are now floating by default:
dialog, utility, toolbar and splash windows, modal windows, windows with an
equal minimum and maximum size
• send last event timestamp with WM_TAKE_FOCUS message
• maintain the _NET_CLIENT_LIST property
• dont set input focus _and_ send WM_TAKE_FOCUS
• respect CFLAGS in linking command
• fix parallel make
• reset SIGPIPE handler before executing a command
• render default window border width retina-correctly
• draw workspace buttons and padded text blocks retina-correctly
• render resize windows retina-correctly
• delegate click handling to dock clients
• send complete config on barconfig_update
• implement the window::fullscreen_mode ipc event
• make all workspaces starting with "__" internal
• improve error messages for i3-internal workspace names
• allow _NET_ACTIVE_WINDOW requests to switch workspaces if they indicate
that they are a pager (following the spec)
• workspace assignments by number
• add configuration option for disabling mouse warping
• set _NET_ACTIVE_WINDOW to None when none has focus
• set X-LightDM-DesktopName in i3.xsession.desktop to fix autostart on Ubuntu
• dont ELOG ipc EOF
• replace all printf()s with D?LOG
• delete ipc socket when exiting, cleanup tmpdir
• default config: switch to DejaVu Sans Mono 8 as default font
• cleanup tmpdir when restarting and not using XDG_RUNTIME_DIR
• Snap pointer to resize bar on drag resize
• Size resizebar according to container size
• Fix clang -Wextra except -Wunused-parameter
• Respect Motif hint for window decorations
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• create con pixmaps when not needed
• i3bar: fix resource leak: statusline_ctx needs to be freed first
• tree_split should not split floating cons
• fix memory leak with ipc_receive_message
• fix invalid reads by setting con->window to NULL in tree_close
• fix memory leak when closing windows
• fix memory leak when matching window by criteria
• fix memory leak when matching window by con_id
• ignore dock clients in the resize command
• clear wm_size_hints if they are not set
• resize window check should check for NULL
• fix window event crash with no window
• i3-dmenu-desktop: also quote the %c field code
• new_window and new_float can now be used simultaneously with different
border widths
• fix crash when using multiple for_window statements that move windows
• Set input focus with last timestamp
• handle windows whose WM_TRANSIENT_FOR points to themselve
• dont overwrite the original size of floating windows when changing border
• dont errnously render floating fullscreen windows during restart
• ensure floating windows dont drop out of fullscreen when restarting
• dont overwrite the windows geometry after restartingnext
• i3bar: Set `mapped` flag on trayclient creation
• i3bar: don't show "EOF" status line error
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
Aleksi Blinnikka, Alexander Berntsen, Alexander Kedrik, Antonio, Arun
Persaud, Atte Peltomaki, bo, Campbell Barton, chris, David Coppa, eeemsi,
Holger Langenau, Jean-Philippe Ouellet, Jens, jeroentbt, Jonas Maaskola,
Julian Ospald, Kernc, Koston, lasers, lkraav, Marcin, Marco Hunsicker,
Marcus Crestani, Matthias Thubauville, Maxime, Michael Stapelberg, Peter
Boström, Petr Písař, Quentin Glidic, Steve Jones, TonyC, Tony Crisci,
Vivien Didelot, Wieland Hoffmann, x33a, xeen
-- Michael Stapelberg, 2014-06-15

124
RELEASE-NOTES-4.9 Normal file
View File

@ -0,0 +1,124 @@
┌──────────────────────────────┐
│ Release notes for i3 v4.9 │
└──────────────────────────────┘
This is i3 v4.9. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
Notable new features include mouse button bindings and improved EWMH
compatibility, meaning more external pager programs work with i3 now.
Aside from that, this release contains plenty of bugfixes and little
enhancements.
The new dependency on libxkbcommon ≥ 0.4.0 is notable for distribution
packages. This dependency allowed us to drop our last direct dependency
on Xlib :).
Its also worth mentioning that all i3 repositories are now on GitHub, see
http://thread.gmane.org/gmane.comp.window-managers.i3.general/1666 for the
announcement.
┌────────────────────────────┐
│ Changes in v4.9 │
└────────────────────────────┘
• docs/ipc: use an actual event type
• docs/debugging: use logs.i3wm.org
• docs/testsuite: add hint to use xvfb-run
• testcases: use Xephyr instead of XDummy
• i3-sensible-*: use command -v (built-in) instead of which(1)
• i3.xsession.desktop: set DesktopNames (which gdm uses)
• i3-save-tree: interpret commandline parameters as utf-8
• i3-save-tree: add 'mark' as allowed key to i3-save-tree output
• i3bar-protocol: ensure align = left is the default
• i3bar: implement custom mouse wheel commands
• i3bar: improve error message when a full_text property is missing
• i3bar: respect the urgency flag on status blocks
• i3bar: inset the urgent background of a status block for consistency with
workspace buttons
• i3bar: suspend the child when bars are fully obscured
• i3bar: use Pango markup
• ipc: implement the window::close event
• ipc: implement the window::move event
• ipc: implement the window::floating event
• ipc: implement the window::urgent event
• ipc: set ws reply "num" member to -1 when named
• ipc: add deco_rect property to con in ipc response
• ipc: include workspace con in workspace event
• ewmh: implement property _NET_NUMBER_OF_DESKTOPS
• ewmh: implement property _NET_DESKTOP_VIEWPORT
• ewmh: implement property _NET_DESKTOP_NAMES
• ewmh: handle _NET_CURRENT_DESKTOP requests
• ewmh: handle _NET_CLOSE_WINDOW requests
• ewmh: handle _NET_WM_MOVERESIZE requests
• implement mouse bindings (e.g. bindsym button3 kill)
• add mouse binding --whole-window flag
• add mouse binding --release flag
• switch to xcb-xkb and libxkbcommon, removing our last direct Xlib dep
• make “move [direction]” work with criteria
• make “move <window|container> to <absolute> position” work with criteria
• “workspace <n>” and “move to workspace <n>” now look for a workspace
starting with number <n> (unless there is a workspace exactly matching that
number). I.e., “workspace 4” will go to a workspace called “4: www” unless
you have a workspace “4”
• “focus <direction>” now focuses floating containers when there are no
tiling containers on the destination output
• take the motif border into account when calculating floating window
geometry
• revert “Disable pointer warps when focus_follows_mouse is disabled” as it
was unexpected by a number of users. Sorry for the back-and-forth
• handle WM_CLASS changes
• raise floating windows on “focus <direction>”
• align lower line of bar decoration to border width
• parse tray_output as a word, not string
• allow to validate the config file without X
• do not resend focus on click, fixes compatibility problems with some wine
or mono apps (e.g. Office 2010)
• don't draw borders wider than actual width
• prevent workspace change during global fullscreen
• extend the fullscreen command (fullscreen <enable|toggle|disable> [global])
• fix start_application() doc about which shell is used
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• i3-dmenu-desktop: quote path
• i3bar: fix a double free when changing color configuration
• i3bar: render bars after the first chunk of JSON
• i3bar: add a sync call to confirm reparents before exiting (fixes tray
restart issues)
• i3bar: correctly calculate clicks on i3bar status blocks
• i3bar: make click events on status blocks work with 'workspace_buttons no'
• retina support: convert logical to physical pixels for default_border_width
• retina support: treat everything up to 120 dpi as 96 dpi
• dont set input focus if not accepted (fixes problems with xfce4-notifyd)
• dont focus unmapped container on manage
• create the directory for storing the restart state
• avoid changing border width when changing containers from tiling to
floating
• layout saving: properly restore workspace containers
• rerender the decoration when the container requires a pixmap and doesnt
have one
• dont set focus in con_set_layout() on invisible workspaces
• properly handle windows unsetting WM_TRANSIENT_FOR
• use the command parser to properly extract workspace names
• copy binding before run (fixes reloads)
• revert "Bugfix: Set input focus with last timestamp"
• render floating windows during global fullscreen
• actually parse client.placeholder
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
Alexander Monakov, aszlig, cornerman, dmurph, Mats, dsargrad, hercek, hjem,
Ingo, Ingo Bürk, Janus, javier, jefvel, Lukas K, Marein Konings, Mats,
Michael Stapelberg, Mii, nikolaus, okraits, Peter, smlb, sur5r, Tony Crisci,
val, vals, xeen, Yves-Alexis
-- Michael Stapelberg, 2015-02-28

View File

@ -92,6 +92,7 @@ else
XCB_CFLAGS += $(call cflags_for_lib, xcb-util) XCB_CFLAGS += $(call cflags_for_lib, xcb-util)
XCB_LIBS += $(call ldflags_for_lib, xcb-util) XCB_LIBS += $(call ldflags_for_lib, xcb-util)
endif endif
XCB_XKB_LIBS := $(call ldflags_for_lib, xcb-xkb,xcb-xkb)
# XCB keyboard stuff # XCB keyboard stuff
XCB_KBD_CFLAGS := $(call cflags_for_lib, xcb-keysyms) XCB_KBD_CFLAGS := $(call cflags_for_lib, xcb-keysyms)
@ -105,9 +106,10 @@ XCB_WM_LIBS := $(call ldflags_for_lib, xcb-icccm,xcb-icccm)
XCB_WM_LIBS += $(call ldflags_for_lib, xcb-xinerama,xcb-xinerama) XCB_WM_LIBS += $(call ldflags_for_lib, xcb-xinerama,xcb-xinerama)
XCB_WM_LIBS += $(call ldflags_for_lib, xcb-randr,xcb-randr) XCB_WM_LIBS += $(call ldflags_for_lib, xcb-randr,xcb-randr)
# Xlib XKB_COMMON_CFLAGS := $(call cflags_for_lib, xkbcommon,xkbcommon)
X11_CFLAGS := $(call cflags_for_lib, x11) XKB_COMMON_LIBS := $(call ldflags_for_lib, xkbcommon,xkbcommon)
X11_LIBS := $(call ldflags_for_lib, x11,X11) XKB_COMMON_X11_CFLAGS := $(call cflags_for_lib, xkbcommon-x11,xkbcommon-x11)
XKB_COMMON_X11_LIBS := $(call ldflags_for_lib, xkbcommon-x11,xkbcommon-x11)
# Xcursor # Xcursor
XCURSOR_CFLAGS := $(call cflags_for_lib, xcb-cursor) XCURSOR_CFLAGS := $(call cflags_for_lib, xcb-cursor)

19
debian/changelog vendored
View File

@ -1,8 +1,23 @@
i3-wm (4.7.3-1) unstable; urgency=low i3-wm (4.8.1-1) unstable; urgency=medium
* NOT YET RELEASED * NOT YET RELEASED
-- Michael Stapelberg <stapelberg@debian.org> Thu, 23 Jan 2014 23:11:48 +0100 -- Michael Stapelberg <stapelberg@debian.org> Sun, 15 Jun 2014 19:37:32 +0200
i3-wm (4.8-2) unstable; urgency=medium
* Backport two bugfixes:
- backport-dpi-fix.patch (Closes: #778460)
- backport-i3bar-tray-fix.patch (Closes: #778461)
-- Michael Stapelberg <stapelberg@debian.org> Sun, 15 Feb 2015 13:24:42 +0100
i3-wm (4.8-1) unstable; urgency=medium
* New upstream release.
* Bump standards-version to 3.9.5 (no changes necessary)
-- Michael Stapelberg <stapelberg@debian.org> Sun, 15 Jun 2014 19:15:29 +0200
i3-wm (4.7.2-1) unstable; urgency=low i3-wm (4.7.2-1) unstable; urgency=low

7
debian/control vendored
View File

@ -10,6 +10,9 @@ Build-Depends: debhelper (>= 7.0.50~),
libxcb-randr0-dev, libxcb-randr0-dev,
libxcb-icccm4-dev, libxcb-icccm4-dev,
libxcb-cursor-dev, libxcb-cursor-dev,
libxcb-xkb-dev,
libxkbcommon-dev (>= 0.4.0),
libxkbcommon-x11-dev (>= 0.4.0),
asciidoc (>= 8.4.4), asciidoc (>= 8.4.4),
xmlto, xmlto,
docbook-xml, docbook-xml,
@ -21,7 +24,7 @@ Build-Depends: debhelper (>= 7.0.50~),
libcairo2-dev, libcairo2-dev,
libpango1.0-dev, libpango1.0-dev,
libpod-simple-perl libpod-simple-perl
Standards-Version: 3.9.4 Standards-Version: 3.9.5
Homepage: http://i3wm.org/ Homepage: http://i3wm.org/
Package: i3 Package: i3
@ -38,7 +41,7 @@ Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, x11-utils Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, x11-utils
Provides: x-window-manager Provides: x-window-manager
Suggests: rxvt-unicode | x-terminal-emulator Suggests: rxvt-unicode | x-terminal-emulator
Recommends: xfonts-base, fonts-dejavu-core, libanyevent-i3-perl, libjson-xs-perl Recommends: xfonts-base, fonts-dejavu-core, libanyevent-i3-perl (>= 0.12), libjson-xs-perl
Description: improved dynamic tiling window manager Description: improved dynamic tiling window manager
Key features of i3 are good documentation, reasonable defaults (changeable in Key features of i3 are good documentation, reasonable defaults (changeable in
a simple configuration file) and good multi-monitor support. The user a simple configuration file) and good multi-monitor support. The user

View File

@ -9,4 +9,5 @@ man/i3-sensible-pager.1
man/i3-sensible-editor.1 man/i3-sensible-editor.1
man/i3-sensible-terminal.1 man/i3-sensible-terminal.1
man/i3-dmenu-desktop.1 man/i3-dmenu-desktop.1
man/i3-save-tree.1
man/i3bar.1 man/i3bar.1

View File

@ -1,20 +0,0 @@
Description: list x-terminal-emulator as one of i3-sensible-terminals choices
Author: Michael Stapelberg <stapelberg@debian.org>
Origin: vendor
Forwarded: not-needed
Last-Update: 2011-12-28
---
Index: i3-4.1.1/man/i3-sensible-terminal.man
===================================================================
--- i3-4.1.1.orig/man/i3-sensible-terminal.man 2011-12-28 23:56:55.487581000 +0100
+++ i3-4.1.1/man/i3-sensible-terminal.man 2011-12-28 23:57:06.725802633 +0100
@@ -22,6 +22,7 @@
It tries to start one of the following (in that order):
* $TERMINAL (this is a non-standard variable)
+* x-terminal-emulator
* urxvt
* rxvt
* terminator

View File

@ -1,2 +0,0 @@
use-x-terminal-emulator.patch
manpage-x-terminal-emulator.patch

View File

@ -1,25 +0,0 @@
Description: i3-sensible-terminal: try x-terminal-emulator first
Author: Michael Stapelberg <stapelberg@debian.org>
Origin: vendor
Forwarded: not-needed
Last-Update: 2012-09-19
---
Index: i3-4.3/i3-sensible-terminal
===================================================================
--- i3-4.3.orig/i3-sensible-terminal 2012-09-19 18:08:09.000000000 +0200
+++ i3-4.3/i3-sensible-terminal 2012-09-19 18:32:06.393883488 +0200
@@ -4,11 +4,7 @@
#
# This script tries to exec a terminal emulator by trying some known terminal
# emulators.
-#
-# Distributions/packagers should enhance this script with a
-# distribution-specific mechanism to find the preferred terminal emulator. On
-# Debian, there is the x-terminal-emulator symlink for example.
-for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do
+for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do
if which $terminal > /dev/null 2>&1; then
exec $terminal "$@"
fi

View File

@ -72,15 +72,17 @@ i3-msg 'debuglog on; shmlog on; reload'
No matter whether i3 misbehaved in some way without crashing or whether it just No matter whether i3 misbehaved in some way without crashing or whether it just
crashed, the logfile provides all information necessary to debug the problem. crashed, the logfile provides all information necessary to debug the problem.
To save a compressed version of the logfile (suitable for attaching it to a To upload a compressed version of the logfile (for a bugreport), use:
bugreport), use: ------------------------------------------------------------------------------
-------------------------------------------------------------------- DISPLAY=:0 i3-dump-log | bzip2 -c | curl --data-binary @- http://logs.i3wm.org
DISPLAY=:0 i3-dump-log | bzip2 -c > /tmp/i3.log.bz2 ------------------------------------------------------------------------------
--------------------------------------------------------------------
This command does not depend on i3 (it also works while i3 displays This command does not depend on i3 (it also works while i3 displays
the crash dialog), but it requires a working X11 connection. the crash dialog), but it requires a working X11 connection.
After running it, you will get a URL to the logfile. Please include that URL in
your bug report.
== On crashes: Obtaining a backtrace == On crashes: Obtaining a backtrace
When i3 crashes, it will display a dialog stating “i3 just crashed”, offering When i3 crashes, it will display a dialog stating “i3 just crashed”, offering

View File

@ -956,10 +956,10 @@ accepted. There are a few things which we dont want to see in i3, e.g. a
command which will focus windows in an alt+tab like way. command which will focus windows in an alt+tab like way.
When working on bugfixes, please make sure you mention that you are working on When working on bugfixes, please make sure you mention that you are working on
it in the corresponding bugreport at http://bugs.i3wm.org/. In case there is no it in the corresponding bugreport at https://github.com/i3/i3/issues In case
bugreport yet, please create one. there is no bugreport yet, please create one.
After you are done, please submit your work for review at http://cr.i3wm.org/ After you are done, please submit your work for review at https://github.com/i3/i3
Do not send emails to the mailing list or any author directly, and dont submit Do not send emails to the mailing list or any author directly, and dont submit
them in the bugtracker, since all reviews should be done in public at them in the bugtracker, since all reviews should be done in public at

View File

@ -119,7 +119,8 @@ click_events::
full_text:: full_text::
The most simple block you can think of is one which just includes the The most simple block you can think of is one which just includes the
only required key, the +full_text+ key. i3bar will display the string only required key, the +full_text+ key. i3bar will display the string
value and thats it. value parsed as
https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup].
short_text:: short_text::
Where appropriate, the +short_text+ (string) entry should also be Where appropriate, the +short_text+ (string) entry should also be
provided. It will be used in case the status line needs to be shortened provided. It will be used in case the status line needs to be shortened
@ -148,7 +149,7 @@ min_width::
when you want to set a sensible minimum width regardless of which font you when you want to set a sensible minimum width regardless of which font you
are using, and at what particular size. are using, and at what particular size.
align:: align::
Align text on the +center+ (default), +right+ or +left+ of the block, when Align text on the +center+, +right+ or +left+ (default) of the block, when
the minimum width of the latter, specified by the +min_width+ key, is not the minimum width of the latter, specified by the +min_width+ key, is not
reached. reached.
name and instance:: name and instance::

View File

@ -1,7 +1,7 @@
IPC interface (interprocess communication) IPC interface (interprocess communication)
========================================== ==========================================
Michael Stapelberg <michael@i3wm.org> Michael Stapelberg <michael@i3wm.org>
February 2014 October 2014
This document describes how to interface with i3 from a separate process. This This document describes how to interface with i3 from a separate process. This
is useful for example to remote-control i3 (to write test cases for example) or is useful for example to remote-control i3 (to write test cases for example) or
@ -156,7 +156,7 @@ following properties:
num (integer):: num (integer)::
The logical number of the workspace. Corresponds to the command The logical number of the workspace. Corresponds to the command
to switch to this workspace. to switch to this workspace. For named workspaces, this will be -1.
name (string):: name (string)::
The name of this workspace (by default num+1), as changed by the The name of this workspace (by default num+1), as changed by the
user. Encoded in UTF-8. user. Encoded in UTF-8.
@ -316,6 +316,10 @@ window_rect (map)::
So, when using the +default+ layout, you will have a 2 pixel border on So, when using the +default+ layout, you will have a 2 pixel border on
each side, making the window_rect +{ "x": 2, "y": 0, "width": 632, each side, making the window_rect +{ "x": 2, "y": 0, "width": 632,
"height": 366 }+ (for example). "height": 366 }+ (for example).
deco_rect (map)::
The coordinates of the *window decoration* inside its container. These
coordinates are relative to the container and do not include the actual
client window.
geometry (map):: geometry (map)::
The original geometry the window specified when i3 mapped it. Used when The original geometry the window specified when i3 mapped it. Used when
switching a window to floating mode, for example. switching a window to floating mode, for example.
@ -613,7 +617,7 @@ you can register to an event.
*Example:* *Example:*
--------------------------------- ---------------------------------
type: SUBSCRIBE type: SUBSCRIBE
payload: [ "workspace", "focus" ] payload: [ "workspace", "output" ]
--------------------------------- ---------------------------------
@ -638,6 +642,9 @@ window (3)::
barconfig_update (4):: barconfig_update (4)::
Sent when the hidden_state or mode field in the barconfig of any bar Sent when the hidden_state or mode field in the barconfig of any bar
instance was updated and when the config is reloaded. instance was updated and when the config is reloaded.
binding (5)::
Sent when a configured command binding is triggered with the keyboard or
mouse
*Example:* *Example:*
-------------------------------------------------------------------- --------------------------------------------------------------------
@ -661,15 +668,16 @@ if ($is_event) {
This event consists of a single serialized map containing a property This event consists of a single serialized map containing a property
+change (string)+ which indicates the type of the change ("focus", "init", +change (string)+ which indicates the type of the change ("focus", "init",
"empty", "urgent"). "empty", "urgent"). A +current (object)+ property will be present with the
affected workspace whenever the type of event affects a workspace (otherwise,
it will be +null).
Moreover, when the change is "focus", an +old (object)+ and a +current When the change is "focus", an +old (object)+ property will be present with the
(object)+ properties will be present with the previous and current previous workspace. When the first switch occurs (when i3 focuses the
workspace respectively. When the first switch occurs (when i3 focuses workspace visible at the beginning) there is no previous workspace, and the
the workspace visible at the beginning) there is no previous +old+ property will be set to +null+. Also note that if the previous is empty
workspace, and the +old+ property will be set to +null+. Also note it will get destroyed when switching, but will still be present in the "old"
that if the previous is empty it will get destroyed when switching, property.
but will still be present in the "old" property.
*Example:* *Example:*
--------------------- ---------------------
@ -717,9 +725,13 @@ This event consists of a single serialized map containing a property
+change (string)+ which indicates the type of the change +change (string)+ which indicates the type of the change
* +new+ - the window has become managed by i3 * +new+ - the window has become managed by i3
* +close+ - the window has closed
* +focus+ - the window has received input focus * +focus+ - the window has received input focus
* +title+ - the window's title has changed * +title+ - the window's title has changed
* +fullscreen_mode+ - the window has entered or exited fullscreen mode * +fullscreen_mode+ - the window has entered or exited fullscreen mode
* +move+ - the window has changed its position in the tree
* +floating+ - the window has transitioned to or from floating
* +urgent+ - the window has become urgent or lost its urgent status
Additionally a +container (object)+ field will be present, which consists Additionally a +container (object)+ field will be present, which consists
of the window's parent container. Be aware that for the "new" event, the of the window's parent container. Be aware that for the "new" event, the
@ -745,6 +757,47 @@ This event consists of a single serialized map reporting on options from the
barconfig of the specified bar_id that were updated in i3. This event is the barconfig of the specified bar_id that were updated in i3. This event is the
same as a +GET_BAR_CONFIG+ reply for the bar with the given id. same as a +GET_BAR_CONFIG+ reply for the bar with the given id.
=== binding event
This event consists of a single serialized map reporting on the details of a
binding that ran a command because of user input. The +change (sring)+ field
indicates what sort of binding event was triggered (right now it will always be
+"run"+ but may be expanded in the future).
The +binding (object)+ field contains details about the binding that was run:
command (string)::
The i3 command that is configured to run for this binding.
mods (array of strings)::
The modifier keys that were configured with this binding.
input_code (integer)::
If the binding was configured with +bindcode+, this will be the key code
that was given for the binding. If the binding is a mouse binding, it will be
the number of the mouse button that was pressed. Otherwise it will be 0.
symbol (string or null)::
If this is a keyboard binding that was configured with +bindsym+, this
field will contain the given symbol. Otherwise it will be +null+.
input_type (string)::
This will be +"keyboard"+ or +"mouse"+ depending on whether or not this was
a keyboard or a mouse binding.
*Example:*
---------------------------
{
"change": "run",
"binding": {
"command": "nop",
"mods": [
"shift",
"ctrl"
],
"input_code": 0,
"symbol": "t",
"input_type": "keyboard"
}
}
---------------------------
== See also (existing libraries) == See also (existing libraries)
[[libraries]] [[libraries]]
@ -754,15 +807,14 @@ all this on your own). This list names some (if you wrote one, please let me
know): know):
C:: C::
i3 includes a headerfile +i3/ipc.h+ which provides you all constants. * i3 includes a headerfile +i3/ipc.h+ which provides you all constants.
* https://github.com/acrisci/i3ipc-glib
https://github.com/acrisci/i3ipc-glib
Go:: Go::
* https://github.com/proxypoke/i3ipc * https://github.com/proxypoke/i3ipc
JavaScript:: JavaScript::
* https://github.com/acrisci/i3ipc-gjs * https://github.com/acrisci/i3ipc-gjs
Lua:: Lua::
* https:/github.com/acrisci/i3ipc-lua * https://github.com/acrisci/i3ipc-lua
Perl:: Perl::
* https://metacpan.org/module/AnyEvent::I3 * https://metacpan.org/module/AnyEvent::I3
Python:: Python::
@ -770,4 +822,4 @@ Python::
* https://github.com/whitelynx/i3ipc (not maintained) * https://github.com/whitelynx/i3ipc (not maintained)
* https://github.com/ziberna/i3-py (not maintained) * https://github.com/ziberna/i3-py (not maintained)
Ruby:: Ruby::
http://github.com/badboy/i3-ipc * http://github.com/badboy/i3-ipc

View File

@ -74,6 +74,9 @@ client, simply called +cpan+. It comes with every Perl installation and can be
used to install the testsuite. Many users prefer to use the more modern used to install the testsuite. Many users prefer to use the more modern
+cpanminus+ instead, though (because it asks no questions and just works): +cpanminus+ instead, though (because it asks no questions and just works):
The tests additionally require +Xephyr(1)+ to run a nested X server. Install
+xserver-xephyr+ on Debian or +xorg-xserver-xephyr+ on Arch Linux.
.Installing testsuite dependencies using cpanminus (preferred) .Installing testsuite dependencies using cpanminus (preferred)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
$ cd ~/i3/testcases $ cd ~/i3/testcases
@ -102,7 +105,12 @@ more testcases. Also, it takes care of starting up a separate instance of i3
with an appropriate configuration file and creates a folder for each run with an appropriate configuration file and creates a folder for each run
containing the appropriate i3 logfile for each testcase. The latest folder can containing the appropriate i3 logfile for each testcase. The latest folder can
always be found under the symlink +latest/+. Unless told differently, it will always be found under the symlink +latest/+. Unless told differently, it will
run the tests on a separate X server instance (using the Xdummy script). run the tests on a separate X server instance (using Xephyr).
Xephyr will open a window where you can inspect the running test. You can run
the tests without an X session with Xvfb, such as with +xvfb-run
./complete-run+. This will also speed up the tests signficantly especially on
machines without a powerful video card.
.Example invocation of complete-run.pl+ .Example invocation of complete-run.pl+
--------------------------------------- ---------------------------------------
@ -146,12 +154,11 @@ $ less latest/i3-log-for-04-floating.t
If your attempt to run the tests with a bare call to ./complete-run.pl fails, try this: If your attempt to run the tests with a bare call to ./complete-run.pl fails, try this:
--------------------------------------------------- ---------------------------------------------------
$ ./complete-run.pl --parallel=1 --keep-xdummy-output $ ./complete-run.pl --parallel=1 --keep-xserver-output
--------------------------------------------------- ---------------------------------------------------
One common cause of failures is not having the X dummy server module This will show the output of Xephyr, which is the X server implementation we
installed. Under Debian and Ubuntu this is the package use for testing.
+xserver-xorg-video-dummy+.
==== IPC interface ==== IPC interface
@ -175,10 +182,9 @@ manager.
=== Filesystem structure === Filesystem structure
In the git root of i3, the testcases live in the folder +testcases+. This In the git root of i3, the testcases live in the folder +testcases+. This
folder contains the +complete-run.pl+ and +Xdummy+ scripts and a base folder contains the +complete-run.pl+ and a base configuration file which will
configuration file which will be used for the tests. The different testcases be used for the tests. The different testcases (their file extension is .t, not
(their file extension is .t, not .pl) themselves can be found in the .pl) themselves can be found in the conventionally named subfolder +t+:
conventionally named subfolder +t+:
.Filesystem structure .Filesystem structure
-------------------------------------------- --------------------------------------------
@ -197,7 +203,6 @@ conventionally named subfolder +t+:
│   │   ├── omitted for brevity │   │   ├── omitted for brevity
│   │   ├── ... │   │   ├── ...
│   │   └── 74-regress-focus-toggle.t │   │   └── 74-regress-focus-toggle.t
│   └── Xdummy
-------------------------------------------- --------------------------------------------
== Anatomy of a testcase == Anatomy of a testcase

View File

@ -91,7 +91,7 @@ To display a window in fullscreen mode or to go out of fullscreen mode again,
press +$mod+f+. press +$mod+f+.
There is also a global fullscreen mode in i3 in which the client will span all There is also a global fullscreen mode in i3 in which the client will span all
available outputs (the command is +fullscreen global+). available outputs (the command is +fullscreen toggle global+).
=== Opening other applications === Opening other applications
@ -153,6 +153,7 @@ to upgrade to a newer version of i3) you can use +$mod+Shift+r+.
=== Exiting i3 === Exiting i3
To cleanly exit i3 without killing your X server, you can use +$mod+Shift+e+. To cleanly exit i3 without killing your X server, you can use +$mod+Shift+e+.
By default, a dialog will ask you to confirm if you really want to quit.
=== Floating === Floating
@ -366,7 +367,7 @@ bindcode [--release] [Modifiers+]keycode command
*Examples*: *Examples*:
-------------------------------- --------------------------------
# Fullscreen # Fullscreen
bindsym $mod+f fullscreen bindsym $mod+f fullscreen toggle
# Restart # Restart
bindsym $mod+Shift+r restart bindsym $mod+Shift+r restart
@ -393,6 +394,41 @@ umlauts or special characters 'and' having some comfortably reachable key
bindings. For example, when typing, capslock+1 or capslock+2 for switching bindings. For example, when typing, capslock+1 or capslock+2 for switching
workspaces is totally convenient. Try it :-). workspaces is totally convenient. Try it :-).
[[mousebindings]]
=== Mouse bindings
A mouse binding makes i3 execute a command upon pressing a specific mouse
button in the scope of the clicked container (see <<command_criteria>>). You
can configure mouse bindings in a similar way to key bindings.
*Syntax*:
----------------------------------
bindsym [--release] [--whole-window] [Modifiers+]button[n] command
----------------------------------
By default, the binding will only run when you click on the titlebar of the
window. If the +--whole-window+ flag is given, it will run when any part of the
window is clicked. If the +--release+ flag is given, it will run when the mouse
button is released.
*Examples*:
--------------------------------
# The middle button over a titlebar kills the window
bindsym --release button2 kill
# The middle button and a modifer over any part of the window kills the window
bindsym --whole-window $mod+button2 kill
# The right button toggles floating
bindsym button3 floating toggle
bindsym $mod+button3 floating toggle
# The side buttons move the window around
bindsym button9 move left
bindsym button8 move right
--------------------------------
[[floating_modifier]] [[floating_modifier]]
=== The floating modifier === The floating modifier
@ -1069,6 +1105,27 @@ bar {
Available modifiers are Mod1-Mod5, Shift, Control (see +xmodmap(1)+). Available modifiers are Mod1-Mod5, Shift, Control (see +xmodmap(1)+).
=== Mouse button commands
Specifies a command to run when a button was pressed on i3bar to override the
default behavior. Currently only the mouse wheel buttons are supported. This is
useful for disabling the scroll wheel action or running scripts that implement
custom behavior for these buttons.
*Syntax*:
---------------------
wheel_up_cmd <command>
wheel_down_cmd <command>
---------------------
*Example*:
---------------------
bar {
wheel_up_cmd nop
wheel_down_cmd exec ~/.i3/scripts/custom_wheel_down
}
---------------------
=== Bar ID === Bar ID
Specifies the bar ID for the configured bar instance. If this option is missing, Specifies the bar ID for the configured bar instance. If this option is missing,
@ -1446,9 +1503,13 @@ Use +layout toggle split+, +layout stacking+, +layout tabbed+, +layout splitv+
or +layout splith+ to change the current container layout to splith/splitv, or +layout splith+ to change the current container layout to splith/splitv,
stacking, tabbed layout, splitv or splith, respectively. stacking, tabbed layout, splitv or splith, respectively.
To make the current window (!) fullscreen, use +fullscreen+, to make To make the current window (!) fullscreen, use +fullscreen enable+ (or
it floating (or tiling again) use +floating enable+ respectively +floating disable+ +fullscreen enable global+ for the global mode), to leave either fullscreen
(or +floating toggle+): mode use +fullscreen disable+, and to toggle between these two states use
+fullscreen toggle+ (or +fullscreen toggle global+).
Likewise, to make the current window floating (or tiling again) use +floating
enable+ respectively +floating disable+ (or +floating toggle+):
*Syntax*: *Syntax*:
-------------- --------------
@ -1469,7 +1530,7 @@ bindsym $mod+x layout toggle
bindsym $mod+x layout toggle all bindsym $mod+x layout toggle all
# Toggle fullscreen # Toggle fullscreen
bindsym $mod+f fullscreen bindsym $mod+f fullscreen toggle
# Toggle floating/tiling # Toggle floating/tiling
bindsym $mod+t floating toggle bindsym $mod+t floating toggle
@ -1564,6 +1625,10 @@ container to the next/previous workspace and +move container to workspace curren
See <<move_to_outputs>> for how to move a container/workspace to a different See <<move_to_outputs>> for how to move a container/workspace to a different
RandR output. RandR output.
Workspace names are parsed as
https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup]
by i3bar.
[[back_and_forth]] [[back_and_forth]]
To switch back to the previously focused workspace, use +workspace To switch back to the previously focused workspace, use +workspace
back_and_forth+; likewise, you can move containers to the previously focused back_and_forth+; likewise, you can move containers to the previously focused
@ -1585,6 +1650,7 @@ move [window|container] [to] workspace <prev|next|current>
------------------------- -------------------------
bindsym $mod+1 workspace 1 bindsym $mod+1 workspace 1
bindsym $mod+2 workspace 2 bindsym $mod+2 workspace 2
bindsym $mod+3 workspace 3:<span foreground="red">vim</span>
... ...
bindsym $mod+Shift+1 move container to workspace 1 bindsym $mod+Shift+1 move container to workspace 1

View File

@ -4,8 +4,8 @@ CLEAN_TARGETS += clean-i3-config-wizard
i3_config_wizard_SOURCES := $(wildcard i3-config-wizard/*.c) i3_config_wizard_SOURCES := $(wildcard i3-config-wizard/*.c)
i3_config_wizard_HEADERS := $(wildcard i3-config-wizard/*.h) i3_config_wizard_HEADERS := $(wildcard i3-config-wizard/*.h)
i3_config_wizard_CFLAGS = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(X11_CFLAGS) $(PANGO_CFLAGS) i3_config_wizard_CFLAGS = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(PANGO_CFLAGS) $(XKB_COMMON_CFLAGS) $(XKB_COMMON_X11_CFLAGS)
i3_config_wizard_LIBS = $(XCB_LIBS) $(XCB_KBD_LIBS) $(X11_LIBS) $(PANGO_LIBS) i3_config_wizard_LIBS = $(XCB_LIBS) $(XCB_KBD_LIBS) $(PANGO_LIBS) $(XKB_COMMON_LIBS) $(XKB_COMMON_X11_LIBS)
i3_config_wizard_OBJECTS := $(i3_config_wizard_SOURCES:.c=.o) i3_config_wizard_OBJECTS := $(i3_config_wizard_SOURCES:.c=.o)

View File

@ -43,6 +43,9 @@
#include <xcb/xcb_event.h> #include <xcb/xcb_event.h>
#include <xcb/xcb_keysyms.h> #include <xcb/xcb_keysyms.h>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-x11.h>
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/keysym.h> #include <X11/keysym.h>
#include <X11/XKBlib.h> #include <X11/XKBlib.h>
@ -83,7 +86,9 @@ static xcb_pixmap_t pixmap;
static xcb_gcontext_t pixmap_gc; static xcb_gcontext_t pixmap_gc;
static xcb_key_symbols_t *symbols; static xcb_key_symbols_t *symbols;
xcb_window_t root; xcb_window_t root;
Display *dpy; static struct xkb_keymap *xkb_keymap;
static uint8_t xkb_base_event;
static uint8_t xkb_base_error;
static void finish(); static void finish();
@ -250,12 +255,24 @@ static char *next_state(const cmdp_token *token) {
* This reduces a lot of confusion for users who switch keyboard * This reduces a lot of confusion for users who switch keyboard
* layouts from qwerty to qwertz or other slight variations of * layouts from qwerty to qwertz or other slight variations of
* qwerty (yes, that happens quite often). */ * qwerty (yes, that happens quite often). */
KeySym sym = XkbKeycodeToKeysym(dpy, keycode, 0, 0); const xkb_keysym_t *syms;
if (!keysym_used_on_other_key(sym, keycode)) int num = xkb_keymap_key_get_syms_by_level(xkb_keymap, keycode, 0, 0, &syms);
if (num == 0)
errx(1, "xkb_keymap_key_get_syms_by_level returned no symbols for keycode %d", keycode);
if (!keysym_used_on_other_key(syms[0], keycode))
level = 0; level = 0;
} }
KeySym sym = XkbKeycodeToKeysym(dpy, keycode, 0, level);
char *str = XKeysymToString(sym); const xkb_keysym_t *syms;
int num = xkb_keymap_key_get_syms_by_level(xkb_keymap, keycode, 0, level, &syms);
if (num == 0)
errx(1, "xkb_keymap_key_get_syms_by_level returned no symbols for keycode %d", keycode);
if (num > 1)
printf("xkb_keymap_key_get_syms_by_level (keycode = %d) returned %d symbolsinstead of 1, using only the first one.\n", keycode, num);
char str[4096];
if (xkb_keysym_get_name(syms[0], str, sizeof(str)) == -1)
errx(EXIT_FAILURE, "xkb_keysym_get_name(%u) failed", syms[0]);
const char *release = get_string("release"); const char *release = get_string("release");
char *res; char *res;
char *modrep = (modifiers == NULL ? sstrdup("") : sstrdup(modifiers)); char *modrep = (modifiers == NULL ? sstrdup("") : sstrdup(modifiers));
@ -642,8 +659,14 @@ static void handle_button_press(xcb_button_press_event_t *event) {
static void finish() { static void finish() {
printf("creating \"%s\"...\n", config_path); printf("creating \"%s\"...\n", config_path);
if (!(dpy = XOpenDisplay(NULL))) struct xkb_context *xkb_context;
errx(1, "Could not connect to X11");
if ((xkb_context = xkb_context_new(0)) == NULL)
errx(1, "could not create xkbcommon context");
int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn);
if ((xkb_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL)
errx(1, "xkb_x11_keymap_new_from_device failed");
FILE *kc_config = fopen(SYSCONFDIR "/i3/config.keycodes", "r"); FILE *kc_config = fopen(SYSCONFDIR "/i3/config.keycodes", "r");
if (kc_config == NULL) if (kc_config == NULL)
@ -797,6 +820,16 @@ int main(int argc, char *argv[]) {
xcb_connection_has_error(conn)) xcb_connection_has_error(conn))
errx(1, "Cannot open display\n"); errx(1, "Cannot open display\n");
if (xkb_x11_setup_xkb_extension(conn,
XKB_X11_MIN_MAJOR_XKB_VERSION,
XKB_X11_MIN_MINOR_XKB_VERSION,
0,
NULL,
NULL,
&xkb_base_event,
&xkb_base_error) != 1)
errx(EXIT_FAILURE, "Could not setup XKB extension.");
if (socket_path == NULL) if (socket_path == NULL)
socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen); socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen);

View File

@ -3,12 +3,12 @@
#include <err.h> #include <err.h>
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__); #define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
#define FREE(pointer) do { \ #define FREE(pointer) \
do { \
if (pointer != NULL) { \ if (pointer != NULL) { \
free(pointer); \ free(pointer); \
pointer = NULL; \ pointer = NULL; \
} \ } \
} \ } while (0)
while (0)
extern xcb_window_t root; extern xcb_window_t root;

View File

@ -3,13 +3,13 @@
#include <err.h> #include <err.h>
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__); #define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
#define FREE(pointer) do { \ #define FREE(pointer) \
do { \
if (pointer != NULL) { \ if (pointer != NULL) { \
free(pointer); \ free(pointer); \
pointer = NULL; \ pointer = NULL; \
} \ } \
} \ } while (0)
while (0)
#define xmacro(atom) xcb_atom_t A_##atom; #define xmacro(atom) xcb_atom_t A_##atom;
#include "atoms.xmacro" #include "atoms.xmacro"

View File

@ -92,6 +92,7 @@ my %allowed_keys = map { ($_, 1) } qw(
name name
geometry geometry
window_properties window_properties
mark
); );
sub strip_containers { sub strip_containers {

View File

@ -10,7 +10,7 @@
# Hopefully one of these is installed (no flamewars about preference please!): # Hopefully one of these is installed (no flamewars about preference please!):
for editor in $VISUAL $EDITOR nano vim vi emacs pico qe mg jed gedit mc-edit; do for editor in $VISUAL $EDITOR nano vim vi emacs pico qe mg jed gedit mc-edit; do
if which $editor > /dev/null 2>&1; then if command -v $editor > /dev/null 2>&1; then
exec $editor "$@" exec $editor "$@"
fi fi
done done

View File

@ -12,7 +12,7 @@
# We don't use 'more' because it will exit if the file is too short. # We don't use 'more' because it will exit if the file is too short.
# Worst case scenario we'll open the file in your editor. # Worst case scenario we'll open the file in your editor.
for pager in $PAGER less most w3m i3-sensible-editor; do for pager in $PAGER less most w3m i3-sensible-editor; do
if which $pager > /dev/null 2>&1; then if command -v $pager > /dev/null 2>&1; then
exec $pager "$@" exec $pager "$@"
fi fi
done done

View File

@ -5,11 +5,11 @@
# This script tries to exec a terminal emulator by trying some known terminal # This script tries to exec a terminal emulator by trying some known terminal
# emulators. # emulators.
# #
# Distributions/packagers should enhance this script with a # We welcome patches that add distribution-specific mechanisms to find the
# distribution-specific mechanism to find the preferred terminal emulator. On # preferred terminal emulator. On Debian, there is the x-terminal-emulator
# Debian, there is the x-terminal-emulator symlink for example. # symlink for example.
for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal; do
if which $terminal > /dev/null 2>&1; then if command -v $terminal > /dev/null 2>&1; then
exec $terminal "$@" exec $terminal "$@"
fi fi
done done

View File

@ -75,7 +75,7 @@ bindsym Mod1+h split h
bindsym Mod1+v split v bindsym Mod1+v split v
# enter fullscreen mode for the focused container # enter fullscreen mode for the focused container
bindsym Mod1+f fullscreen bindsym Mod1+f fullscreen toggle
# change container layout (stacked, tabbed, toggle split) # change container layout (stacked, tabbed, toggle split)
bindsym Mod1+s layout stacking bindsym Mod1+s layout stacking

View File

@ -69,7 +69,7 @@ bindcode $mod+43 split h
bindcode $mod+55 split v bindcode $mod+55 split v
# enter fullscreen mode for the focused container # enter fullscreen mode for the focused container
bindcode $mod+41 fullscreen bindcode $mod+41 fullscreen toggle
# change container layout (stacked, tabbed, toggle split) # change container layout (stacked, tabbed, toggle split)
bindcode $mod+39 layout stacking bindcode $mod+39 layout stacking

View File

@ -5,3 +5,4 @@ Exec=i3
TryExec=i3 TryExec=i3
Type=Application Type=Application
X-LightDM-DesktopName=i3 X-LightDM-DesktopName=i3
DesktopNames=i3

View File

@ -4,8 +4,8 @@ CLEAN_TARGETS += clean-i3bar
i3bar_SOURCES := $(wildcard i3bar/src/*.c) i3bar_SOURCES := $(wildcard i3bar/src/*.c)
i3bar_HEADERS := $(wildcard i3bar/include/*.h) i3bar_HEADERS := $(wildcard i3bar/include/*.h)
i3bar_CFLAGS = $(XCB_CFLAGS) $(X11_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) i3bar_CFLAGS = $(XCB_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS)
i3bar_LIBS = $(XCB_LIBS) $(X11_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) i3bar_LIBS = $(XCB_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(XCB_XKB_LIBS)
i3bar_OBJECTS := $(i3bar_SOURCES:.c=.o) i3bar_OBJECTS := $(i3bar_SOURCES:.c=.o)

View File

@ -27,6 +27,7 @@ struct rect_t {
}; };
typedef enum { typedef enum {
/* First value to make it the default. */
ALIGN_LEFT, ALIGN_LEFT,
ALIGN_CENTER, ALIGN_CENTER,
ALIGN_RIGHT ALIGN_RIGHT

View File

@ -18,10 +18,14 @@ typedef enum {
} position_t; } position_t;
/* Bar display mode (hide unless modifier is pressed or show in dock mode or always hide in invisible mode) */ /* Bar display mode (hide unless modifier is pressed or show in dock mode or always hide in invisible mode) */
typedef enum { M_DOCK = 0, M_HIDE = 1, M_INVISIBLE = 2 } bar_display_mode_t; typedef enum { M_DOCK = 0,
M_HIDE = 1,
M_INVISIBLE = 2 } bar_display_mode_t;
typedef struct config_t { typedef struct config_t {
int modifier; int modifier;
char *wheel_up_cmd;
char *wheel_down_cmd;
position_t position; position_t position;
int verbose; int verbose;
struct xcb_color_strings_t colors; struct xcb_color_strings_t colors;
@ -38,7 +42,8 @@ typedef struct config_t {
bar_display_mode_t hide_on_modifier; bar_display_mode_t hide_on_modifier;
/* The current hidden_state of the bar, which indicates whether it is hidden or shown */ /* The current hidden_state of the bar, which indicates whether it is hidden or shown */
enum { S_HIDE = 0, S_SHOW = 1 } hidden_state; enum { S_HIDE = 0,
S_SHOW = 1 } hidden_state;
} config_t; } config_t;
config_t config; config_t config;

View File

@ -40,6 +40,7 @@ struct i3_output {
char* name; /* Name of the output */ char* name; /* Name of the output */
bool active; /* If the output is active */ bool active; /* If the output is active */
bool primary; /* If it is the primary output */ bool primary; /* If it is the primary output */
bool visible; /* If the bar is visible on this output */
int ws; /* The number of the currently visible ws */ int ws; /* The number of the currently visible ws */
rect rect; /* The rect (relative to the root-win) */ rect rect; /* The rect (relative to the root-win) */

View File

@ -18,7 +18,8 @@
#define STARTS_WITH(string, len, needle) ((len >= strlen(needle)) && strncasecmp(string, needle, strlen(needle)) == 0) #define STARTS_WITH(string, len, needle) ((len >= strlen(needle)) && strncasecmp(string, needle, strlen(needle)) == 0)
/* Securely free p */ /* Securely free p */
#define FREE(p) do { \ #define FREE(p) \
do { \
if (p != NULL) { \ if (p != NULL) { \
free(p); \ free(p); \
p = NULL; \ p = NULL; \
@ -26,7 +27,8 @@
} while (0) } while (0)
/* Securely fee single-linked list */ /* Securely fee single-linked list */
#define FREE_SLIST(l, type) do { \ #define FREE_SLIST(l, type) \
do { \
type *walk = SLIST_FIRST(l); \ type *walk = SLIST_FIRST(l); \
while (!SLIST_EMPTY(l)) { \ while (!SLIST_EMPTY(l)) { \
SLIST_REMOVE_HEAD(l, slist); \ SLIST_REMOVE_HEAD(l, slist); \
@ -36,7 +38,8 @@
} while (0) } while (0)
/* Securely fee tail-queues */ /* Securely fee tail-queues */
#define FREE_TAILQ(l, type) do { \ #define FREE_TAILQ(l, type) \
do { \
type *walk = TAILQ_FIRST(l); \ type *walk = TAILQ_FIRST(l); \
while (!TAILQ_EMPTY(l)) { \ while (!TAILQ_EMPTY(l)) { \
TAILQ_REMOVE(l, TAILQ_FIRST(l), tailq); \ TAILQ_REMOVE(l, TAILQ_FIRST(l), tailq); \
@ -49,7 +52,8 @@
#undef DLOG #undef DLOG
#endif #endif
/* Use cool logging-macros */ /* Use cool logging-macros */
#define DLOG(fmt, ...) do { \ #define DLOG(fmt, ...) \
do { \
if (config.verbose) { \ if (config.verbose) { \
printf("[%s:%d] " fmt, __FILE__, __LINE__, ##__VA_ARGS__); \ printf("[%s:%d] " fmt, __FILE__, __LINE__, ##__VA_ARGS__); \
} \ } \
@ -60,6 +64,7 @@
#if defined(ELOG) #if defined(ELOG)
#undef ELOG #undef ELOG
#endif #endif
#define ELOG(fmt, ...) do { \ #define ELOG(fmt, ...) \
do { \
fprintf(stderr, "[%s:%d] ERROR: " fmt, __FILE__, __LINE__, ##__VA_ARGS__); \ fprintf(stderr, "[%s:%d] ERROR: " fmt, __FILE__, __LINE__, ##__VA_ARGS__); \
} while (0) } while (0)

View File

@ -182,21 +182,21 @@ static int stdin_boolean(void *context, int val) {
static int stdin_string(void *context, const unsigned char *val, size_t len) { static int stdin_string(void *context, const unsigned char *val, size_t len) {
parser_ctx *ctx = context; parser_ctx *ctx = context;
if (strcasecmp(ctx->last_map_key, "full_text") == 0) { if (strcasecmp(ctx->last_map_key, "full_text") == 0) {
ctx->block.full_text = i3string_from_utf8_with_length((const char *)val, len); ctx->block.full_text = i3string_from_markup_with_length((const char *)val, len);
} }
if (strcasecmp(ctx->last_map_key, "color") == 0) { if (strcasecmp(ctx->last_map_key, "color") == 0) {
sasprintf(&(ctx->block.color), "%.*s", len, val); sasprintf(&(ctx->block.color), "%.*s", len, val);
} }
if (strcasecmp(ctx->last_map_key, "align") == 0) { if (strcasecmp(ctx->last_map_key, "align") == 0) {
if (len == strlen("left") && !strncmp((const char *)val, "left", strlen("left"))) { if (len == strlen("center") && !strncmp((const char *)val, "center", strlen("center"))) {
ctx->block.align = ALIGN_LEFT; ctx->block.align = ALIGN_CENTER;
} else if (len == strlen("right") && !strncmp((const char *)val, "right", strlen("right"))) { } else if (len == strlen("right") && !strncmp((const char *)val, "right", strlen("right"))) {
ctx->block.align = ALIGN_RIGHT; ctx->block.align = ALIGN_RIGHT;
} else { } else {
ctx->block.align = ALIGN_CENTER; ctx->block.align = ALIGN_LEFT;
} }
} else if (strcasecmp(ctx->last_map_key, "min_width") == 0) { } else if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
i3String *text = i3string_from_utf8_with_length((const char *)val, len); i3String *text = i3string_from_markup_with_length((const char *)val, len);
ctx->block.min_width = (uint32_t)predict_text_width(text); ctx->block.min_width = (uint32_t)predict_text_width(text);
i3string_free(text); i3string_free(text);
} }
@ -233,7 +233,7 @@ static int stdin_end_map(void *context) {
/* Ensure we have a full_text set, so that when it is missing (or null), /* Ensure we have a full_text set, so that when it is missing (or null),
* i3bar doesnt crash and the user gets an annoying message. */ * i3bar doesnt crash and the user gets an annoying message. */
if (!new_block->full_text) if (!new_block->full_text)
new_block->full_text = i3string_from_utf8("SPEC VIOLATION (null)"); new_block->full_text = i3string_from_utf8("SPEC VIOLATION: full_text is NULL!");
if (new_block->urgent) if (new_block->urgent)
ctx->has_urgent = true; ctx->has_urgent = true;
TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks); TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
@ -304,7 +304,7 @@ static void read_flat_input(char *buffer, int length) {
buffer[length - 1] = '\0'; buffer[length - 1] = '\0';
else else
buffer[length] = '\0'; buffer[length] = '\0';
first->full_text = i3string_from_utf8(buffer); first->full_text = i3string_from_markup(buffer);
} }
static bool read_json_input(unsigned char *input, int length) { static bool read_json_input(unsigned char *input, int length) {

View File

@ -112,6 +112,20 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
return 1; return 1;
} }
if (!strcmp(cur_key, "wheel_up_cmd")) {
DLOG("wheel_up_cmd = %.*s\n", len, val);
FREE(config.wheel_up_cmd);
sasprintf(&config.wheel_up_cmd, "%.*s", len, val);
return 1;
}
if (!strcmp(cur_key, "wheel_down_cmd")) {
DLOG("wheel_down_cmd = %.*s\n", len, val);
FREE(config.wheel_down_cmd);
sasprintf(&config.wheel_down_cmd, "%.*s", len, val);
return 1;
}
if (!strcmp(cur_key, "position")) { if (!strcmp(cur_key, "position")) {
DLOG("position = %.*s\n", len, val); DLOG("position = %.*s\n", len, val);
config.position = (len == 3 && !strncmp((const char *)val, "top", strlen("top")) ? POS_TOP : POS_BOT); config.position = (len == 3 && !strncmp((const char *)val, "top", strlen("top")) ? POS_TOP : POS_BOT);

View File

@ -159,6 +159,9 @@ void got_bar_config_update(char *event) {
if (found_id == NULL) if (found_id == NULL)
return; return;
/* reconfigure the bar based on the current outputs */
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
free_colors(&(config.colors)); free_colors(&(config.colors));
/* update the configuration with the received settings */ /* update the configuration with the received settings */
@ -169,6 +172,8 @@ void got_bar_config_update(char *event) {
reconfig_windows(true); reconfig_windows(true);
} }
/* update fonts and colors */
init_xcb_late(config.fontname);
init_colors(&(config.colors)); init_colors(&(config.colors));
realloc_sl_buffer(); realloc_sl_buffer();

View File

@ -123,12 +123,12 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
/* Offset may be equal to length, in which case display the number */ /* Offset may be equal to length, in which case display the number */
params->workspaces_walk->name = (offset < len params->workspaces_walk->name = (offset < len
? i3string_from_utf8_with_length(ws_name + offset, len - offset) ? i3string_from_markup_with_length(ws_name + offset, len - offset)
: i3string_from_utf8(ws_num)); : i3string_from_markup(ws_num));
} else { } else {
/* Default case: just save the name */ /* Default case: just save the name */
params->workspaces_walk->name = i3string_from_utf8_with_length(ws_name, len); params->workspaces_walk->name = i3string_from_markup_with_length(ws_name, len);
} }
/* Save its rendered width */ /* Save its rendered width */

View File

@ -8,6 +8,7 @@
* *
*/ */
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <xcb/xkb.h>
#include <xcb/xproto.h> #include <xcb/xproto.h>
#include <xcb/xcb_aux.h> #include <xcb/xcb_aux.h>
@ -63,8 +64,7 @@ static i3Font font;
int bar_height; int bar_height;
/* These are only relevant for XKB, which we only need for grabbing modifiers */ /* These are only relevant for XKB, which we only need for grabbing modifiers */
Display *xkb_dpy; int xkb_base;
int xkb_event_base;
int mod_pressed = 0; int mod_pressed = 0;
/* Because the statusline is the same on all outputs, we have /* Because the statusline is the same on all outputs, we have
@ -117,6 +117,12 @@ int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
return 0; return 0;
} }
uint32_t get_sep_offset(struct status_block *block) {
if (!block->no_separator && block->sep_block_width > 0)
return block->sep_block_width / 2 + block->sep_block_width % 2;
return 0;
}
/* /*
* Redraws the statusline to the buffer * Redraws the statusline to the buffer
* *
@ -148,15 +154,15 @@ void refresh_statusline(void) {
block->x_offset = padding_width; block->x_offset = padding_width;
break; break;
case ALIGN_CENTER: case ALIGN_CENTER:
block->x_offset = padding_width / logical_px(2); block->x_offset = padding_width / 2;
block->x_append = padding_width / logical_px(2) + padding_width % logical_px(2); block->x_append = padding_width / 2 + padding_width % 2;
break; break;
} }
} }
/* If this is not the last block, add some pixels for a separator. */ /* If this is not the last block, add some pixels for a separator. */
if (TAILQ_NEXT(block, blocks) != NULL) if (TAILQ_NEXT(block, blocks) != NULL)
block->width += block->sep_block_width; statusline_width += block->sep_block_width;
statusline_width += block->width + block->x_offset + block->x_append; statusline_width += block->width + block->x_offset + block->x_append;
} }
@ -168,7 +174,7 @@ void refresh_statusline(void) {
realloc_sl_buffer(); realloc_sl_buffer();
/* Clear the statusline pixmap. */ /* Clear the statusline pixmap. */
xcb_rectangle_t rect = {0, 0, root_screen->width_in_pixels, font.height + logical_px(5)}; xcb_rectangle_t rect = {0, 0, root_screen->width_in_pixels, bar_height};
xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect); xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect);
/* Draw the text of each block. */ /* Draw the text of each block. */
@ -176,22 +182,41 @@ void refresh_statusline(void) {
TAILQ_FOREACH(block, &statusline_head, blocks) { TAILQ_FOREACH(block, &statusline_head, blocks) {
if (i3string_get_num_bytes(block->full_text) == 0) if (i3string_get_num_bytes(block->full_text) == 0)
continue; continue;
uint32_t fg_color;
uint32_t colorpixel = (block->color ? get_colorpixel(block->color) : colors.bar_fg); /* If this block is urgent, draw it with the defined color and border. */
set_font_colors(statusline_ctx, colorpixel, colors.bar_bg); if (block->urgent) {
draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, 1, block->width); fg_color = colors.urgent_ws_fg;
x += block->width + block->x_offset + block->x_append;
if (TAILQ_NEXT(block, blocks) != NULL && !block->no_separator && block->sep_block_width > 0) { uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
/* Draw the background */
uint32_t bg_color = colors.urgent_ws_bg;
uint32_t bg_values[] = { bg_color, bg_color };
xcb_change_gc(xcb_connection, statusline_ctx, mask, bg_values);
/* The urgent background “overshoots” by 2 px so that the text that
* is printed onto it will not be look so cut off. */
xcb_rectangle_t bg_rect = { x - logical_px(2), logical_px(1), block->width + logical_px(4), bar_height - logical_px(2) };
xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_ctx, 1, &bg_rect);
} else {
fg_color = (block->color ? get_colorpixel(block->color) : colors.bar_fg);
}
set_font_colors(statusline_ctx, fg_color, colors.bar_bg);
draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, 3, block->width);
x += block->width + block->sep_block_width + block->x_offset + block->x_append;
uint32_t sep_offset = get_sep_offset(block);
if (TAILQ_NEXT(block, blocks) != NULL && sep_offset > 0) {
/* This is not the last block, draw a separator. */ /* This is not the last block, draw a separator. */
uint32_t sep_offset = block->sep_block_width / 2 + block->sep_block_width % 2;
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH; uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH;
uint32_t values[] = {colors.sep_fg, colors.bar_bg, logical_px(1)}; uint32_t values[] = {colors.sep_fg, colors.bar_bg, logical_px(1)};
xcb_change_gc(xcb_connection, statusline_ctx, mask, values); xcb_change_gc(xcb_connection, statusline_ctx, mask, values);
xcb_poly_line(xcb_connection, XCB_COORD_MODE_ORIGIN, statusline_pm, xcb_poly_line(xcb_connection, XCB_COORD_MODE_ORIGIN, statusline_pm,
statusline_ctx, 2, statusline_ctx, 2,
(xcb_point_t[]) {{x - sep_offset, 2}, (xcb_point_t[]) { { x - sep_offset, logical_px(4) },
{x - sep_offset, font.height - 2}}); { x - sep_offset, bar_height - logical_px(4) } });
} }
} }
} }
@ -331,22 +356,31 @@ void handle_button(xcb_button_press_event_t *event) {
continue; continue;
tray_width += (font.height + logical_px(2)); tray_width += (font.height + logical_px(2));
} }
if (tray_width > 0)
tray_width += logical_px(2);
int block_x = 0, last_block_x; int block_x = 0, last_block_x;
int offset = (walk->rect.w - (statusline_width + tray_width)) - logical_px(10); int offset = walk->rect.w - statusline_width - tray_width - logical_px(4);
x = original_x - offset; x = original_x - offset;
if (x >= 0) { if (x >= 0) {
struct status_block *block; struct status_block *block;
int sep_offset_remainder = 0;
TAILQ_FOREACH (block, &statusline_head, blocks) { TAILQ_FOREACH (block, &statusline_head, blocks) {
if (i3string_get_num_bytes(block->full_text) == 0)
continue;
last_block_x = block_x; last_block_x = block_x;
block_x += block->width + block->x_offset + block->x_append; block_x += block->width + block->x_offset + block->x_append
+ get_sep_offset(block) + sep_offset_remainder;
if (x <= block_x && x >= last_block_x) { if (x <= block_x && x >= last_block_x) {
send_block_clicked(event->detail, block->name, block->instance, event->root_x, event->root_y); send_block_clicked(event->detail, block->name, block->instance, event->root_x, event->root_y);
return; return;
} }
sep_offset_remainder = block->sep_block_width - get_sep_offset(block);
} }
} }
x = original_x; x = original_x;
@ -370,6 +404,14 @@ void handle_button(xcb_button_press_event_t *event) {
* If there is no more workspace, dont even send the workspace * If there is no more workspace, dont even send the workspace
* command, otherwise (with workspace auto_back_and_forth) wed end * command, otherwise (with workspace auto_back_and_forth) wed end
* up on the wrong workspace. */ * up on the wrong workspace. */
/* If `wheel_up_cmd [COMMAND]` was specified, it should override
* the default behavior */
if (config.wheel_up_cmd) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, config.wheel_up_cmd);
return;
}
if (cur_ws == TAILQ_FIRST(walk->workspaces)) if (cur_ws == TAILQ_FIRST(walk->workspaces))
return; return;
@ -380,6 +422,14 @@ void handle_button(xcb_button_press_event_t *event) {
* If there is no more workspace, dont even send the workspace * If there is no more workspace, dont even send the workspace
* command, otherwise (with workspace auto_back_and_forth) wed end * command, otherwise (with workspace auto_back_and_forth) wed end
* up on the wrong workspace. */ * up on the wrong workspace. */
/* if `wheel_down_cmd [COMMAND]` was specified, it should override
* the default behavior */
if (config.wheel_down_cmd) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, config.wheel_down_cmd);
return;
}
if (cur_ws == TAILQ_LAST(walk->workspaces, ws_head)) if (cur_ws == TAILQ_LAST(walk->workspaces, ws_head))
return; return;
@ -446,6 +496,39 @@ void handle_button(xcb_button_press_event_t *event) {
free(buffer); free(buffer);
} }
/*
* Handle visibility notifications: when none of the bars are visible, e.g.
* if windows are in full-screen on each output, suspend the child process.
*
*/
static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
bool visible = (event->state != XCB_VISIBILITY_FULLY_OBSCURED);
int num_visible = 0;
i3_output *output;
SLIST_FOREACH (output, outputs, slist) {
if (!output->active) {
continue;
}
if (output->bar == event->window) {
if (output->visible == visible) {
return;
}
output->visible = visible;
}
num_visible += output->visible;
}
if (num_visible == 0) {
stop_child();
} else if (num_visible == visible) {
/* Wake the child only when transitioning from 0 to 1 visible bar.
* We cannot transition from 0 to 2 or more visible bars at once since
* visibility events are delivered to each window separately */
cont_child();
}
}
/* /*
* Adjusts the size of the tray window and alignment of the tray clients by * Adjusts the size of the tray window and alignment of the tray clients by
* configuring their respective x coordinates. To be called when mapping or * configuring their respective x coordinates. To be called when mapping or
@ -854,7 +937,66 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
} }
while ((event = xcb_poll_for_event(xcb_connection)) != NULL) { while ((event = xcb_poll_for_event(xcb_connection)) != NULL) {
switch (event->response_type & ~0x80) { int type = (event->response_type & ~0x80);
if (type == xkb_base && xkb_base > -1) {
DLOG("received an xkb event\n");
xcb_xkb_state_notify_event_t *state = (xcb_xkb_state_notify_event_t *)event;
if (state->xkbType == XCB_XKB_STATE_NOTIFY) {
int modstate = state->mods & config.modifier;
#define DLOGMOD(modmask, status) \
do { \
switch (modmask) { \
case ShiftMask: \
DLOG("ShiftMask got " #status "!\n"); \
break; \
case ControlMask: \
DLOG("ControlMask got " #status "!\n"); \
break; \
case Mod1Mask: \
DLOG("Mod1Mask got " #status "!\n"); \
break; \
case Mod2Mask: \
DLOG("Mod2Mask got " #status "!\n"); \
break; \
case Mod3Mask: \
DLOG("Mod3Mask got " #status "!\n"); \
break; \
case Mod4Mask: \
DLOG("Mod4Mask got " #status "!\n"); \
break; \
case Mod5Mask: \
DLOG("Mod5Mask got " #status "!\n"); \
break; \
} \
} while (0)
if (modstate != mod_pressed) {
if (modstate == 0) {
DLOGMOD(config.modifier, released);
if (!activated_mode)
hide_bars();
} else {
DLOGMOD(config.modifier, pressed);
activated_mode = false;
unhide_bars();
}
mod_pressed = modstate;
}
#undef DLOGMOD
}
free(event);
continue;
}
switch (type) {
case XCB_VISIBILITY_NOTIFY:
/* Visibility change: a bar is [un]obscured by other window */
handle_visibility_notify((xcb_visibility_notify_event_t *)event);
break;
case XCB_EXPOSE: case XCB_EXPOSE:
/* Expose-events happen, when the window needs to be redrawn */ /* Expose-events happen, when the window needs to be redrawn */
redraw_bars(); redraw_bars();
@ -900,76 +1042,6 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
void xcb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { void xcb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
} }
/*
* We need to bind to the modifier per XKB. Sadly, XCB does not implement this
*
*/
void xkb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
XkbEvent ev;
int modstate = 0;
DLOG("Got XKB-Event!\n");
while (XPending(xkb_dpy)) {
XNextEvent(xkb_dpy, (XEvent *)&ev);
if (ev.type != xkb_event_base) {
ELOG("No Xkb-Event!\n");
continue;
}
if (ev.any.xkb_type != XkbStateNotify) {
ELOG("No State Notify!\n");
continue;
}
unsigned int mods = ev.state.mods;
modstate = mods & config.modifier;
}
#define DLOGMOD(modmask, status) \
do { \
switch (modmask) { \
case ShiftMask: \
DLOG("ShiftMask got " #status "!\n"); \
break; \
case ControlMask: \
DLOG("ControlMask got " #status "!\n"); \
break; \
case Mod1Mask: \
DLOG("Mod1Mask got " #status "!\n"); \
break; \
case Mod2Mask: \
DLOG("Mod2Mask got " #status "!\n"); \
break; \
case Mod3Mask: \
DLOG("Mod3Mask got " #status "!\n"); \
break; \
case Mod4Mask: \
DLOG("Mod4Mask got " #status "!\n"); \
break; \
case Mod5Mask: \
DLOG("Mod5Mask got " #status "!\n"); \
break; \
} \
} while (0)
if (modstate != mod_pressed) {
if (modstate == 0) {
DLOGMOD(config.modifier, released);
if (!activated_mode)
hide_bars();
} else {
DLOGMOD(config.modifier, pressed);
activated_mode = false;
unhide_bars();
}
mod_pressed = modstate;
}
#undef DLOGMOD
}
/* /*
* Early initialization of the connection to X11: Everything which does not * Early initialization of the connection to X11: Everything which does not
* depend on 'config'. * depend on 'config'.
@ -1053,44 +1125,23 @@ char *init_xcb_early() {
* *
*/ */
void register_xkb_keyevents() { void register_xkb_keyevents() {
if (xkb_dpy == NULL) { const xcb_query_extension_reply_t *extreply;
int xkb_major, xkb_minor, xkb_errbase, xkb_err; extreply = xcb_get_extension_data(conn, &xcb_xkb_id);
xkb_major = XkbMajorVersion; if (!extreply->present) {
xkb_minor = XkbMinorVersion; ELOG("xkb is not present on this server\n");
xkb_dpy = XkbOpenDisplay(NULL,
&xkb_event_base,
&xkb_errbase,
&xkb_major,
&xkb_minor,
&xkb_err);
if (xkb_dpy == NULL) {
ELOG("No XKB!\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
DLOG("initializing xcb-xkb\n");
if (fcntl(ConnectionNumber(xkb_dpy), F_SETFD, FD_CLOEXEC) == -1) { xcb_xkb_use_extension(conn, XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION);
ELOG("Could not set FD_CLOEXEC on xkbdpy: %s\n", strerror(errno)); xcb_xkb_select_events(conn,
exit(EXIT_FAILURE); XCB_XKB_ID_USE_CORE_KBD,
} XCB_XKB_EVENT_TYPE_STATE_NOTIFY,
0,
int i1; XCB_XKB_EVENT_TYPE_STATE_NOTIFY,
if (!XkbQueryExtension(xkb_dpy, &i1, &xkb_event_base, &xkb_errbase, &xkb_major, &xkb_minor)) { 0xff,
ELOG("XKB not supported by X-server!\n"); 0xff,
exit(EXIT_FAILURE); NULL);
} xkb_base = extreply->first_event;
if (!XkbSelectEvents(xkb_dpy, XkbUseCoreKbd, XkbStateNotifyMask, XkbStateNotifyMask)) {
ELOG("Could not grab Key!\n");
exit(EXIT_FAILURE);
}
xkb_io = smalloc(sizeof(ev_io));
ev_io_init(xkb_io, &xkb_io_cb, ConnectionNumber(xkb_dpy), EV_READ);
ev_io_start(main_loop, xkb_io);
XFlush(xkb_dpy);
}
} }
/* /*
@ -1098,13 +1149,14 @@ void register_xkb_keyevents() {
* *
*/ */
void deregister_xkb_keyevents() { void deregister_xkb_keyevents() {
if (xkb_dpy != NULL) { xcb_xkb_select_events(conn,
ev_io_stop(main_loop, xkb_io); XCB_XKB_ID_USE_CORE_KBD,
XCloseDisplay(xkb_dpy); 0,
close(xkb_io->fd); 0,
FREE(xkb_io); 0,
xkb_dpy = NULL; 0xff,
} 0xff,
NULL);
} }
/* /*
@ -1462,6 +1514,12 @@ void reconfig_windows(bool redraw_bars) {
values[2] = XCB_EVENT_MASK_EXPOSURE | values[2] = XCB_EVENT_MASK_EXPOSURE |
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
XCB_EVENT_MASK_BUTTON_PRESS; XCB_EVENT_MASK_BUTTON_PRESS;
if (config.hide_on_modifier == M_DOCK) {
/* If the bar is normally visible, catch visibility change events to suspend
* the status process when the bar is obscured by full-screened windows. */
values[2] |= XCB_EVENT_MASK_VISIBILITY_CHANGE;
walk->visible = true;
}
xcb_void_cookie_t win_cookie = xcb_create_window_checked(xcb_connection, xcb_void_cookie_t win_cookie = xcb_create_window_checked(xcb_connection,
root_screen->root_depth, root_screen->root_depth,
walk->bar, walk->bar,
@ -1720,18 +1778,18 @@ void draw_bars(bool unhide) {
/* We assume the tray icons are quadratic (we use the font /* We assume the tray icons are quadratic (we use the font
* *height* as *width* of the icons) because we configured them * *height* as *width* of the icons) because we configured them
* like this. */ * like this. */
traypx += font.height + 2; traypx += font.height + logical_px(2);
} }
/* Add 2px of padding if there are any tray icons */ /* Add 2px of padding if there are any tray icons */
if (traypx > 0) if (traypx > 0)
traypx += 2; traypx += logical_px(2);
xcb_copy_area(xcb_connection, xcb_copy_area(xcb_connection,
statusline_pm, statusline_pm,
outputs_walk->buffer, outputs_walk->buffer,
outputs_walk->bargc, outputs_walk->bargc,
MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0, MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + logical_px(4))), 0,
MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3, MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - logical_px(4))), 0,
MIN(outputs_walk->rect.w - traypx - 4, (int)statusline_width), font.height + 2); MIN(outputs_walk->rect.w - traypx - logical_px(4), (int)statusline_width), bar_height);
} }
if (!config.disable_ws) { if (!config.disable_ws) {

View File

@ -1,6 +1,7 @@
xmacro(_NET_SUPPORTED) xmacro(_NET_SUPPORTED)
xmacro(_NET_SUPPORTING_WM_CHECK) xmacro(_NET_SUPPORTING_WM_CHECK)
xmacro(_NET_WM_NAME) xmacro(_NET_WM_NAME)
xmacro(_NET_WM_MOVERESIZE)
xmacro(_NET_WM_STATE_FULLSCREEN) xmacro(_NET_WM_STATE_FULLSCREEN)
xmacro(_NET_WM_STATE_DEMANDS_ATTENTION) xmacro(_NET_WM_STATE_DEMANDS_ATTENTION)
xmacro(_NET_WM_STATE_MODAL) xmacro(_NET_WM_STATE_MODAL)
@ -16,7 +17,11 @@ xmacro(_NET_WM_STRUT_PARTIAL)
xmacro(_NET_CLIENT_LIST) xmacro(_NET_CLIENT_LIST)
xmacro(_NET_CLIENT_LIST_STACKING) xmacro(_NET_CLIENT_LIST_STACKING)
xmacro(_NET_CURRENT_DESKTOP) xmacro(_NET_CURRENT_DESKTOP)
xmacro(_NET_NUMBER_OF_DESKTOPS)
xmacro(_NET_DESKTOP_NAMES)
xmacro(_NET_DESKTOP_VIEWPORT)
xmacro(_NET_ACTIVE_WINDOW) xmacro(_NET_ACTIVE_WINDOW)
xmacro(_NET_CLOSE_WINDOW)
xmacro(_NET_STARTUP_ID) xmacro(_NET_STARTUP_ID)
xmacro(_NET_WORKAREA) xmacro(_NET_WORKAREA)
xmacro(WM_PROTOCOLS) xmacro(WM_PROTOCOLS)
@ -34,3 +39,4 @@ xmacro(I3_PID)
xmacro(_NET_REQUEST_FRAME_EXTENTS) xmacro(_NET_REQUEST_FRAME_EXTENTS)
xmacro(_NET_FRAME_EXTENTS) xmacro(_NET_FRAME_EXTENTS)
xmacro(_MOTIF_WM_HINTS) xmacro(_MOTIF_WM_HINTS)
xmacro(WM_CHANGE_STATE)

View File

@ -24,7 +24,7 @@ const char *DEFAULT_BINDING_MODE;
* *
*/ */
Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code, Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code,
const char *release, const char *command, const char *mode); const char *release, const char *whole_window, const char *command, const char *mode);
/** /**
* Grab the bound keys (tell X to send us keypress events for those keycodes) * Grab the bound keys (tell X to send us keypress events for those keycodes)
@ -61,9 +61,15 @@ void switch_mode(const char *new_mode);
void check_for_duplicate_bindings(struct context *context); void check_for_duplicate_bindings(struct context *context);
/** /**
* Runs the given binding and handles parse errors. Returns a CommandResult for * Frees the binding. If bind is null, it simply returns.
* running the binding's command. Caller should render tree if */
* needs_tree_render is true. Free with command_result_free(). void binding_free(Binding *bind);
/**
* Runs the given binding and handles parse errors. If con is passed, it will
* execute the command binding with that container selected by criteria.
* Returns a CommandResult for running the binding's command. Caller should
* render tree if needs_tree_render is true. Free with command_result_free().
* *
*/ */
CommandResult *run_binding(Binding *bind); CommandResult *run_binding(Binding *bind, Con *con);

View File

@ -187,10 +187,10 @@ void cmd_focus_level(I3_CMD, char *level);
void cmd_focus(I3_CMD); void cmd_focus(I3_CMD);
/** /**
* Implementation of 'fullscreen [global]'. * Implementation of 'fullscreen [enable|disable|toggle] [global]'.
* *
*/ */
void cmd_fullscreen(I3_CMD, char *fullscreen_mode); void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode);
/** /**
* Implementation of 'move <direction> [<pixels> [px]]'. * Implementation of 'move <direction> [<pixels> [px]]'.

View File

@ -44,6 +44,14 @@ struct CommandResult {
bool needs_tree_render; bool needs_tree_render;
}; };
/**
* Parses a string (or word, if as_word is true). Extracted out of
* parse_command so that it can be used in src/workspace.c for interpreting
* workspace commands.
*
*/
char *parse_string(const char **walk, bool as_word);
/** /**
* Parses and executes the given command. If a caller-allocated yajl_gen is * Parses and executes the given command. If a caller-allocated yajl_gen is
* passed, a json reply will be generated in the format specified by the ipc * passed, a json reply will be generated in the format specified by the ipc

View File

@ -18,7 +18,6 @@
*/ */
Con *con_new_skeleton(Con *parent, i3Window *window); Con *con_new_skeleton(Con *parent, i3Window *window);
/* A wrapper for con_new_skeleton, to retain the old con_new behaviour /* A wrapper for con_new_skeleton, to retain the old con_new behaviour
* *
*/ */
@ -173,6 +172,18 @@ void con_fix_percent(Con *con);
*/ */
void con_toggle_fullscreen(Con *con, int fullscreen_mode); void con_toggle_fullscreen(Con *con, int fullscreen_mode);
/**
* Enables fullscreen mode for the given container, if necessary.
*
*/
void con_enable_fullscreen(Con *con, fullscreen_mode_t fullscreen_mode);
/**
* Disables fullscreen mode for the given container, if necessary.
*
*/
void con_disable_fullscreen(Con *con);
/** /**
* Moves the given container to the currently focused container on the given * Moves the given container to the currently focused container on the given
* workspace. * workspace.
@ -345,3 +356,9 @@ void con_set_urgency(Con *con, bool urgent);
* *
*/ */
char *con_get_tree_representation(Con *con); char *con_get_tree_representation(Con *con);
/**
* force parent split containers to be redrawn
*
*/
void con_force_split_parents_redraw(Con *con);

View File

@ -241,10 +241,13 @@ struct Barconfig {
char *socket_path; char *socket_path;
/** Bar display mode (hide unless modifier is pressed or show in dock mode or always hide in invisible mode) */ /** Bar display mode (hide unless modifier is pressed or show in dock mode or always hide in invisible mode) */
enum { M_DOCK = 0, M_HIDE = 1, M_INVISIBLE = 2 } mode; enum { M_DOCK = 0,
M_HIDE = 1,
M_INVISIBLE = 2 } mode;
/* The current hidden_state of the bar, which indicates whether it is hidden or shown */ /* The current hidden_state of the bar, which indicates whether it is hidden or shown */
enum { S_HIDE = 0, S_SHOW = 1 } hidden_state; enum { S_HIDE = 0,
S_SHOW = 1 } hidden_state;
/** Bar modifier (to show bar when in hide mode). */ /** Bar modifier (to show bar when in hide mode). */
enum { enum {
@ -258,8 +261,17 @@ struct Barconfig {
M_MOD5 = 7 M_MOD5 = 7
} modifier; } modifier;
/** Command that should be run when mouse wheel up button is pressed over
* i3bar to override the default behavior. */
char *wheel_up_cmd;
/** Command that should be run when mouse wheel down button is pressed over
* i3bar to override the default behavior. */
char *wheel_down_cmd;
/** Bar position (bottom by default). */ /** Bar position (bottom by default). */
enum { P_BOTTOM = 0, P_TOP = 1 } position; enum { P_BOTTOM = 0,
P_TOP = 1 } position;
/** Command that should be run to execute i3bar, give a full path if i3bar is not /** Command that should be run to execute i3bar, give a full path if i3bar is not
* in your $PATH. * in your $PATH.
@ -314,6 +326,20 @@ struct Barconfig {
TAILQ_ENTRY(Barconfig) configs; TAILQ_ENTRY(Barconfig) configs;
}; };
/**
* Finds the configuration file to use (either the one specified by
* override_configpath), the users one or the system default) and calls
* parse_file().
*
* If you specify override_configpath, only this path is used to look for a
* configuration file.
*
* If use_nagbar is false, don't try to start i3-nagbar but log the errors to
* stdout/stderr instead.
*
*/
bool parse_configuration(const char *override_configpath, bool use_nagbar);
/** /**
* Reads the configuration from ~/.i3/config or /etc/i3/config if not found. * Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
* *

View File

@ -61,10 +61,10 @@ CFGFUN(color_single, const char *colorclass, const char *color);
CFGFUN(floating_modifier, const char *modifiers); CFGFUN(floating_modifier, const char *modifiers);
CFGFUN(new_window, const char *windowtype, const char *border, const long width); CFGFUN(new_window, const char *windowtype, const char *border, const long width);
CFGFUN(workspace, const char *workspace, const char *output); CFGFUN(workspace, const char *workspace, const char *output);
CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command); CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *whole_window, const char *command);
CFGFUN(enter_mode, const char *mode); CFGFUN(enter_mode, const char *mode);
CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command); CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *whole_window, const char *command);
CFGFUN(bar_font, const char *font); CFGFUN(bar_font, const char *font);
CFGFUN(bar_mode, const char *mode); CFGFUN(bar_mode, const char *mode);
@ -73,6 +73,8 @@ CFGFUN(bar_id, const char *bar_id);
CFGFUN(bar_output, const char *output); CFGFUN(bar_output, const char *output);
CFGFUN(bar_verbose, const char *verbose); CFGFUN(bar_verbose, const char *verbose);
CFGFUN(bar_modifier, const char *modifier); CFGFUN(bar_modifier, const char *modifier);
CFGFUN(bar_wheel_up_cmd, const char *command);
CFGFUN(bar_wheel_down_cmd, const char *command);
CFGFUN(bar_position, const char *position); CFGFUN(bar_position, const char *position);
CFGFUN(bar_i3bar_command, const char *i3bar_command); CFGFUN(bar_i3bar_command, const char *i3bar_command);
CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text); CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text);

View File

@ -33,7 +33,10 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context);
/** /**
* Parses the given file by first replacing the variables, then calling * Parses the given file by first replacing the variables, then calling
* parse_config and possibly launching i3-nagbar. * parse_config and launching i3-nagbar if use_nagbar is true.
*
* The return value is a boolean indicating whether there were errors during
* parsing.
* *
*/ */
void parse_file(const char *f); bool parse_file(const char *f, bool use_nagbar);

View File

@ -47,17 +47,25 @@ typedef struct Match Match;
typedef struct Assignment Assignment; typedef struct Assignment Assignment;
typedef struct Window i3Window; typedef struct Window i3Window;
/****************************************************************************** /******************************************************************************
* Helper types * Helper types
*****************************************************************************/ *****************************************************************************/
typedef enum { D_LEFT, D_RIGHT, D_UP, D_DOWN } direction_t; typedef enum { D_LEFT,
typedef enum { NO_ORIENTATION = 0, HORIZ, VERT } orientation_t; D_RIGHT,
typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_PIXEL = 2 } border_style_t; D_UP,
D_DOWN } direction_t;
typedef enum { NO_ORIENTATION = 0,
HORIZ,
VERT } orientation_t;
typedef enum { BS_NORMAL = 0,
BS_NONE = 1,
BS_PIXEL = 2 } border_style_t;
/** parameter to specify whether tree_close() and x_window_kill() should kill /** parameter to specify whether tree_close() and x_window_kill() should kill
* only this specific window or the whole X11 client */ * only this specific window or the whole X11 client */
typedef enum { DONT_KILL_WINDOW = 0, KILL_WINDOW = 1, KILL_CLIENT = 2 } kill_window_t; typedef enum { DONT_KILL_WINDOW = 0,
KILL_WINDOW = 1,
KILL_CLIENT = 2 } kill_window_t;
/** describes if the window is adjacent to the output (physical screen) edges. */ /** describes if the window is adjacent to the output (physical screen) edges. */
typedef enum { ADJ_NONE = 0, typedef enum { ADJ_NONE = 0,
@ -247,6 +255,11 @@ struct Binding {
B_UPON_KEYRELEASE_IGNORE_MODS = 2, B_UPON_KEYRELEASE_IGNORE_MODS = 2,
} release; } release;
/** If this is true for a mouse binding, the binding should be executed
* when the button is pressed over any part of the window, not just the
* title bar (default). */
bool whole_window;
uint32_t number_keycodes; uint32_t number_keycodes;
/** Keycode to bind */ /** Keycode to bind */
@ -267,7 +280,6 @@ struct Binding {
* This is an array of number_keycodes size. */ * This is an array of number_keycodes size. */
xcb_keycode_t *translated_to; xcb_keycode_t *translated_to;
/** Command, like in command mode */ /** Command, like in command mode */
char *command; char *command;
@ -367,7 +379,9 @@ struct Window {
bool doesnt_accept_focus; bool doesnt_accept_focus;
/** Whether the window says it is a dock window */ /** Whether the window says it is a dock window */
enum { W_NODOCK = 0, W_DOCK_TOP = 1, W_DOCK_BOTTOM = 2 } dock; enum { W_NODOCK = 0,
W_DOCK_TOP = 1,
W_DOCK_BOTTOM = 2 } dock;
/** When this window was marked urgent. 0 means not urgent */ /** When this window was marked urgent. 0 means not urgent */
struct timeval urgent; struct timeval urgent;
@ -407,7 +421,9 @@ struct Match {
M_DOCK_BOTTOM = 3 M_DOCK_BOTTOM = 3
} dock; } dock;
xcb_window_t id; xcb_window_t id;
enum { M_ANY = 0, M_TILING, M_FLOATING } floating; enum { M_ANY = 0,
M_TILING,
M_FLOATING } floating;
Con *con_id; Con *con_id;
/* Where the window looking for a match should be inserted: /* Where the window looking for a match should be inserted:
@ -419,7 +435,9 @@ struct Match {
* (dockareas) * (dockareas)
* *
*/ */
enum { M_HERE = 0, M_ASSIGN_WS, M_BELOW } insert_where; enum { M_HERE = 0,
M_ASSIGN_WS,
M_BELOW } insert_where;
TAILQ_ENTRY(Match) matches; TAILQ_ENTRY(Match) matches;
@ -470,7 +488,9 @@ struct Assignment {
}; };
/** Fullscreen modes. Used by Con.fullscreen_mode. */ /** Fullscreen modes. Used by Con.fullscreen_mode. */
typedef enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode_t; typedef enum { CF_NONE = 0,
CF_OUTPUT = 1,
CF_GLOBAL = 2 } fullscreen_mode_t;
/** /**
* A 'Con' represents everything from the X11 root window down to a single X11 window. * A 'Con' represents everything from the X11 root window down to a single X11 window.

View File

@ -18,6 +18,24 @@
*/ */
void ewmh_update_current_desktop(void); void ewmh_update_current_desktop(void);
/**
* Updates _NET_NUMBER_OF_DESKTOPS which we interpret as the number of
* noninternal workspaces.
*/
void ewmh_update_number_of_desktops(void);
/**
* Updates _NET_DESKTOP_NAMES: "The names of all virtual desktops. This is a
* list of NULL-terminated strings in UTF-8 encoding"
*/
void ewmh_update_desktop_names(void);
/**
* Updates _NET_DESKTOP_VIEWPORT, which is an array of pairs of cardinals that
* define the top left corner of each desktop's viewport.
*/
void ewmh_update_desktop_viewport(void);
/** /**
* Updates _NET_ACTIVE_WINDOW with the currently focused window. * Updates _NET_ACTIVE_WINDOW with the currently focused window.
* *

View File

@ -13,6 +13,7 @@
#include <xcb/randr.h> #include <xcb/randr.h>
extern int randr_base; extern int randr_base;
extern int xkb_base;
/** /**
* Adds the given sequence to the list of events which are ignored. * Adds the given sequence to the list of events which are ignored.

View File

@ -13,6 +13,7 @@
#include <sys/resource.h> #include <sys/resource.h>
#include <xcb/xcb_keysyms.h> #include <xcb/xcb_keysyms.h>
#include <xcb/xkb.h>
#include <X11/XKBlib.h> #include <X11/XKBlib.h>

View File

@ -100,3 +100,6 @@ typedef struct i3_ipc_header {
/** Bar config update will be triggered to update the bar config */ /** Bar config update will be triggered to update the bar config */
#define I3_IPC_EVENT_BARCONFIG_UPDATE (I3_IPC_EVENT_MASK | 4) #define I3_IPC_EVENT_BARCONFIG_UPDATE (I3_IPC_EVENT_MASK | 4)
/** The binding event will be triggered when bindings run */
#define I3_IPC_EVENT_BINDING (I3_IPC_EVENT_MASK | 5)

View File

@ -89,11 +89,17 @@ void ipc_shutdown(void);
void dump_node(yajl_gen gen, Con *con, bool inplace_restart); void dump_node(yajl_gen gen, Con *con, bool inplace_restart);
/** /**
* For the workspace "focus" event we send, along the usual "change" field, * Generates a json workspace event. Returns a dynamically allocated yajl
* also the current and previous workspace, in "current" and "old" * generator. Free with yajl_gen_free().
* respectively.
*/ */
void ipc_send_workspace_focus_event(Con *current, Con *old); yajl_gen ipc_marshal_workspace_event(const char *change, Con *current, Con *old);
/**
* For the workspace events we send, along with the usual "change" field, also
* the workspace container in "current". For focus events, we send the
* previously focused workspace in "old".
*/
void ipc_send_workspace_event(const char *change, Con *current, Con *old);
/** /**
* For the window events we send, along the usual "change" field, * For the window events we send, along the usual "change" field,
@ -105,3 +111,8 @@ void ipc_send_window_event(const char *property, Con *con);
* For the barconfig update events, we send the serialized barconfig. * For the barconfig update events, we send the serialized barconfig.
*/ */
void ipc_send_barconfig_update_event(Barconfig *barconfig); void ipc_send_barconfig_update_event(Barconfig *barconfig);
/**
* For the binding events, we send the serialized binding struct.
*/
void ipc_send_binding_event(const char *event_type, Binding *bind);

View File

@ -141,6 +141,12 @@ int sasprintf(char **strp, const char *fmt, ...);
*/ */
i3String *i3string_from_utf8(const char *from_utf8); i3String *i3string_from_utf8(const char *from_utf8);
/**
* Build an i3String from an UTF-8 encoded string in Pango markup.
*
*/
i3String *i3string_from_markup(const char *from_markup);
/** /**
* Build an i3String from an UTF-8 encoded string with fixed length. * Build an i3String from an UTF-8 encoded string with fixed length.
* To be used when no proper NUL-terminaison is available. * To be used when no proper NUL-terminaison is available.
@ -149,6 +155,13 @@ i3String *i3string_from_utf8(const char *from_utf8);
*/ */
i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes); i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes);
/**
* Build an i3String from an UTF-8 encoded string in Pango markup with fixed
* length.
*
*/
i3String *i3string_from_markup_with_length(const char *from_markup, size_t num_bytes);
/** /**
* Build an i3String from an UCS-2 encoded string. * Build an i3String from an UCS-2 encoded string.
* Returns the newly-allocated i3String. * Returns the newly-allocated i3String.
@ -193,6 +206,11 @@ const xcb_char2b_t *i3string_as_ucs2(i3String *str);
*/ */
size_t i3string_get_num_bytes(i3String *str); size_t i3string_get_num_bytes(i3String *str);
/**
* Whether the given i3String is in Pango markup.
*/
bool i3string_is_markup(i3String *str);
/** /**
* Returns the number of glyphs in an i3String. * Returns the number of glyphs in an i3String.
* *
@ -290,7 +308,8 @@ uint32_t get_mod_mask_for(uint32_t keysym,
/** /**
* Loads a font for usage, also getting its height. If fallback is true, * Loads a font for usage, also getting its height. If fallback is true,
* the fonts 'fixed' or '-misc-*' will be loaded instead of exiting. * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting. If any
* font was previously loaded, it will be freed.
* *
*/ */
i3Font load_font(const char *pattern, const bool fallback); i3Font load_font(const char *pattern, const bool fallback);
@ -302,7 +321,8 @@ i3Font load_font(const char *pattern, const bool fallback);
void set_font(i3Font *font); void set_font(i3Font *font);
/** /**
* Frees the resources taken by the current font. * Frees the resources taken by the current font. If no font was previously
* loaded, it simply returns.
* *
*/ */
void free_font(void); void free_font(void);

View File

@ -10,8 +10,8 @@
#pragma once #pragma once
/** /**
* Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT, * Moves the given container in the given direction (TOK_LEFT, TOK_RIGHT,
* TOK_UP, TOK_DOWN from cmdparse.l) * TOK_UP, TOK_DOWN from cmdparse.l)
* *
*/ */
void tree_move(int direction); void tree_move(Con *con, int direction);

View File

@ -124,29 +124,35 @@ struct { \
/* /*
* Singly-linked List functions. * Singly-linked List functions.
*/ */
#define SLIST_INIT(head) { \ #define SLIST_INIT(head) \
{ \
SLIST_FIRST(head) = SLIST_END(head); \ SLIST_FIRST(head) = SLIST_END(head); \
} }
#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ #define SLIST_INSERT_AFTER(slistelm, elm, field) \
do { \
(elm)->field.sle_next = (slistelm)->field.sle_next; \ (elm)->field.sle_next = (slistelm)->field.sle_next; \
(slistelm)->field.sle_next = (elm); \ (slistelm)->field.sle_next = (elm); \
} while (0) } while (0)
#define SLIST_INSERT_HEAD(head, elm, field) do { \ #define SLIST_INSERT_HEAD(head, elm, field) \
do { \
(elm)->field.sle_next = (head)->slh_first; \ (elm)->field.sle_next = (head)->slh_first; \
(head)->slh_first = (elm); \ (head)->slh_first = (elm); \
} while (0) } while (0)
#define SLIST_REMOVE_NEXT(head, elm, field) do { \ #define SLIST_REMOVE_NEXT(head, elm, field) \
do { \
(elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \
} while (0) } while (0)
#define SLIST_REMOVE_HEAD(head, field) do { \ #define SLIST_REMOVE_HEAD(head, field) \
do { \
(head)->slh_first = (head)->slh_first->field.sle_next; \ (head)->slh_first = (head)->slh_first->field.sle_next; \
} while (0) } while (0)
#define SLIST_REMOVE(head, elm, type, field) do { \ #define SLIST_REMOVE(head, elm, type, field) \
do { \
if ((head)->slh_first == (elm)) { \ if ((head)->slh_first == (elm)) { \
SLIST_REMOVE_HEAD((head), field); \ SLIST_REMOVE_HEAD((head), field); \
} else { \ } else { \
@ -154,8 +160,7 @@ struct { \
\ \
while (curelm->field.sle_next != (elm)) \ while (curelm->field.sle_next != (elm)) \
curelm = curelm->field.sle_next; \ curelm = curelm->field.sle_next; \
curelm->field.sle_next = \ curelm->field.sle_next = curelm->field.sle_next->field.sle_next; \
curelm->field.sle_next->field.sle_next; \
_Q_INVALIDATE((elm)->field.sle_next); \ _Q_INVALIDATE((elm)->field.sle_next); \
} \ } \
} while (0) } while (0)
@ -193,45 +198,48 @@ struct { \
/* /*
* List functions. * List functions.
*/ */
#define LIST_INIT(head) do { \ #define LIST_INIT(head) \
do { \
LIST_FIRST(head) = LIST_END(head); \ LIST_FIRST(head) = LIST_END(head); \
} while (0) } while (0)
#define LIST_INSERT_AFTER(listelm, elm, field) do { \ #define LIST_INSERT_AFTER(listelm, elm, field) \
do { \
if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \
(listelm)->field.le_next->field.le_prev = \ (listelm)->field.le_next->field.le_prev = &(elm)->field.le_next; \
&(elm)->field.le_next; \
(listelm)->field.le_next = (elm); \ (listelm)->field.le_next = (elm); \
(elm)->field.le_prev = &(listelm)->field.le_next; \ (elm)->field.le_prev = &(listelm)->field.le_next; \
} while (0) } while (0)
#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ #define LIST_INSERT_BEFORE(listelm, elm, field) \
do { \
(elm)->field.le_prev = (listelm)->field.le_prev; \ (elm)->field.le_prev = (listelm)->field.le_prev; \
(elm)->field.le_next = (listelm); \ (elm)->field.le_next = (listelm); \
*(listelm)->field.le_prev = (elm); \ *(listelm)->field.le_prev = (elm); \
(listelm)->field.le_prev = &(elm)->field.le_next; \ (listelm)->field.le_prev = &(elm)->field.le_next; \
} while (0) } while (0)
#define LIST_INSERT_HEAD(head, elm, field) do { \ #define LIST_INSERT_HEAD(head, elm, field) \
do { \
if (((elm)->field.le_next = (head)->lh_first) != NULL) \ if (((elm)->field.le_next = (head)->lh_first) != NULL) \
(head)->lh_first->field.le_prev = &(elm)->field.le_next; \ (head)->lh_first->field.le_prev = &(elm)->field.le_next; \
(head)->lh_first = (elm); \ (head)->lh_first = (elm); \
(elm)->field.le_prev = &(head)->lh_first; \ (elm)->field.le_prev = &(head)->lh_first; \
} while (0) } while (0)
#define LIST_REMOVE(elm, field) do { \ #define LIST_REMOVE(elm, field) \
do { \
if ((elm)->field.le_next != NULL) \ if ((elm)->field.le_next != NULL) \
(elm)->field.le_next->field.le_prev = \ (elm)->field.le_next->field.le_prev = (elm)->field.le_prev; \
(elm)->field.le_prev; \
*(elm)->field.le_prev = (elm)->field.le_next; \ *(elm)->field.le_prev = (elm)->field.le_next; \
_Q_INVALIDATE((elm)->field.le_prev); \ _Q_INVALIDATE((elm)->field.le_prev); \
_Q_INVALIDATE((elm)->field.le_next); \ _Q_INVALIDATE((elm)->field.le_next); \
} while (0) } while (0)
#define LIST_REPLACE(elm, elm2, field) do { \ #define LIST_REPLACE(elm, elm2, field) \
do { \
if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \
(elm2)->field.le_next->field.le_prev = \ (elm2)->field.le_next->field.le_prev = &(elm2)->field.le_next; \
&(elm2)->field.le_next; \
(elm2)->field.le_prev = (elm)->field.le_prev; \ (elm2)->field.le_prev = (elm)->field.le_prev; \
*(elm2)->field.le_prev = (elm2); \ *(elm2)->field.le_prev = (elm2); \
_Q_INVALIDATE((elm)->field.le_prev); \ _Q_INVALIDATE((elm)->field.le_prev); \
@ -271,30 +279,35 @@ struct { \
/* /*
* Simple queue functions. * Simple queue functions.
*/ */
#define SIMPLEQ_INIT(head) do { \ #define SIMPLEQ_INIT(head) \
do { \
(head)->sqh_first = NULL; \ (head)->sqh_first = NULL; \
(head)->sqh_last = &(head)->sqh_first; \ (head)->sqh_last = &(head)->sqh_first; \
} while (0) } while (0)
#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ #define SIMPLEQ_INSERT_HEAD(head, elm, field) \
do { \
if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \
(head)->sqh_last = &(elm)->field.sqe_next; \ (head)->sqh_last = &(elm)->field.sqe_next; \
(head)->sqh_first = (elm); \ (head)->sqh_first = (elm); \
} while (0) } while (0)
#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ #define SIMPLEQ_INSERT_TAIL(head, elm, field) \
do { \
(elm)->field.sqe_next = NULL; \ (elm)->field.sqe_next = NULL; \
*(head)->sqh_last = (elm); \ *(head)->sqh_last = (elm); \
(head)->sqh_last = &(elm)->field.sqe_next; \ (head)->sqh_last = &(elm)->field.sqe_next; \
} while (0) } while (0)
#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ #define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) \
do { \
if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL) \ if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL) \
(head)->sqh_last = &(elm)->field.sqe_next; \ (head)->sqh_last = &(elm)->field.sqe_next; \
(listelm)->field.sqe_next = (elm); \ (listelm)->field.sqe_next = (elm); \
} while (0) } while (0)
#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ #define SIMPLEQ_REMOVE_HEAD(head, field) \
do { \
if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
(head)->sqh_last = &(head)->sqh_first; \ (head)->sqh_last = &(head)->sqh_first; \
} while (0) } while (0)
@ -344,49 +357,52 @@ struct { \
/* /*
* Tail queue functions. * Tail queue functions.
*/ */
#define TAILQ_INIT(head) do { \ #define TAILQ_INIT(head) \
do { \
(head)->tqh_first = NULL; \ (head)->tqh_first = NULL; \
(head)->tqh_last = &(head)->tqh_first; \ (head)->tqh_last = &(head)->tqh_first; \
} while (0) } while (0)
#define TAILQ_INSERT_HEAD(head, elm, field) do { \ #define TAILQ_INSERT_HEAD(head, elm, field) \
do { \
if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \
(head)->tqh_first->field.tqe_prev = \ (head)->tqh_first->field.tqe_prev = &(elm)->field.tqe_next; \
&(elm)->field.tqe_next; \
else \ else \
(head)->tqh_last = &(elm)->field.tqe_next; \ (head)->tqh_last = &(elm)->field.tqe_next; \
(head)->tqh_first = (elm); \ (head)->tqh_first = (elm); \
(elm)->field.tqe_prev = &(head)->tqh_first; \ (elm)->field.tqe_prev = &(head)->tqh_first; \
} while (0) } while (0)
#define TAILQ_INSERT_TAIL(head, elm, field) do { \ #define TAILQ_INSERT_TAIL(head, elm, field) \
do { \
(elm)->field.tqe_next = NULL; \ (elm)->field.tqe_next = NULL; \
(elm)->field.tqe_prev = (head)->tqh_last; \ (elm)->field.tqe_prev = (head)->tqh_last; \
*(head)->tqh_last = (elm); \ *(head)->tqh_last = (elm); \
(head)->tqh_last = &(elm)->field.tqe_next; \ (head)->tqh_last = &(elm)->field.tqe_next; \
} while (0) } while (0)
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ #define TAILQ_INSERT_AFTER(head, listelm, elm, field) \
do { \
if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL) \ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL) \
(elm)->field.tqe_next->field.tqe_prev = \ (elm)->field.tqe_next->field.tqe_prev = &(elm)->field.tqe_next; \
&(elm)->field.tqe_next; \
else \ else \
(head)->tqh_last = &(elm)->field.tqe_next; \ (head)->tqh_last = &(elm)->field.tqe_next; \
(listelm)->field.tqe_next = (elm); \ (listelm)->field.tqe_next = (elm); \
(elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \
} while (0) } while (0)
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ #define TAILQ_INSERT_BEFORE(listelm, elm, field) \
do { \
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
(elm)->field.tqe_next = (listelm); \ (elm)->field.tqe_next = (listelm); \
*(listelm)->field.tqe_prev = (elm); \ *(listelm)->field.tqe_prev = (elm); \
(listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
} while (0) } while (0)
#define TAILQ_REMOVE(head, elm, field) do { \ #define TAILQ_REMOVE(head, elm, field) \
do { \
if (((elm)->field.tqe_next) != NULL) \ if (((elm)->field.tqe_next) != NULL) \
(elm)->field.tqe_next->field.tqe_prev = \ (elm)->field.tqe_next->field.tqe_prev = (elm)->field.tqe_prev; \
(elm)->field.tqe_prev; \
else \ else \
(head)->tqh_last = (elm)->field.tqe_prev; \ (head)->tqh_last = (elm)->field.tqe_prev; \
*(elm)->field.tqe_prev = (elm)->field.tqe_next; \ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \
@ -394,10 +410,10 @@ struct { \
_Q_INVALIDATE((elm)->field.tqe_next); \ _Q_INVALIDATE((elm)->field.tqe_next); \
} while (0) } while (0)
#define TAILQ_REPLACE(head, elm, elm2, field) do { \ #define TAILQ_REPLACE(head, elm, elm2, field) \
do { \
if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \
(elm2)->field.tqe_next->field.tqe_prev = \ (elm2)->field.tqe_next->field.tqe_prev = &(elm2)->field.tqe_next; \
&(elm2)->field.tqe_next; \
else \ else \
(head)->tqh_last = &(elm2)->field.tqe_next; \ (head)->tqh_last = &(elm2)->field.tqe_next; \
(elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \
@ -407,7 +423,8 @@ struct { \
} while (0) } while (0)
/* Swaps two consecutive elements. 'second' *MUST* follow 'first' */ /* Swaps two consecutive elements. 'second' *MUST* follow 'first' */
#define TAILQ_SWAP(first, second, head, field) do { \ #define TAILQ_SWAP(first, second, head, field) \
do { \
*((first)->field.tqe_prev) = (second); \ *((first)->field.tqe_prev) = (second); \
(second)->field.tqe_prev = (first)->field.tqe_prev; \ (second)->field.tqe_prev = (first)->field.tqe_prev; \
(first)->field.tqe_prev = &((second)->field.tqe_next); \ (first)->field.tqe_prev = &((second)->field.tqe_next); \
@ -461,12 +478,14 @@ struct { \
/* /*
* Circular queue functions. * Circular queue functions.
*/ */
#define CIRCLEQ_INIT(head) do { \ #define CIRCLEQ_INIT(head) \
do { \
(head)->cqh_first = CIRCLEQ_END(head); \ (head)->cqh_first = CIRCLEQ_END(head); \
(head)->cqh_last = CIRCLEQ_END(head); \ (head)->cqh_last = CIRCLEQ_END(head); \
} while (0) } while (0)
#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ #define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) \
do { \
(elm)->field.cqe_next = (listelm)->field.cqe_next; \ (elm)->field.cqe_next = (listelm)->field.cqe_next; \
(elm)->field.cqe_prev = (listelm); \ (elm)->field.cqe_prev = (listelm); \
if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \ if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \
@ -476,7 +495,8 @@ struct { \
(listelm)->field.cqe_next = (elm); \ (listelm)->field.cqe_next = (elm); \
} while (0) } while (0)
#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ #define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) \
do { \
(elm)->field.cqe_next = (listelm); \ (elm)->field.cqe_next = (listelm); \
(elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \
if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \ if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \
@ -486,7 +506,8 @@ struct { \
(listelm)->field.cqe_prev = (elm); \ (listelm)->field.cqe_prev = (elm); \
} while (0) } while (0)
#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ #define CIRCLEQ_INSERT_HEAD(head, elm, field) \
do { \
(elm)->field.cqe_next = (head)->cqh_first; \ (elm)->field.cqe_next = (head)->cqh_first; \
(elm)->field.cqe_prev = CIRCLEQ_END(head); \ (elm)->field.cqe_prev = CIRCLEQ_END(head); \
if ((head)->cqh_last == CIRCLEQ_END(head)) \ if ((head)->cqh_last == CIRCLEQ_END(head)) \
@ -496,7 +517,8 @@ struct { \
(head)->cqh_first = (elm); \ (head)->cqh_first = (elm); \
} while (0) } while (0)
#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ #define CIRCLEQ_INSERT_TAIL(head, elm, field) \
do { \
(elm)->field.cqe_next = CIRCLEQ_END(head); \ (elm)->field.cqe_next = CIRCLEQ_END(head); \
(elm)->field.cqe_prev = (head)->cqh_last; \ (elm)->field.cqe_prev = (head)->cqh_last; \
if ((head)->cqh_first == CIRCLEQ_END(head)) \ if ((head)->cqh_first == CIRCLEQ_END(head)) \
@ -506,29 +528,27 @@ struct { \
(head)->cqh_last = (elm); \ (head)->cqh_last = (elm); \
} while (0) } while (0)
#define CIRCLEQ_REMOVE(head, elm, field) do { \ #define CIRCLEQ_REMOVE(head, elm, field) \
do { \
if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \ if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \
(head)->cqh_last = (elm)->field.cqe_prev; \ (head)->cqh_last = (elm)->field.cqe_prev; \
else \ else \
(elm)->field.cqe_next->field.cqe_prev = \ (elm)->field.cqe_next->field.cqe_prev = (elm)->field.cqe_prev; \
(elm)->field.cqe_prev; \
if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \ if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \
(head)->cqh_first = (elm)->field.cqe_next; \ (head)->cqh_first = (elm)->field.cqe_next; \
else \ else \
(elm)->field.cqe_prev->field.cqe_next = \ (elm)->field.cqe_prev->field.cqe_next = (elm)->field.cqe_next; \
(elm)->field.cqe_next; \
_Q_INVALIDATE((elm)->field.cqe_prev); \ _Q_INVALIDATE((elm)->field.cqe_prev); \
_Q_INVALIDATE((elm)->field.cqe_next); \ _Q_INVALIDATE((elm)->field.cqe_next); \
} while (0) } while (0)
#define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \ #define CIRCLEQ_REPLACE(head, elm, elm2, field) \
if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \ do { \
CIRCLEQ_END(head)) \ if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == CIRCLEQ_END(head)) \
(head)->cqh_last = (elm2); \ (head)->cqh_last = (elm2); \
else \ else \
(elm2)->field.cqe_next->field.cqe_prev = (elm2); \ (elm2)->field.cqe_next->field.cqe_prev = (elm2); \
if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \ if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == CIRCLEQ_END(head)) \
CIRCLEQ_END(head)) \
(head)->cqh_first = (elm2); \ (head)->cqh_first = (elm2); \
else \ else \
(elm2)->field.cqe_prev->field.cqe_next = (elm2); \ (elm2)->field.cqe_prev->field.cqe_next = (elm2); \

View File

@ -21,8 +21,8 @@
* (immediately), the application is reparented to init (process-id 1), which * (immediately), the application is reparented to init (process-id 1), which
* correctly handles childs, so we dont have to do it :-). * correctly handles childs, so we dont have to do it :-).
* *
* The shell is determined by looking for the SHELL environment variable. If * The shell used to start applications is the system's bourne shell (i.e.,
* it does not exist, /bin/sh is used. * /bin/sh).
* *
* The no_startup_id flag determines whether a startup notification context * The no_startup_id flag determines whether a startup notification context
* (and ID) should be created, which is the default and encouraged behavior. * (and ID) should be created, which is the default and encouraged behavior.

View File

@ -15,12 +15,14 @@
#include "data.h" #include "data.h"
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__); #define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
#define exit_if_null(pointer, ...) { if (pointer == NULL) die(__VA_ARGS__); } #define exit_if_null(pointer, ...) \
{ \
if (pointer == NULL) \
die(__VA_ARGS__); \
}
#define STARTS_WITH(string, needle) (strncasecmp(string, needle, strlen(needle)) == 0) #define STARTS_WITH(string, needle) (strncasecmp(string, needle, strlen(needle)) == 0)
#define CIRCLEQ_NEXT_OR_NULL(head, elm, field) (CIRCLEQ_NEXT(elm, field) != CIRCLEQ_END(head) ? \ #define CIRCLEQ_NEXT_OR_NULL(head, elm, field) (CIRCLEQ_NEXT(elm, field) != CIRCLEQ_END(head) ? CIRCLEQ_NEXT(elm, field) : NULL)
CIRCLEQ_NEXT(elm, field) : NULL) #define CIRCLEQ_PREV_OR_NULL(head, elm, field) (CIRCLEQ_PREV(elm, field) != CIRCLEQ_END(head) ? CIRCLEQ_PREV(elm, field) : NULL)
#define CIRCLEQ_PREV_OR_NULL(head, elm, field) (CIRCLEQ_PREV(elm, field) != CIRCLEQ_END(head) ? \
CIRCLEQ_PREV(elm, field) : NULL)
#define FOR_TABLE(workspace) \ #define FOR_TABLE(workspace) \
for (int cols = 0; cols < (workspace)->cols; cols++) \ for (int cols = 0; cols < (workspace)->cols; cols++) \
for (int rows = 0; rows < (workspace)->rows; rows++) for (int rows = 0; rows < (workspace)->rows; rows++)
@ -43,13 +45,13 @@
break; \ break; \
} }
#define FREE(pointer) do { \ #define FREE(pointer) \
do { \
if (pointer != NULL) { \ if (pointer != NULL) { \
free(pointer); \ free(pointer); \
pointer = NULL; \ pointer = NULL; \
} \ } \
} \ } while (0)
while (0)
#define CALL(obj, member, ...) obj->member(obj, ##__VA_ARGS__) #define CALL(obj, member, ...) obj->member(obj, ##__VA_ARGS__)

View File

@ -100,7 +100,6 @@ void workspace_back_and_forth(void);
*/ */
Con *workspace_back_and_forth_get(void); Con *workspace_back_and_forth_get(void);
#if 0 #if 0
/** /**
* Assigns the given workspace to the given screen by correctly updating its * Assigns the given workspace to the given screen by correctly updating its

View File

@ -108,7 +108,6 @@ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window);
*/ */
void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r); void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r);
bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom); bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom);
/** /**

View File

@ -1,3 +1,10 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2014 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#include "libi3.h" #include "libi3.h"
#include <math.h> #include <math.h>
@ -12,5 +19,13 @@ extern xcb_screen_t *root_screen;
int logical_px(const int logical) { int logical_px(const int logical) {
const int dpi = (double)root_screen->height_in_pixels * 25.4 / const int dpi = (double)root_screen->height_in_pixels * 25.4 /
(double)root_screen->height_in_millimeters; (double)root_screen->height_in_millimeters;
/* There are many misconfigurations out there, i.e. systems with screens
* whose dpi is in fact higher than 96 dpi, but not significantly higher,
* so software was never adapted. We could tell people to reconfigure their
* systems to 96 dpi in order to get the behavior they expect/are used to,
* but since we can easily detect this case in code, lets do it for them.
*/
if ((dpi / 96.0) < 1.25)
return logical;
return ceil((dpi / 96.0) * logical); return ceil((dpi / 96.0) * logical);
} }

View File

@ -102,7 +102,8 @@ static bool load_pango_font(i3Font *font, const char *desc) {
* *
*/ */
static void draw_text_pango(const char *text, size_t text_len, static void draw_text_pango(const char *text, size_t text_len,
xcb_drawable_t drawable, int x, int y, int max_width) { xcb_drawable_t drawable, int x, int y,
int max_width, bool is_markup) {
/* Create the Pango layout */ /* Create the Pango layout */
/* root_visual_type is cached in load_pango_font */ /* root_visual_type is cached in load_pango_font */
cairo_surface_t *surface = cairo_xcb_surface_create(conn, drawable, cairo_surface_t *surface = cairo_xcb_surface_create(conn, drawable,
@ -116,6 +117,9 @@ static void draw_text_pango(const char *text, size_t text_len,
pango_layout_set_wrap(layout, PANGO_WRAP_CHAR); pango_layout_set_wrap(layout, PANGO_WRAP_CHAR);
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
if (is_markup)
pango_layout_set_markup(layout, text, text_len);
else
pango_layout_set_text(layout, text, text_len); pango_layout_set_text(layout, text, text_len);
/* Do the drawing */ /* Do the drawing */
@ -135,7 +139,7 @@ static void draw_text_pango(const char *text, size_t text_len,
* Calculate the text width using Pango rendering. * Calculate the text width using Pango rendering.
* *
*/ */
static int predict_text_width_pango(const char *text, size_t text_len) { static int predict_text_width_pango(const char *text, size_t text_len, bool is_markup) {
/* Create a dummy Pango layout */ /* Create a dummy Pango layout */
/* root_visual_type is cached in load_pango_font */ /* root_visual_type is cached in load_pango_font */
cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1); cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1);
@ -145,7 +149,12 @@ static int predict_text_width_pango(const char *text, size_t text_len) {
/* Get the font width */ /* Get the font width */
gint width; gint width;
pango_layout_set_font_description(layout, savedFont->specific.pango_desc); pango_layout_set_font_description(layout, savedFont->specific.pango_desc);
if (is_markup)
pango_layout_set_markup(layout, text, text_len);
else
pango_layout_set_text(layout, text, text_len); pango_layout_set_text(layout, text, text_len);
pango_cairo_update_layout(cr, layout); pango_cairo_update_layout(cr, layout);
pango_layout_get_pixel_size(layout, &width, NULL); pango_layout_get_pixel_size(layout, &width, NULL);
@ -160,13 +169,23 @@ static int predict_text_width_pango(const char *text, size_t text_len) {
/* /*
* Loads a font for usage, also getting its metrics. If fallback is true, * Loads a font for usage, also getting its metrics. If fallback is true,
* the fonts 'fixed' or '-misc-*' will be loaded instead of exiting. * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting. If any
* font was previously loaded, it will be freed.
* *
*/ */
i3Font load_font(const char *pattern, const bool fallback) { i3Font load_font(const char *pattern, const bool fallback) {
/* if any font was previously loaded, free it now */
free_font();
i3Font font; i3Font font;
font.type = FONT_TYPE_NONE; font.type = FONT_TYPE_NONE;
/* No XCB connction, return early because we're just validating the
* configuration file. */
if (conn == NULL) {
return font;
}
#if PANGO_SUPPORT #if PANGO_SUPPORT
/* Try to load a pango font if specified */ /* Try to load a pango font if specified */
if (strlen(pattern) > strlen("pango:") && !strncmp(pattern, "pango:", strlen("pango:"))) { if (strlen(pattern) > strlen("pango:") && !strncmp(pattern, "pango:", strlen("pango:"))) {
@ -251,10 +270,15 @@ void set_font(i3Font *font) {
} }
/* /*
* Frees the resources taken by the current font. * Frees the resources taken by the current font. If no font was previously
* loaded, it simply returns.
* *
*/ */
void free_font(void) { void free_font(void) {
/* if there is no saved font, simply return */
if (savedFont == NULL)
return;
free(savedFont->pattern); free(savedFont->pattern);
switch (savedFont->type) { switch (savedFont->type) {
case FONT_TYPE_NONE: case FONT_TYPE_NONE:
@ -277,6 +301,8 @@ void free_font(void) {
assert(false); assert(false);
break; break;
} }
savedFont = NULL;
} }
/* /*
@ -366,7 +392,7 @@ void draw_text(i3String *text, xcb_drawable_t drawable,
case FONT_TYPE_PANGO: case FONT_TYPE_PANGO:
/* Render the text using Pango */ /* Render the text using Pango */
draw_text_pango(i3string_as_utf8(text), i3string_get_num_bytes(text), draw_text_pango(i3string_as_utf8(text), i3string_get_num_bytes(text),
drawable, x, y, max_width); drawable, x, y, max_width, i3string_is_markup(text));
return; return;
#endif #endif
default: default:
@ -405,7 +431,7 @@ void draw_text_ascii(const char *text, xcb_drawable_t drawable,
case FONT_TYPE_PANGO: case FONT_TYPE_PANGO:
/* Render the text using Pango */ /* Render the text using Pango */
draw_text_pango(text, strlen(text), draw_text_pango(text, strlen(text),
drawable, x, y, max_width); drawable, x, y, max_width, false);
return; return;
#endif #endif
default: default:
@ -501,7 +527,8 @@ int predict_text_width(i3String *text) {
#if PANGO_SUPPORT #if PANGO_SUPPORT
case FONT_TYPE_PANGO: case FONT_TYPE_PANGO:
/* Calculate extents using Pango */ /* Calculate extents using Pango */
return predict_text_width_pango(i3string_as_utf8(text), i3string_get_num_bytes(text)); return predict_text_width_pango(i3string_as_utf8(text), i3string_get_num_bytes(text),
i3string_is_markup(text));
#endif #endif
default: default:
assert(false); assert(false);

View File

@ -20,6 +20,7 @@ struct _i3String {
xcb_char2b_t *ucs2; xcb_char2b_t *ucs2;
size_t num_glyphs; size_t num_glyphs;
size_t num_bytes; size_t num_bytes;
bool is_markup;
}; };
/* /*
@ -39,6 +40,19 @@ i3String *i3string_from_utf8(const char *from_utf8) {
return str; return str;
} }
/*
* Build an i3String from an UTF-8 encoded string in Pango markup.
*
*/
i3String *i3string_from_markup(const char *from_markup) {
i3String *str = i3string_from_utf8(from_markup);
/* Set the markup flag */
str->is_markup = true;
return str;
}
/* /*
* Build an i3String from an UTF-8 encoded string with fixed length. * Build an i3String from an UTF-8 encoded string with fixed length.
* To be used when no proper NUL-terminaison is available. * To be used when no proper NUL-terminaison is available.
@ -59,6 +73,20 @@ i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes
return str; return str;
} }
/*
* Build an i3String from an UTF-8 encoded string in Pango markup with fixed
* length.
*
*/
i3String *i3string_from_markup_with_length(const char *from_markup, size_t num_bytes) {
i3String *str = i3string_from_utf8_with_length(from_markup, num_bytes);
/* set the markup flag */
str->is_markup = true;
return str;
}
/* /*
* Build an i3String from an UCS-2 encoded string. * Build an i3String from an UCS-2 encoded string.
* Returns the newly-allocated i3String. * Returns the newly-allocated i3String.
@ -133,6 +161,13 @@ size_t i3string_get_num_bytes(i3String *str) {
return str->num_bytes; return str->num_bytes;
} }
/*
* Whether the given i3String is in Pango markup.
*/
bool i3string_is_markup(i3String *str) {
return str->is_markup;
}
/* /*
* Returns the number of glyphs in an i3String. * Returns the number of glyphs in an i3String.
* *

View File

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

View File

@ -22,6 +22,7 @@ is appropriate for the distribution.
It tries to start one of the following (in that order): It tries to start one of the following (in that order):
* $TERMINAL (this is a non-standard variable) * $TERMINAL (this is a non-standard variable)
* x-terminal-emulator (only present on Debian and derivatives)
* urxvt * urxvt
* rxvt * rxvt
* terminator * terminator

View File

@ -230,7 +230,7 @@ bindsym Mod1+h split h
bindsym Mod1+v split v bindsym Mod1+v split v
# enter fullscreen mode for the focused container # enter fullscreen mode for the focused container
bindsym Mod1+f fullscreen bindsym Mod1+f fullscreen toggle
# change container layout (stacked, tabbed, default) # change container layout (stacked, tabbed, default)
bindsym Mod1+s layout stacking bindsym Mod1+s layout stacking

View File

@ -156,12 +156,28 @@ state KILL:
end end
-> call cmd_kill($kill_mode) -> call cmd_kill($kill_mode)
# fullscreen enable|toggle [global]
# fullscreen disable
# fullscreen [global] # fullscreen [global]
state FULLSCREEN: state FULLSCREEN:
fullscreen_mode = 'global' action = 'disable'
-> call cmd_fullscreen($fullscreen_mode) -> call cmd_fullscreen($action, "output")
action = 'enable', 'toggle'
-> FULLSCREEN_MODE
action = ''
-> FULLSCREEN_COMPAT
state FULLSCREEN_MODE:
mode = 'global'
-> call cmd_fullscreen($action, $mode)
end end
-> call cmd_fullscreen($fullscreen_mode) -> call cmd_fullscreen($action, "output")
state FULLSCREEN_COMPAT:
mode = 'global'
-> call cmd_fullscreen("toggle", $mode)
end
-> call cmd_fullscreen("toggle", "output")
# split v|h|vertical|horizontal # split v|h|vertical|horizontal
state SPLIT: state SPLIT:

View File

@ -278,6 +278,8 @@ state FONT:
state BINDING: state BINDING:
release = '--release' release = '--release'
-> ->
whole_window = '--whole-window'
->
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod' modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod'
-> ->
'+' '+'
@ -288,8 +290,10 @@ state BINDING:
state BINDCOMMAND: state BINDCOMMAND:
release = '--release' release = '--release'
-> ->
whole_window = '--whole-window'
->
command = string command = string
-> call cfg_binding($bindtype, $modifiers, $key, $release, $command) -> call cfg_binding($bindtype, $modifiers, $key, $release, $whole_window, $command)
################################################################################ ################################################################################
# Mode configuration # Mode configuration
@ -333,8 +337,10 @@ state MODE_BINDING:
state MODE_BINDCOMMAND: state MODE_BINDCOMMAND:
release = '--release' release = '--release'
-> ->
whole_window = '--whole-window'
->
command = string command = string
-> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $command); MODE -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $whole_window, $command); MODE
################################################################################ ################################################################################
# Bar configuration (i3bar) # Bar configuration (i3bar)
@ -358,6 +364,8 @@ state BAR:
'hidden_state' -> BAR_HIDDEN_STATE 'hidden_state' -> BAR_HIDDEN_STATE
'id' -> BAR_ID 'id' -> BAR_ID
'modifier' -> BAR_MODIFIER 'modifier' -> BAR_MODIFIER
'wheel_up_cmd' -> BAR_WHEEL_UP_CMD
'wheel_down_cmd' -> BAR_WHEEL_DOWN_CMD
'position' -> BAR_POSITION 'position' -> BAR_POSITION
'output' -> BAR_OUTPUT 'output' -> BAR_OUTPUT
'tray_output' -> BAR_TRAY_OUTPUT 'tray_output' -> BAR_TRAY_OUTPUT
@ -403,6 +411,14 @@ state BAR_MODIFIER:
modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift' modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift'
-> call cfg_bar_modifier($modifier); BAR -> call cfg_bar_modifier($modifier); BAR
state BAR_WHEEL_UP_CMD:
command = string
-> call cfg_bar_wheel_up_cmd($command); BAR
state BAR_WHEEL_DOWN_CMD:
command = string
-> call cfg_bar_wheel_down_cmd($command); BAR
state BAR_POSITION: state BAR_POSITION:
position = 'top', 'bottom' position = 'top', 'bottom'
-> call cfg_bar_position($position); BAR -> call cfg_bar_position($position); BAR
@ -412,7 +428,7 @@ state BAR_OUTPUT:
-> call cfg_bar_output($output); BAR -> call cfg_bar_output($output); BAR
state BAR_TRAY_OUTPUT: state BAR_TRAY_OUTPUT:
output = string output = word
-> call cfg_bar_tray_output($output); BAR -> call cfg_bar_tray_output($output); BAR
state BAR_FONT: state BAR_FONT:

View File

@ -8,6 +8,8 @@
*/ */
#include "all.h" #include "all.h"
#include <xkbcommon/xkbcommon.h>
pid_t command_error_nagbar_pid = -1; pid_t command_error_nagbar_pid = -1;
/* /*
@ -47,10 +49,11 @@ static struct Mode *mode_from_name(const char *name) {
* *
*/ */
Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code, Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code,
const char *release, const char *command, const char *modename) { const char *release, const char *whole_window, const char *command, const char *modename) {
Binding *new_binding = scalloc(sizeof(Binding)); Binding *new_binding = scalloc(sizeof(Binding));
DLOG("bindtype %s, modifiers %s, input code %s, release %s\n", bindtype, modifiers, input_code, release); DLOG("bindtype %s, modifiers %s, input code %s, release %s\n", bindtype, modifiers, input_code, release);
new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS); new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS);
new_binding->whole_window = (whole_window != NULL);
if (strcmp(bindtype, "bindsym") == 0) { if (strcmp(bindtype, "bindsym") == 0) {
new_binding->input_type = (strncasecmp(input_code, "button", (sizeof("button") - 1)) == 0 new_binding->input_type = (strncasecmp(input_code, "button", (sizeof("button") - 1)) == 0
? B_MOUSE ? B_MOUSE
@ -263,8 +266,8 @@ void translate_keysyms(void) {
continue; continue;
/* We need to translate the symbol to a keycode */ /* We need to translate the symbol to a keycode */
keysym = XStringToKeysym(bind->symbol); keysym = xkb_keysym_from_name(bind->symbol, XKB_KEYSYM_NO_FLAGS);
if (keysym == NoSymbol) { if (keysym == XKB_KEY_NoSymbol) {
ELOG("Could not translate string to key symbol: \"%s\"\n", ELOG("Could not translate string to key symbol: \"%s\"\n",
bind->symbol); bind->symbol);
continue; continue;
@ -379,18 +382,57 @@ void check_for_duplicate_bindings(struct context *context) {
} }
/* /*
* Runs the given binding and handles parse errors. Returns a CommandResult for * Creates a dynamically allocated copy of bind.
* running the binding's command. Caller should render tree if */
* needs_tree_render is true. Free with command_result_free(). static Binding *binding_copy(Binding *bind) {
Binding *ret = smalloc(sizeof(Binding));
*ret = *bind;
if (bind->symbol != NULL)
ret->symbol = strdup(bind->symbol);
if (bind->command != NULL)
ret->command = strdup(bind->command);
if (bind->translated_to != NULL) {
ret->translated_to = smalloc(sizeof(xcb_keycode_t) * bind->number_keycodes);
memcpy(ret->translated_to, bind->translated_to, sizeof(xcb_keycode_t) * bind->number_keycodes);
}
return ret;
}
/*
* Frees the binding. If bind is null, it simply returns.
*/
void binding_free(Binding *bind) {
if (bind == NULL) {
return;
}
FREE(bind->symbol);
FREE(bind->translated_to);
FREE(bind->command);
FREE(bind);
}
/*
* Runs the given binding and handles parse errors. If con is passed, it will
* execute the command binding with that container selected by criteria.
* Returns a CommandResult for running the binding's command. Caller should
* render tree if needs_tree_render is true. Free with command_result_free().
* *
*/ */
CommandResult *run_binding(Binding *bind) { CommandResult *run_binding(Binding *bind, Con *con) {
/* We need to copy the command since “reload” may be part of the command, char *command;
* and then the memory that bind->command points to may not contain the
/* We need to copy the binding and command since “reload” may be part of
* the command, and then the memory that bind points to may not contain the
* same data anymore. */ * same data anymore. */
char *command_copy = sstrdup(bind->command); if (con == NULL)
CommandResult *result = parse_command(command_copy, NULL); command = sstrdup(bind->command);
free(command_copy); else
sasprintf(&command, "[con_id=\"%d\"] %s", con, bind->command);
Binding *bind_cp = binding_copy(bind);
CommandResult *result = parse_command(command, NULL);
free(command);
if (result->needs_tree_render) if (result->needs_tree_render)
tree_render(); tree_render();
@ -414,7 +456,8 @@ CommandResult *run_binding(Binding *bind) {
free(pageraction); free(pageraction);
} }
/* TODO: emit event for running a binding */ ipc_send_binding_event("run", bind_cp);
binding_free(bind_cp);
return result; return result;
} }

View File

@ -177,6 +177,34 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
if (con->parent->type == CT_DOCKAREA) if (con->parent->type == CT_DOCKAREA)
goto done; goto done;
/* if the user has bound an action to this click, it should override the
* default behavior. */
if (dest == CLICK_DECORATION || dest == CLICK_INSIDE) {
Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event);
/* clicks over a window decoration will always trigger the binding and
* clicks on the inside of the window will only trigger a binding if
* the --whole-window flag was given for the binding. */
if (bind && (dest == CLICK_DECORATION || bind->whole_window)) {
CommandResult *result = run_binding(bind, con);
/* ASYNC_POINTER eats the event */
xcb_allow_events(conn, XCB_ALLOW_ASYNC_POINTER, event->time);
xcb_flush(conn);
if (result->needs_tree_render)
tree_render();
command_result_free(result);
return 0;
}
}
/* There is no default behavior for button release events so we are done. */
if (event->response_type == XCB_BUTTON_RELEASE) {
goto done;
}
/* Any click in a workspace should focus that workspace. If the /* Any click in a workspace should focus that workspace. If the
* workspace is on another output we need to do a workspace_show in * workspace is on another output we need to do a workspace_show in
* order for i3bar (and others) to notice the change in workspace. */ * order for i3bar (and others) to notice the change in workspace. */
@ -191,7 +219,6 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
if (ws != focused_workspace) if (ws != focused_workspace)
workspace_show(ws); workspace_show(ws);
focused_id = XCB_NONE;
/* get the floating con */ /* get the floating con */
Con *floatingcon = con_inside_floating(con); Con *floatingcon = con_inside_floating(con);
@ -300,6 +327,7 @@ done:
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn); xcb_flush(conn);
tree_render(); tree_render();
return 0; return 0;
} }
@ -313,9 +341,10 @@ done:
*/ */
int handle_button_press(xcb_button_press_event_t *event) { int handle_button_press(xcb_button_press_event_t *event) {
Con *con; Con *con;
DLOG("Button %d pressed on window 0x%08x (child 0x%08x) at (%d, %d) (root %d, %d)\n", DLOG("Button %d %s on window 0x%08x (child 0x%08x) at (%d, %d) (root %d, %d)\n",
event->state, event->event, event->child, event->event_x, event->event_y, event->state, (event->response_type == XCB_BUTTON_PRESS ? "press" : "release"),
event->root_x, event->root_y); event->event, event->child, event->event_x, event->event_y, event->root_x,
event->root_y);
last_timestamp = event->time; last_timestamp = event->time;
@ -328,7 +357,7 @@ int handle_button_press(xcb_button_press_event_t *event) {
if (!(con = con_by_frame_id(event->event))) { if (!(con = con_by_frame_id(event->event))) {
/* If the root window is clicked, find the relevant output from the /* If the root window is clicked, find the relevant output from the
* click coordinates and focus the output's active workspace. */ * click coordinates and focus the output's active workspace. */
if (event->event == root) { if (event->event == root && event->response_type == XCB_BUTTON_PRESS) {
Con *output, *ws; Con *output, *ws;
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
if (con_is_internal(output) || if (con_is_internal(output) ||

View File

@ -513,7 +513,27 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
LOG("should move window to workspace %s\n", name); LOG("should move window to workspace %s\n", name);
/* get the workspace */ /* get the workspace */
Con *ws = workspace_get(name, NULL); Con *ws = NULL;
Con *output = NULL;
/* first look for a workspace with this name */
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
GREP_FIRST(ws, output_get_content(output), !strcasecmp(child->name, name));
}
/* if the name is plain digits, we interpret this as a "workspace number"
* command */
if (!ws && name_is_digits(name)) {
long parsed_num = ws_name_to_number(name);
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
GREP_FIRST(ws, output_get_content(output),
child->num == parsed_num);
}
}
/* if no workspace was found, make a new one */
if (!ws)
ws = workspace_get(name, NULL);
ws = maybe_auto_back_and_forth_workspace(ws); ws = maybe_auto_back_and_forth_workspace(ws);
@ -550,12 +570,9 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
/* get the workspace */ /* get the workspace */
Con *output, *workspace = NULL; Con *output, *workspace = NULL;
char *endptr = NULL; long parsed_num = ws_name_to_number(which);
long parsed_num = strtol(which, &endptr, 10);
if (parsed_num == LONG_MIN || if (parsed_num == -1) {
parsed_num == LONG_MAX ||
parsed_num < 0 ||
endptr == which) {
LOG("Could not parse initial part of \"%s\" as a number.\n", which); LOG("Could not parse initial part of \"%s\" as a number.\n", which);
// TODO: better error message // TODO: better error message
yerror("Could not parse number"); yerror("Could not parse number");
@ -927,7 +944,7 @@ void cmd_append_layout(I3_CMD, char *path) {
restore_open_placeholder_windows(parent); restore_open_placeholder_windows(parent);
if (content == JSON_CONTENT_WORKSPACE) if (content == JSON_CONTENT_WORKSPACE)
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"restored\"}"); ipc_send_workspace_event("restored", parent, NULL);
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
} }
@ -941,6 +958,12 @@ void cmd_workspace(I3_CMD, char *which) {
DLOG("which=%s\n", which); DLOG("which=%s\n", which);
if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
LOG("Cannot switch workspace while in global fullscreen\n");
ysuccess(false);
return;
}
if (strcmp(which, "next") == 0) if (strcmp(which, "next") == 0)
ws = workspace_next(); ws = workspace_next();
else if (strcmp(which, "prev") == 0) else if (strcmp(which, "prev") == 0)
@ -969,16 +992,18 @@ void cmd_workspace(I3_CMD, char *which) {
void cmd_workspace_number(I3_CMD, char *which) { void cmd_workspace_number(I3_CMD, char *which) {
Con *output, *workspace = NULL; Con *output, *workspace = NULL;
char *endptr = NULL; if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
long parsed_num = strtol(which, &endptr, 10); LOG("Cannot switch workspace while in global fullscreen\n");
if (parsed_num == LONG_MIN || ysuccess(false);
parsed_num == LONG_MAX || return;
parsed_num < 0 || }
endptr == which) {
long parsed_num = ws_name_to_number(which);
if (parsed_num == -1) {
LOG("Could not parse initial part of \"%s\" as a number.\n", which); LOG("Could not parse initial part of \"%s\" as a number.\n", which);
// TODO: better error message // TODO: better error message
yerror("Could not parse number"); yerror("Could not parse number");
return; return;
} }
@ -1007,6 +1032,12 @@ void cmd_workspace_number(I3_CMD, char *which) {
* *
*/ */
void cmd_workspace_back_and_forth(I3_CMD) { void cmd_workspace_back_and_forth(I3_CMD) {
if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
LOG("Cannot switch workspace while in global fullscreen\n");
ysuccess(false);
return;
}
workspace_back_and_forth(); workspace_back_and_forth();
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
@ -1025,10 +1056,39 @@ void cmd_workspace_name(I3_CMD, char *name) {
return; return;
} }
if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
LOG("Cannot switch workspace while in global fullscreen\n");
ysuccess(false);
return;
}
DLOG("should switch to workspace %s\n", name); DLOG("should switch to workspace %s\n", name);
if (maybe_back_and_forth(cmd_output, name)) if (maybe_back_and_forth(cmd_output, name))
return; return;
workspace_show_by_name(name);
Con *ws = NULL;
Con *output = NULL;
/* first look for a workspace with this name */
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
GREP_FIRST(ws, output_get_content(output), !strcasecmp(child->name, name));
}
/* if the name is only digits, we interpret this as a "workspace number"
* command */
if (!ws && name_is_digits(name)) {
long parsed_num = ws_name_to_number(name);
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
GREP_FIRST(ws, output_get_content(output),
child->num == parsed_num);
}
}
/* if no workspace was found, make a new one */
if (!ws)
ws = workspace_get(name, NULL);
workspace_show(ws);
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply // XXX: default reply for now, make this a better reply
@ -1253,7 +1313,7 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
create_workspace_on_output(current_output, ws->parent); create_workspace_on_output(current_output, ws->parent);
/* notify the IPC listeners */ /* notify the IPC listeners */
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); ipc_send_workspace_event("init", ws, NULL);
} }
DLOG("Detaching\n"); DLOG("Detaching\n");
@ -1274,7 +1334,7 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
TAILQ_FOREACH(floating_con, &(ws->floating_head), floating_windows) TAILQ_FOREACH(floating_con, &(ws->floating_head), floating_windows)
floating_fix_coordinates(floating_con, &(old_content->rect), &(content->rect)); floating_fix_coordinates(floating_con, &(old_content->rect), &(content->rect));
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"move\"}"); ipc_send_workspace_event("move", ws, NULL);
if (workspace_was_visible) { if (workspace_was_visible) {
/* Focus the moved workspace on the destination output. */ /* Focus the moved workspace on the destination output. */
workspace_show(ws); workspace_show(ws);
@ -1538,20 +1598,26 @@ void cmd_focus(I3_CMD) {
} }
/* /*
* Implementation of 'fullscreen [global]'. * Implementation of 'fullscreen enable|toggle [global]' and
* 'fullscreen disable'
* *
*/ */
void cmd_fullscreen(I3_CMD, char *fullscreen_mode) { void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode) {
if (fullscreen_mode == NULL) fullscreen_mode_t mode = strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT;
fullscreen_mode = "output"; DLOG("%s fullscreen, mode = %s\n", action, fullscreen_mode);
DLOG("toggling fullscreen, mode = %s\n", fullscreen_mode);
owindow *current; owindow *current;
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) { TAILQ_FOREACH(current, &owindows, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name); DLOG("matching: %p / %s\n", current->con, current->con->name);
con_toggle_fullscreen(current->con, (strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT)); if (strcmp(action, "toggle") == 0) {
con_toggle_fullscreen(current->con, mode);
} else if (strcmp(action, "enable") == 0) {
con_enable_fullscreen(current->con, mode);
} else if (strcmp(action, "disable") == 0) {
con_disable_fullscreen(current->con);
}
} }
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
@ -1567,11 +1633,16 @@ void cmd_move_direction(I3_CMD, char *direction, char *move_px) {
// TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking
int px = atoi(move_px); int px = atoi(move_px);
/* TODO: make 'move' work with criteria. */ owindow *current;
HANDLE_EMPTY_MATCH;
Con *initially_focused = focused;
TAILQ_FOREACH(current, &owindows, owindows) {
DLOG("moving in direction %s, px %s\n", direction, move_px); DLOG("moving in direction %s, px %s\n", direction, move_px);
if (con_is_floating(focused)) { if (con_is_floating(current->con)) {
DLOG("floating move with %d pixels\n", px); DLOG("floating move with %d pixels\n", px);
Rect newrect = focused->parent->rect; Rect newrect = current->con->parent->rect;
if (strcmp(direction, "left") == 0) { if (strcmp(direction, "left") == 0) {
newrect.x -= px; newrect.x -= px;
} else if (strcmp(direction, "right") == 0) { } else if (strcmp(direction, "right") == 0) {
@ -1581,11 +1652,16 @@ void cmd_move_direction(I3_CMD, char *direction, char *move_px) {
} else if (strcmp(direction, "down") == 0) { } else if (strcmp(direction, "down") == 0) {
newrect.y += px; newrect.y += px;
} }
floating_reposition(focused->parent, newrect); floating_reposition(current->con->parent, newrect);
} else { } else {
tree_move((strcmp(direction, "right") == 0 ? D_RIGHT : (strcmp(direction, "left") == 0 ? D_LEFT : (strcmp(direction, "up") == 0 ? D_UP : D_DOWN)))); tree_move(current->con, (strcmp(direction, "right") == 0 ? D_RIGHT : (strcmp(direction, "left") == 0 ? D_LEFT : (strcmp(direction, "up") == 0 ? D_UP : D_DOWN))));
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
} }
}
/* the move command should not disturb focus */
if (focused != initially_focused)
con_focus(initially_focused);
// XXX: default reply for now, make this a better reply // XXX: default reply for now, make this a better reply
ysuccess(true); ysuccess(true);
@ -1685,7 +1761,7 @@ void cmd_reload(I3_CMD) {
load_configuration(conn, NULL, true); load_configuration(conn, NULL, true);
x_set_i3_atoms(); x_set_i3_atoms();
/* Send an IPC event just in case the ws names have changed */ /* Send an IPC event just in case the ws names have changed */
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}"); ipc_send_workspace_event("reload", NULL, NULL);
/* Send an update event for the barconfig just in case it has changed */ /* Send an update event for the barconfig just in case it has changed */
update_barconfig(); update_barconfig();
@ -1779,33 +1855,45 @@ void cmd_focus_output(I3_CMD, char *name) {
void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) { void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) {
int x = atoi(cx); int x = atoi(cx);
int y = atoi(cy); int y = atoi(cy);
bool has_error = false;
if (!con_is_floating(focused)) { owindow *current;
HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) {
if (!con_is_floating(current->con)) {
ELOG("Cannot change position. The window/container is not floating\n"); ELOG("Cannot change position. The window/container is not floating\n");
yerror("Cannot change position. The window/container is not floating.");
return; if (!has_error) {
yerror("Cannot change position of a window/container because it is not floating.");
has_error = true;
}
continue;
} }
if (strcmp(method, "absolute") == 0) { if (strcmp(method, "absolute") == 0) {
focused->parent->rect.x = x; current->con->parent->rect.x = x;
focused->parent->rect.y = y; current->con->parent->rect.y = y;
DLOG("moving to absolute position %d %d\n", x, y); DLOG("moving to absolute position %d %d\n", x, y);
floating_maybe_reassign_ws(focused->parent); floating_maybe_reassign_ws(current->con->parent);
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
} }
if (strcmp(method, "position") == 0) { if (strcmp(method, "position") == 0) {
Rect newrect = focused->parent->rect; Rect newrect = current->con->parent->rect;
DLOG("moving to position %d %d\n", x, y); DLOG("moving to position %d %d\n", x, y);
newrect.x = x; newrect.x = x;
newrect.y = y; newrect.y = y;
floating_reposition(focused->parent, newrect); floating_reposition(current->con->parent, newrect);
}
} }
// XXX: default reply for now, make this a better reply // XXX: default reply for now, make this a better reply
if (!has_error)
ysuccess(true); ysuccess(true);
} }
@ -1937,15 +2025,8 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
/* Change the name and try to parse it as a number. */ /* Change the name and try to parse it as a number. */
FREE(workspace->name); FREE(workspace->name);
workspace->name = sstrdup(new_name); workspace->name = sstrdup(new_name);
char *endptr = NULL;
long parsed_num = strtol(new_name, &endptr, 10); workspace->num = ws_name_to_number(new_name);
if (parsed_num == LONG_MIN ||
parsed_num == LONG_MAX ||
parsed_num < 0 ||
endptr == new_name)
workspace->num = -1;
else
workspace->num = parsed_num;
LOG("num = %d\n", workspace->num); LOG("num = %d\n", workspace->num);
/* By re-attaching, the sort order will be correct afterwards. */ /* By re-attaching, the sort order will be correct afterwards. */
@ -1959,7 +2040,10 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
ysuccess(true); ysuccess(true);
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"rename\"}"); ipc_send_workspace_event("rename", workspace, NULL);
ewmh_update_desktop_names();
ewmh_update_desktop_viewport();
ewmh_update_current_desktop();
} }
/* /*

View File

@ -204,6 +204,61 @@ static void next_state(const cmdp_token *token) {
} }
} }
/*
* Parses a string (or word, if as_word is true). Extracted out of
* parse_command so that it can be used in src/workspace.c for interpreting
* workspace commands.
*
*/
char *parse_string(const char **walk, bool as_word) {
const char *beginning = *walk;
/* Handle quoted strings (or words). */
if (**walk == '"') {
beginning++;
(*walk)++;
while (**walk != '\0' && (**walk != '"' || *(*walk - 1) == '\\'))
(*walk)++;
} else {
if (!as_word) {
/* For a string (starting with 's'), the delimiters are
* comma (,) and semicolon (;) which introduce a new
* operation or command, respectively. Also, newlines
* end a command. */
while (**walk != ';' && **walk != ',' &&
**walk != '\0' && **walk != '\r' &&
**walk != '\n')
(*walk)++;
} else {
/* For a word, the delimiters are white space (' ' or
* '\t'), closing square bracket (]), comma (,) and
* semicolon (;). */
while (**walk != ' ' && **walk != '\t' &&
**walk != ']' && **walk != ',' &&
**walk != ';' && **walk != '\r' &&
**walk != '\n' && **walk != '\0')
(*walk)++;
}
}
if (*walk == beginning)
return NULL;
char *str = scalloc(*walk - beginning + 1);
/* We copy manually to handle escaping of characters. */
int inpos, outpos;
for (inpos = 0, outpos = 0;
inpos < (*walk - beginning);
inpos++, outpos++) {
/* We only handle escaped double quotes to not break
* backwards compatibility with people using \w in
* regular expressions etc. */
if (beginning[inpos] == '\\' && beginning[inpos + 1] == '"')
inpos++;
str[outpos] = beginning[inpos];
}
return str;
}
/* /*
* Parses and executes the given command. If a caller-allocated yajl_gen is * Parses and executes the given command. If a caller-allocated yajl_gen is
* passed, a json reply will be generated in the format specified by the ipc * passed, a json reply will be generated in the format specified by the ipc
@ -262,48 +317,8 @@ CommandResult *parse_command(const char *input, yajl_gen gen) {
if (strcmp(token->name, "string") == 0 || if (strcmp(token->name, "string") == 0 ||
strcmp(token->name, "word") == 0) { strcmp(token->name, "word") == 0) {
const char *beginning = walk; char *str = parse_string(&walk, (token->name[0] != 's'));
/* Handle quoted strings (or words). */ if (str != NULL) {
if (*walk == '"') {
beginning++;
walk++;
while (*walk != '\0' && (*walk != '"' || *(walk - 1) == '\\'))
walk++;
} else {
if (token->name[0] == 's') {
/* For a string (starting with 's'), the delimiters are
* comma (,) and semicolon (;) which introduce a new
* operation or command, respectively. Also, newlines
* end a command. */
while (*walk != ';' && *walk != ',' &&
*walk != '\0' && *walk != '\r' &&
*walk != '\n')
walk++;
} else {
/* For a word, the delimiters are white space (' ' or
* '\t'), closing square bracket (]), comma (,) and
* semicolon (;). */
while (*walk != ' ' && *walk != '\t' &&
*walk != ']' && *walk != ',' &&
*walk != ';' && *walk != '\r' &&
*walk != '\n' && *walk != '\0')
walk++;
}
}
if (walk != beginning) {
char *str = scalloc(walk - beginning + 1);
/* We copy manually to handle escaping of characters. */
int inpos, outpos;
for (inpos = 0, outpos = 0;
inpos < (walk - beginning);
inpos++, outpos++) {
/* We only handle escaped double quotes to not break
* backwards compatibility with people using \w in
* regular expressions etc. */
if (beginning[inpos] == '\\' && beginning[inpos + 1] == '"')
inpos++;
str[outpos] = beginning[inpos];
}
if (token->identifier) if (token->identifier)
push_string(token->identifier, str); push_string(token->identifier, str);
/* If we are at the end of a quoted string, skip the ending /* If we are at the end of a quoted string, skip the ending

153
src/con.c
View File

@ -12,18 +12,7 @@
* *
*/ */
#include "all.h" #include "all.h"
#include "yajl_utils.h"
char *colors[] = {
"#ff0000",
"#00FF00",
"#0000FF",
"#ff00ff",
"#00ffff",
"#ffff00",
"#aa0000",
"#00aa00",
"#0000aa",
"#aa00aa"};
static void con_on_remove_child(Con *con); static void con_on_remove_child(Con *con);
@ -31,7 +20,7 @@ static void con_on_remove_child(Con *con);
* force parent split containers to be redrawn * force parent split containers to be redrawn
* *
*/ */
static void con_force_split_parents_redraw(Con *con) { void con_force_split_parents_redraw(Con *con) {
Con *parent = con; Con *parent = con;
while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) { while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) {
@ -59,16 +48,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) {
new->depth = window->depth; new->depth = window->depth;
else else
new->depth = XCB_COPY_FROM_PARENT; new->depth = XCB_COPY_FROM_PARENT;
static int cnt = 0; DLOG("opening window\n");
DLOG("opening window %d\n", cnt);
/* TODO: remove window coloring after test-phase */
DLOG("color %s\n", colors[cnt]);
new->name = strdup(colors[cnt]);
//uint32_t cp = get_colorpixel(colors[cnt]);
cnt++;
if ((cnt % (sizeof(colors) / sizeof(char *))) == 0)
cnt = 0;
TAILQ_INIT(&(new->floating_head)); TAILQ_INIT(&(new->floating_head));
TAILQ_INIT(&(new->nodes_head)); TAILQ_INIT(&(new->nodes_head));
@ -231,6 +211,7 @@ void con_focus(Con *con) {
con->urgent = false; con->urgent = false;
con_update_parents_urgency(con); con_update_parents_urgency(con);
workspace_update_urgent_flag(con_get_workspace(con)); workspace_update_urgent_flag(con_get_workspace(con));
ipc_send_window_event("urgent", con);
} }
} }
@ -585,37 +566,27 @@ void con_fix_percent(Con *con) {
* *
*/ */
void con_toggle_fullscreen(Con *con, int fullscreen_mode) { void con_toggle_fullscreen(Con *con, int fullscreen_mode) {
Con *workspace, *fullscreen;
if (con->type == CT_WORKSPACE) { if (con->type == CT_WORKSPACE) {
DLOG("You cannot make a workspace fullscreen.\n"); DLOG("You cannot make a workspace fullscreen.\n");
return; return;
} }
DLOG("toggling fullscreen for %p / %s\n", con, con->name); DLOG("toggling fullscreen for %p / %s\n", con, con->name);
if (con->fullscreen_mode == CF_NONE) {
/* 1: check if there already is a fullscreen con */ if (con->fullscreen_mode == CF_NONE)
if (fullscreen_mode == CF_GLOBAL) con_enable_fullscreen(con, fullscreen_mode);
fullscreen = con_get_fullscreen_con(croot, CF_GLOBAL); else
else { con_disable_fullscreen(con);
workspace = con_get_workspace(con);
fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
}
if (fullscreen != NULL) {
/* Disable fullscreen for the currently fullscreened
* container and enable it for the one the user wants
* to have in fullscreen mode. */
LOG("Disabling fullscreen for (%p/%s) upon user request\n",
fullscreen, fullscreen->name);
fullscreen->fullscreen_mode = CF_NONE;
} }
/* 2: enable fullscreen */ /*
* Sets the specified fullscreen mode for the given container, sends the
* fullscreen_mode event and changes the XCB fullscreen property of the
* containers window, if any.
*
*/
static void con_set_fullscreen_mode(Con *con, fullscreen_mode_t fullscreen_mode) {
con->fullscreen_mode = fullscreen_mode; con->fullscreen_mode = fullscreen_mode;
} else {
/* 1: disable fullscreen */
con->fullscreen_mode = CF_NONE;
}
DLOG("mode now: %d\n", con->fullscreen_mode); DLOG("mode now: %d\n", con->fullscreen_mode);
@ -638,6 +609,79 @@ void con_toggle_fullscreen(Con *con, int fullscreen_mode) {
A__NET_WM_STATE, XCB_ATOM_ATOM, 32, num, values); A__NET_WM_STATE, XCB_ATOM_ATOM, 32, num, values);
} }
/*
* Enables fullscreen mode for the given container, if necessary.
*
* If the containers mode is already CF_OUTPUT or CF_GLOBAL, the container is
* kept fullscreen but its mode is set to CF_GLOBAL and CF_OUTPUT,
* respectively.
*
* Other fullscreen containers will be disabled first, if they hide the new
* one.
*
*/
void con_enable_fullscreen(Con *con, fullscreen_mode_t fullscreen_mode) {
if (con->type == CT_WORKSPACE) {
DLOG("You cannot make a workspace fullscreen.\n");
return;
}
assert(fullscreen_mode == CF_GLOBAL || fullscreen_mode == CF_OUTPUT);
if (fullscreen_mode == CF_GLOBAL)
DLOG("enabling global fullscreen for %p / %s\n", con, con->name);
else
DLOG("enabling fullscreen for %p / %s\n", con, con->name);
if (con->fullscreen_mode == fullscreen_mode) {
DLOG("fullscreen already enabled for %p / %s\n", con, con->name);
return;
}
Con *con_ws = con_get_workspace(con);
/* Disable any fullscreen container that would conflict the new one. */
Con *fullscreen = con_get_fullscreen_con(croot, CF_GLOBAL);
if (fullscreen == NULL)
fullscreen = con_get_fullscreen_con(con_ws, CF_OUTPUT);
if (fullscreen != NULL)
con_disable_fullscreen(fullscreen);
/* Set focus to new fullscreen container. Unless in global fullscreen mode
* and on another workspace restore focus afterwards.
* Switch to the containers workspace if mode is global. */
Con *cur_ws = con_get_workspace(focused);
Con *old_focused = focused;
if (fullscreen_mode == CF_GLOBAL && cur_ws != con_ws)
workspace_show(con_ws);
con_focus(con);
if (fullscreen_mode != CF_GLOBAL && cur_ws != con_ws)
con_focus(old_focused);
con_set_fullscreen_mode(con, fullscreen_mode);
}
/*
* Disables fullscreen mode for the given container regardless of the mode, if
* necessary.
*
*/
void con_disable_fullscreen(Con *con) {
if (con->type == CT_WORKSPACE) {
DLOG("You cannot make a workspace fullscreen.\n");
return;
}
DLOG("disabling fullscreen for %p / %s\n", con, con->name);
if (con->fullscreen_mode == CF_NONE) {
DLOG("fullscreen already disabled for %p / %s\n", con, con->name);
return;
}
con_set_fullscreen_mode(con, CF_NONE);
}
/* /*
* Moves the given container to the currently focused container on the given * Moves the given container to the currently focused container on the given
* workspace. * workspace.
@ -832,6 +876,8 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
} }
CALL(parent, on_remove_child); CALL(parent, on_remove_child);
ipc_send_window_event("move", con);
} }
/* /*
@ -1390,8 +1436,15 @@ static void con_on_remove_child(Con *con) {
if (con->type == CT_WORKSPACE) { if (con->type == CT_WORKSPACE) {
if (TAILQ_EMPTY(&(con->focus_head)) && !workspace_is_visible(con)) { if (TAILQ_EMPTY(&(con->focus_head)) && !workspace_is_visible(con)) {
LOG("Closing old workspace (%p / %s), it is empty\n", con, con->name); LOG("Closing old workspace (%p / %s), it is empty\n", con, con->name);
yajl_gen gen = ipc_marshal_workspace_event("empty", con, NULL);
tree_close(con, DONT_KILL_WINDOW, false, false); tree_close(con, DONT_KILL_WINDOW, false, false);
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
const unsigned char *payload;
ylength length;
y(get_buf, &payload, &length);
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, (const char *)payload);
y(free);
} }
return; return;
} }
@ -1603,14 +1656,16 @@ void con_set_urgency(Con *con, bool urgent) {
con_update_parents_urgency(con); con_update_parents_urgency(con);
if (con->urgent == urgent)
LOG("Urgency flag changed to %d\n", con->urgent);
Con *ws; Con *ws;
/* Set the urgency flag on the workspace, if a workspace could be found /* Set the urgency flag on the workspace, if a workspace could be found
* (for dock clients, that is not the case). */ * (for dock clients, that is not the case). */
if ((ws = con_get_workspace(con)) != NULL) if ((ws = con_get_workspace(con)) != NULL)
workspace_update_urgent_flag(ws); workspace_update_urgent_flag(ws);
if (con->urgent == urgent) {
LOG("Urgency flag changed to %d\n", con->urgent);
ipc_send_window_event("urgent", con);
}
} }
/* /*

View File

@ -11,9 +11,7 @@
* *
*/ */
#include "all.h" #include "all.h"
#include <xkbcommon/xkbcommon.h>
/* We need Xlib for XStringToKeysym */
#include <X11/Xlib.h>
char *current_configpath = NULL; char *current_configpath = NULL;
Config config; Config config;
@ -114,12 +112,19 @@ static char *get_config_path(const char *override_configpath) {
* parse_file(). * parse_file().
* *
*/ */
static void parse_configuration(const char *override_configpath) { bool parse_configuration(const char *override_configpath, bool use_nagbar) {
char *path = get_config_path(override_configpath); char *path = get_config_path(override_configpath);
LOG("Parsing configfile %s\n", path); LOG("Parsing configfile %s\n", path);
FREE(current_configpath); FREE(current_configpath);
current_configpath = path; current_configpath = path;
parse_file(path);
/* initialize default bindings if we're just validating the config file */
if (!use_nagbar && bindings == NULL) {
bindings = scalloc(sizeof(struct bindings_head));
TAILQ_INIT(bindings);
}
return parse_file(path, use_nagbar);
} }
/* /*
@ -142,9 +147,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
while (!TAILQ_EMPTY(bindings)) { while (!TAILQ_EMPTY(bindings)) {
bind = TAILQ_FIRST(bindings); bind = TAILQ_FIRST(bindings);
TAILQ_REMOVE(bindings, bind, bindings); TAILQ_REMOVE(bindings, bind, bindings);
FREE(bind->translated_to); binding_free(bind);
FREE(bind->command);
FREE(bind);
} }
FREE(bindings); FREE(bindings);
SLIST_REMOVE(&modes, mode, Mode, modes); SLIST_REMOVE(&modes, mode, Mode, modes);
@ -262,7 +265,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
if (config.workspace_urgency_timer == 0) if (config.workspace_urgency_timer == 0)
config.workspace_urgency_timer = 0.5; config.workspace_urgency_timer = 0.5;
parse_configuration(override_configpath); parse_configuration(override_configpath, true);
if (reload) { if (reload) {
translate_keysyms(); translate_keysyms();

View File

@ -171,8 +171,8 @@ CFGFUN(font, const char *font) {
font_pattern = sstrdup(font); font_pattern = sstrdup(font);
} }
CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command) { CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *whole_window, const char *command) {
configure_binding(bindtype, modifiers, key, release, command, DEFAULT_BINDING_MODE); configure_binding(bindtype, modifiers, key, release, whole_window, command, DEFAULT_BINDING_MODE);
} }
/******************************************************************************* /*******************************************************************************
@ -181,8 +181,8 @@ CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, co
static char *current_mode; static char *current_mode;
CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command) { CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *whole_window, const char *command) {
configure_binding(bindtype, modifiers, key, release, command, current_mode); configure_binding(bindtype, modifiers, key, release, whole_window, command, current_mode);
} }
CFGFUN(enter_mode, const char *modename) { CFGFUN(enter_mode, const char *modename) {
@ -271,13 +271,15 @@ CFGFUN(new_window, const char *windowtype, const char *border, const long width)
} }
if (strcmp(windowtype, "new_window") == 0) { if (strcmp(windowtype, "new_window") == 0) {
DLOG("default tiled border style = %d and border width = %d\n", border_style, border_width); DLOG("default tiled border style = %d and border width = %d (%d physical px)\n",
border_style, border_width, logical_px(border_width));
config.default_border = border_style; config.default_border = border_style;
config.default_border_width = border_width; config.default_border_width = logical_px(border_width);
} else { } else {
DLOG("default floating border style = %d and border width = %d\n", border_style, border_width); DLOG("default floating border style = %d and border width = %d (%d physical px)\n",
border_style, border_width, logical_px(border_width));
config.default_floating_border = border_style; config.default_floating_border = border_style;
config.default_floating_border_width = border_width; config.default_floating_border_width = logical_px(border_width);
} }
} }
@ -460,6 +462,16 @@ CFGFUN(bar_modifier, const char *modifier) {
current_bar.modifier = M_SHIFT; current_bar.modifier = M_SHIFT;
} }
CFGFUN(bar_wheel_up_cmd, const char *command) {
FREE(current_bar.wheel_up_cmd);
current_bar.wheel_up_cmd = sstrdup(command);
}
CFGFUN(bar_wheel_down_cmd, const char *command) {
FREE(current_bar.wheel_down_cmd);
current_bar.wheel_down_cmd = sstrdup(command);
}
CFGFUN(bar_position, const char *position) { CFGFUN(bar_position, const char *position) {
current_bar.position = (strcmp(position, "top") == 0 ? P_TOP : P_BOTTOM); current_bar.position = (strcmp(position, "top") == 0 ? P_TOP : P_BOTTOM);
} }

View File

@ -840,7 +840,7 @@ static char *migrate_config(char *input, off_t size) {
* parse_config and possibly launching i3-nagbar. * parse_config and possibly launching i3-nagbar.
* *
*/ */
void parse_file(const char *f) { bool parse_file(const char *f, bool use_nagbar) {
SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables); SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
int fd, ret, read_bytes = 0; int fd, ret, read_bytes = 0;
struct stat stbuf; struct stat stbuf;
@ -1000,7 +1000,7 @@ void parse_file(const char *f) {
check_for_duplicate_bindings(context); check_for_duplicate_bindings(context);
if (context->has_errors || context->has_warnings) { if (use_nagbar && (context->has_errors || context->has_warnings)) {
ELOG("FYI: You are using i3 version " I3_VERSION "\n"); ELOG("FYI: You are using i3 version " I3_VERSION "\n");
if (version == 3) if (version == 3)
ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n"); ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n");
@ -1030,6 +1030,8 @@ void parse_file(const char *f) {
free(pageraction); free(pageraction);
} }
bool has_errors = context->has_errors;
FREE(context->line_copy); FREE(context->line_copy);
free(context); free(context);
free(new); free(new);
@ -1042,6 +1044,8 @@ void parse_file(const char *f) {
SLIST_REMOVE_HEAD(&variables, variables); SLIST_REMOVE_HEAD(&variables, variables);
FREE(current); FREE(current);
} }
return !has_errors;
} }
#endif #endif

View File

@ -40,6 +40,102 @@ void ewmh_update_current_desktop(void) {
} }
} }
/*
* Updates _NET_NUMBER_OF_DESKTOPS which we interpret as the number of
* noninternal workspaces.
*/
void ewmh_update_number_of_desktops(void) {
Con *output;
uint32_t idx = 0;
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *ws;
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
if (STARTS_WITH(ws->name, "__"))
continue;
++idx;
}
}
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
A__NET_NUMBER_OF_DESKTOPS, XCB_ATOM_CARDINAL, 32, 1, &idx);
}
/*
* Updates _NET_DESKTOP_NAMES: "The names of all virtual desktops. This is a
* list of NULL-terminated strings in UTF-8 encoding"
*/
void ewmh_update_desktop_names(void) {
Con *output;
int msg_length = 0;
/* count the size of the property message to set */
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *ws;
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
if (STARTS_WITH(ws->name, "__"))
continue;
msg_length += strlen(ws->name) + 1;
}
}
char desktop_names[msg_length];
int current_position = 0;
/* fill the buffer with the names of the i3 workspaces */
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *ws;
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
if (STARTS_WITH(ws->name, "__"))
continue;
for (size_t i = 0; i < strlen(ws->name) + 1; i++) {
desktop_names[current_position++] = ws->name[i];
}
}
}
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
A__NET_DESKTOP_NAMES, A_UTF8_STRING, 8, msg_length, desktop_names);
}
/*
* Updates _NET_DESKTOP_VIEWPORT, which is an array of pairs of cardinals that
* define the top left corner of each desktop's viewport.
*/
void ewmh_update_desktop_viewport(void) {
Con *output;
int num_desktops = 0;
/* count number of desktops */
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *ws;
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
if (STARTS_WITH(ws->name, "__"))
continue;
num_desktops++;
}
}
uint32_t viewports[num_desktops * 2];
int current_position = 0;
/* fill the viewport buffer */
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *ws;
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
if (STARTS_WITH(ws->name, "__"))
continue;
viewports[current_position++] = output->rect.x;
viewports[current_position++] = output->rect.y;
}
}
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
A__NET_DESKTOP_VIEWPORT, XCB_ATOM_CARDINAL, 32, current_position, &viewports);
}
/* /*
* Updates _NET_ACTIVE_WINDOW with the currently focused window. * Updates _NET_ACTIVE_WINDOW with the currently focused window.
* *
@ -138,5 +234,6 @@ void ewmh_setup_hints(void) {
/* Im not entirely sure if we need to keep _NET_WM_NAME on root. */ /* Im not entirely sure if we need to keep _NET_WM_NAME on root. */
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3"); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3");
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 19, supported_atoms); /* only send the first 24 atoms (last one is _NET_CLOSE_WINDOW) increment that number when adding supported atoms */
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 24, supported_atoms);
} }

View File

@ -298,16 +298,22 @@ void floating_enable(Con *con, bool automatic) {
/* Check if we need to re-assign it to a different workspace because of its /* Check if we need to re-assign it to a different workspace because of its
* coordinates and exit if that was done successfully. */ * coordinates and exit if that was done successfully. */
if (floating_maybe_reassign_ws(nc)) if (floating_maybe_reassign_ws(nc)) {
ipc_send_window_event("floating", con);
return; return;
}
/* Sanitize coordinates: Check if they are on any output */ /* Sanitize coordinates: Check if they are on any output */
if (get_output_containing(nc->rect.x, nc->rect.y) != NULL) if (get_output_containing(nc->rect.x, nc->rect.y) != NULL) {
ipc_send_window_event("floating", con);
return; return;
}
ELOG("No output found at destination coordinates, centering floating window on current ws\n"); ELOG("No output found at destination coordinates, centering floating window on current ws\n");
nc->rect.x = ws->rect.x + (ws->rect.width / 2) - (nc->rect.width / 2); nc->rect.x = ws->rect.x + (ws->rect.width / 2) - (nc->rect.width / 2);
nc->rect.y = ws->rect.y + (ws->rect.height / 2) - (nc->rect.height / 2); nc->rect.y = ws->rect.y + (ws->rect.height / 2) - (nc->rect.height / 2);
ipc_send_window_event("floating", con);
} }
void floating_disable(Con *con, bool automatic) { void floating_disable(Con *con, bool automatic) {
@ -351,6 +357,8 @@ void floating_disable(Con *con, bool automatic) {
if (set_focus) if (set_focus)
con_focus(con); con_focus(con);
ipc_send_window_event("floating", con);
} }
/* /*

View File

@ -21,6 +21,8 @@
#include <libsn/sn-monitor.h> #include <libsn/sn-monitor.h>
int randr_base = -1; int randr_base = -1;
int xkb_base = -1;
int xkb_current_group;
/* After mapping/unmapping windows, a notify event is generated. However, we dont want it, /* After mapping/unmapping windows, a notify event is generated. However, we dont want it,
since itd trigger an infinite loop of switching between the different windows when since itd trigger an infinite loop of switching between the different windows when
@ -282,7 +284,6 @@ static void handle_map_request(xcb_map_request_event_t *event) {
add_ignore_event(event->sequence, -1); add_ignore_event(event->sequence, -1);
manage_window(event->window, cookie, false); manage_window(event->window, cookie, false);
x_push_changes(croot);
return; return;
} }
@ -487,7 +488,6 @@ static void handle_unmap_notify_event(xcb_unmap_notify_event_t *event) {
tree_close(con, DONT_KILL_WINDOW, false, false); tree_close(con, DONT_KILL_WINDOW, false, false);
tree_render(); tree_render();
x_push_changes(croot);
ignore_end: ignore_end:
/* If the client (as opposed to i3) destroyed or unmapped a window, an /* If the client (as opposed to i3) destroyed or unmapped a window, an
@ -651,6 +651,19 @@ static void handle_expose_event(xcb_expose_event_t *event) {
return; return;
} }
#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0
#define _NET_WM_MOVERESIZE_SIZE_TOP 1
#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2
#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3
#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4
#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5
#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6
#define _NET_WM_MOVERESIZE_SIZE_LEFT 7
#define _NET_WM_MOVERESIZE_MOVE 8 /* movement only */
#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 /* size via keyboard */
#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */
#define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */
/* /*
* Handle client messages (EWMH) * Handle client messages (EWMH)
* *
@ -791,6 +804,98 @@ static void handle_client_message(xcb_client_message_event_t *event) {
XCB_ATOM_CARDINAL, 32, 4, XCB_ATOM_CARDINAL, 32, 4,
&r); &r);
xcb_flush(conn); xcb_flush(conn);
} else if (event->type == A_WM_CHANGE_STATE) {
/* http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.4 */
Con *con = con_by_window_id(event->window);
if (con && event->data.data32[0] == 3) {
/* this request is so we can play some animiation showing the
* window physically moving to the tray before we close it (I
* think) */
DLOG("Client has requested iconic state. Closing this con. (con = %p)\n", con);
tree_close(con, DONT_KILL_WINDOW, false, false);
tree_render();
} else {
DLOG("Not handling WM_CHANGE_STATE request. (window = %d, state = %d)\n", event->window, event->data.data32[0]);
}
} else if (event->type == A__NET_CURRENT_DESKTOP) {
/* This request is used by pagers and bars to change the current
* desktop likely as a result of some user action. We interpret this as
* a request to focus the given workspace. See
* http://standards.freedesktop.org/wm-spec/latest/ar01s03.html#idm140251368135008
* */
Con *output;
uint32_t idx = 0;
DLOG("Request to change current desktop to index %d\n", event->data.data32[0]);
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *ws;
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
if (STARTS_WITH(ws->name, "__"))
continue;
if (idx == event->data.data32[0]) {
/* data32[1] is a timestamp used to prevent focus race conditions */
if (event->data.data32[1])
last_timestamp = event->data.data32[1];
DLOG("Handling request to focus workspace %s\n", ws->name);
workspace_show(ws);
tree_render();
return;
}
++idx;
}
}
} else if (event->type == A__NET_CLOSE_WINDOW) {
/*
* Pagers wanting to close a window MUST send a _NET_CLOSE_WINDOW
* client message request to the root window.
* http://standards.freedesktop.org/wm-spec/wm-spec-latest.html#idm140200472668896
*/
Con *con = con_by_window_id(event->window);
if (con) {
DLOG("Handling _NET_CLOSE_WINDOW request (con = %p)\n", con);
if (event->data.data32[0])
last_timestamp = event->data.data32[0];
tree_close(con, KILL_WINDOW, false, false);
tree_render();
} else {
DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %d)\n", event->window);
}
} else if (event->type == A__NET_WM_MOVERESIZE) {
/*
* Client-side decorated Gtk3 windows emit this signal when being
* dragged by their GtkHeaderBar
*/
Con *con = con_by_window_id(event->window);
if (!con || !con_is_floating(con)) {
DLOG("Couldn't find con for _NET_WM_MOVERESIZE request, or con not floating (window = %d)\n", event->window);
return;
}
DLOG("Handling _NET_WM_MOVERESIZE request (con = %p)\n", con);
uint32_t direction = event->data.data32[2];
uint32_t x_root = event->data.data32[0];
uint32_t y_root = event->data.data32[1];
/* construct fake xcb_button_press_event_t */
xcb_button_press_event_t fake = {
.root_x = x_root,
.root_y = y_root,
.event_x = x_root - (con->rect.x),
.event_y = y_root - (con->rect.y)};
if (direction == _NET_WM_MOVERESIZE_MOVE) {
floating_drag_window(con->parent, &fake);
} else if (direction >= _NET_WM_MOVERESIZE_SIZE_TOPLEFT && direction <= _NET_WM_MOVERESIZE_SIZE_LEFT) {
floating_resize_window(con->parent, FALSE, &fake);
} else {
DLOG("_NET_WM_MOVERESIZE direction %d not implemented\n", direction);
}
} else { } else {
DLOG("unhandled clientmessage\n"); DLOG("unhandled clientmessage\n");
return; return;
@ -1045,6 +1150,30 @@ static void handle_focus_in(xcb_focus_in_event_t *event) {
return; return;
} }
/*
* Handles the WM_CLASS property for assignments and criteria selection.
*
*/
static bool handle_class_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
xcb_atom_t name, xcb_get_property_reply_t *prop) {
Con *con;
if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
return false;
if (prop == NULL) {
prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
false, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0, 32),
NULL);
if (prop == NULL)
return false;
}
window_update_class(con->window, prop, false);
return true;
}
/* Returns false if the event could not be processed (e.g. the window could not /* Returns false if the event could not be processed (e.g. the window could not
* be found), true otherwise */ * be found), true otherwise */
typedef bool (*cb_property_handler_t)(void *data, xcb_connection_t *c, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property); typedef bool (*cb_property_handler_t)(void *data, xcb_connection_t *c, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property);
@ -1062,7 +1191,8 @@ static struct property_handler_t property_handlers[] = {
{0, UINT_MAX, handle_normal_hints}, {0, UINT_MAX, handle_normal_hints},
{0, UINT_MAX, handle_clientleader_change}, {0, UINT_MAX, handle_clientleader_change},
{0, UINT_MAX, handle_transient_for}, {0, UINT_MAX, handle_transient_for},
{0, 128, handle_windowrole_change}}; {0, 128, handle_windowrole_change},
{0, 128, handle_class_change}};
#define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t)) #define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
/* /*
@ -1080,6 +1210,7 @@ void property_handlers_init(void) {
property_handlers[4].atom = A_WM_CLIENT_LEADER; property_handlers[4].atom = A_WM_CLIENT_LEADER;
property_handlers[5].atom = XCB_ATOM_WM_TRANSIENT_FOR; property_handlers[5].atom = XCB_ATOM_WM_TRANSIENT_FOR;
property_handlers[6].atom = A_WM_WINDOW_ROLE; property_handlers[6].atom = A_WM_WINDOW_ROLE;
property_handlers[7].atom = XCB_ATOM_WM_CLASS;
} }
static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) { static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
@ -1115,12 +1246,50 @@ static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom)
* *
*/ */
void handle_event(int type, xcb_generic_event_t *event) { void handle_event(int type, xcb_generic_event_t *event) {
DLOG("event type %d, xkb_base %d\n", type, xkb_base);
if (randr_base > -1 && if (randr_base > -1 &&
type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
handle_screen_change(event); handle_screen_change(event);
return; return;
} }
if (xkb_base > -1 && type == xkb_base) {
DLOG("xkb event, need to handle it.\n");
xcb_xkb_state_notify_event_t *state = (xcb_xkb_state_notify_event_t *)event;
if (state->xkbType == XCB_XKB_MAP_NOTIFY) {
if (event_is_ignored(event->sequence, type)) {
DLOG("Ignoring map notify event for sequence %d.\n", state->sequence);
} else {
DLOG("xkb map notify, sequence %d, time %d\n", state->sequence, state->time);
add_ignore_event(event->sequence, type);
ungrab_all_keys(conn);
translate_keysyms();
grab_all_keys(conn, false);
}
} else if (state->xkbType == XCB_XKB_STATE_NOTIFY) {
DLOG("xkb state group = %d\n", state->group);
/* See The XKB Extension: Library Specification, section 14.1 */
/* We check if the current group (each group contains
* two levels) has been changed. Mode_switch activates
* group XkbGroup2Index */
if (xkb_current_group == state->group)
return;
xkb_current_group = state->group;
if (state->group == XCB_XKB_GROUP_1) {
DLOG("Mode_switch disabled\n");
ungrab_all_keys(conn);
grab_all_keys(conn, false);
} else {
DLOG("Mode_switch enabled\n");
grab_all_keys(conn, false);
}
}
return;
}
switch (type) { switch (type) {
case XCB_KEY_PRESS: case XCB_KEY_PRESS:
case XCB_KEY_RELEASE: case XCB_KEY_RELEASE:
@ -1128,6 +1297,7 @@ void handle_event(int type, xcb_generic_event_t *event) {
break; break;
case XCB_BUTTON_PRESS: case XCB_BUTTON_PRESS:
case XCB_BUTTON_RELEASE:
handle_button_press((xcb_button_press_event_t *)event); handle_button_press((xcb_button_press_event_t *)event);
break; break;

View File

@ -5,8 +5,8 @@ CLEAN_TARGETS += clean-i3
i3_SOURCES := $(filter-out $(i3_SOURCES_GENERATED),$(wildcard src/*.c)) i3_SOURCES := $(filter-out $(i3_SOURCES_GENERATED),$(wildcard src/*.c))
i3_HEADERS_CMDPARSER := $(wildcard include/GENERATED_*.h) i3_HEADERS_CMDPARSER := $(wildcard include/GENERATED_*.h)
i3_HEADERS := $(filter-out $(i3_HEADERS_CMDPARSER),$(wildcard include/*.h)) i3_HEADERS := $(filter-out $(i3_HEADERS_CMDPARSER),$(wildcard include/*.h))
i3_CFLAGS = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(XCB_WM_CFLAGS) $(X11_CFLAGS) $(XCURSOR_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) $(PCRE_CFLAGS) $(LIBSN_CFLAGS) i3_CFLAGS = $(XKB_COMMON_CFLAGS) $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(XCB_WM_CFLAGS) $(XCURSOR_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) $(PCRE_CFLAGS) $(LIBSN_CFLAGS)
i3_LIBS = $(XCB_LIBS) $(XCB_KBD_LIBS) $(XCB_WM_LIBS) $(X11_LIBS) $(XCURSOR_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(PCRE_LIBS) $(LIBSN_LIBS) -lm -lpthread i3_LIBS = $(XKB_COMMON_LIBS) $(XCB_LIBS) $(XCB_XKB_LIBS) $(XCB_KBD_LIBS) $(XCB_WM_LIBS) $(XCURSOR_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(PCRE_LIBS) $(LIBSN_LIBS) -lm -lpthread
# When using clang, we use pre-compiled headers to speed up the build. With # When using clang, we use pre-compiled headers to speed up the build. With
# gcc, this actually makes the build slower. # gcc, this actually makes the build slower.

134
src/ipc.c
View File

@ -151,6 +151,60 @@ static void dump_rect(yajl_gen gen, const char *name, Rect r) {
y(map_close); y(map_close);
} }
static void dump_binding(yajl_gen gen, Binding *bind) {
y(map_open);
ystr("input_code");
y(integer, bind->keycode);
ystr("input_type");
ystr((const char*)(bind->input_type == B_KEYBOARD ? "keyboard" : "mouse"));
ystr("symbol");
if (bind->symbol == NULL)
y(null);
else
ystr(bind->symbol);
ystr("command");
ystr(bind->command);
ystr("mods");
y(array_open);
for (int i = 0; i < 8; i++) {
if (bind->mods & (1 << i)) {
switch (1 << i) {
case XCB_MOD_MASK_SHIFT:
ystr("shift");
break;
case XCB_MOD_MASK_LOCK:
ystr("lock");
break;
case XCB_MOD_MASK_CONTROL:
ystr("ctrl");
break;
case XCB_MOD_MASK_1:
ystr("Mod1");
break;
case XCB_MOD_MASK_2:
ystr("Mod2");
break;
case XCB_MOD_MASK_3:
ystr("Mod3");
break;
case XCB_MOD_MASK_4:
ystr("Mod4");
break;
case XCB_MOD_MASK_5:
ystr("Mod5");
break;
}
}
}
y(array_close);
y(map_close);
}
void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
y(map_open); y(map_open);
ystr("id"); ystr("id");
@ -293,14 +347,17 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
y(integer, con->current_border_width); y(integer, con->current_border_width);
dump_rect(gen, "rect", con->rect); dump_rect(gen, "rect", con->rect);
dump_rect(gen, "deco_rect", con->deco_rect);
dump_rect(gen, "window_rect", con->window_rect); dump_rect(gen, "window_rect", con->window_rect);
dump_rect(gen, "geometry", con->geometry); dump_rect(gen, "geometry", con->geometry);
ystr("name"); ystr("name");
if (con->window && con->window->name) if (con->window && con->window->name)
ystr(i3string_as_utf8(con->window->name)); ystr(i3string_as_utf8(con->window->name));
else else if (con->name != NULL)
ystr(con->name); ystr(con->name);
else
y(null);
if (con->type == CT_WORKSPACE) { if (con->type == CT_WORKSPACE) {
ystr("num"); ystr("num");
@ -337,6 +394,11 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
ystr(i3string_as_utf8(con->window->name)); ystr(i3string_as_utf8(con->window->name));
} }
ystr("transient_for");
if (con->window->transient_for == XCB_NONE)
y(null);
else y(integer, con->window->transient_for);
y(map_close); y(map_close);
} }
@ -512,6 +574,16 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
break; break;
} }
if (config->wheel_up_cmd) {
ystr("wheel_up_cmd");
ystr(config->wheel_up_cmd);
}
if (config->wheel_down_cmd) {
ystr("wheel_down_cmd");
ystr(config->wheel_down_cmd);
}
ystr("position"); ystr("position");
if (config->position == P_BOTTOM) if (config->position == P_BOTTOM)
ystr("bottom"); ystr("bottom");
@ -600,9 +672,6 @@ IPC_HANDLER(get_workspaces) {
y(map_open); y(map_open);
ystr("num"); ystr("num");
if (ws->num == -1)
y(null);
else
y(integer, ws->num); y(integer, ws->num);
ystr("name"); ystr("name");
@ -1051,20 +1120,22 @@ int ipc_create_socket(const char *filename) {
} }
/* /*
* For the workspace "focus" event we send, along the usual "change" field, * Generates a json workspace event. Returns a dynamically allocated yajl
* also the current and previous workspace, in "current" and "old" * generator. Free with yajl_gen_free().
* respectively.
*/ */
void ipc_send_workspace_focus_event(Con *current, Con *old) { yajl_gen ipc_marshal_workspace_event(const char *change, Con *current, Con *old) {
setlocale(LC_NUMERIC, "C"); setlocale(LC_NUMERIC, "C");
yajl_gen gen = ygenalloc(); yajl_gen gen = ygenalloc();
y(map_open); y(map_open);
ystr("change"); ystr("change");
ystr("focus"); ystr(change);
ystr("current"); ystr("current");
if (current == NULL)
y(null);
else
dump_node(gen, current, false); dump_node(gen, current, false);
ystr("old"); ystr("old");
@ -1075,13 +1146,26 @@ void ipc_send_workspace_focus_event(Con *current, Con *old) {
y(map_close); y(map_close);
setlocale(LC_NUMERIC, "");
return gen;
}
/*
* For the workspace events we send, along with the usual "change" field, also
* the workspace container in "current". For focus events, we send the
* previously focused workspace in "old".
*/
void ipc_send_workspace_event(const char *change, Con *current, Con *old) {
yajl_gen gen = ipc_marshal_workspace_event(change, current, old);
const unsigned char *payload; const unsigned char *payload;
ylength length; ylength length;
y(get_buf, &payload, &length); y(get_buf, &payload, &length);
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, (const char *)payload); ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, (const char *)payload);
y(free); y(free);
setlocale(LC_NUMERIC, "");
} }
/** /**
@ -1132,3 +1216,33 @@ void ipc_send_barconfig_update_event(Barconfig *barconfig) {
y(free); y(free);
setlocale(LC_NUMERIC, ""); setlocale(LC_NUMERIC, "");
} }
/*
* For the binding events, we send the serialized binding struct.
*/
void ipc_send_binding_event(const char *event_type, Binding *bind) {
DLOG("Issue IPC binding %s event (sym = %s, code = %d)\n", event_type, bind->symbol, bind->keycode);
setlocale(LC_NUMERIC, "C");
yajl_gen gen = ygenalloc();
y(map_open);
ystr("change");
ystr(event_type);
ystr("binding");
dump_binding(gen, bind);
y(map_close);
const unsigned char *payload;
ylength length;
y(get_buf, &payload, &length);
ipc_send_event("binding", I3_IPC_EVENT_BINDING, (const char *)payload);
y(free);
setlocale(LC_NUMERIC, "");
}

View File

@ -30,7 +30,7 @@ void handle_key_press(xcb_key_press_event_t *event) {
if (bind == NULL) if (bind == NULL)
return; return;
CommandResult *result = run_binding(bind); CommandResult *result = run_binding(bind, NULL);
if (result->needs_tree_render) if (result->needs_tree_render)
tree_render(); tree_render();

View File

@ -24,6 +24,7 @@ static Con *json_node;
static Con *to_focus; static Con *to_focus;
static bool parsing_swallows; static bool parsing_swallows;
static bool parsing_rect; static bool parsing_rect;
static bool parsing_deco_rect;
static bool parsing_window_rect; static bool parsing_window_rect;
static bool parsing_geometry; static bool parsing_geometry;
static bool parsing_focus; static bool parsing_focus;
@ -47,7 +48,7 @@ static int json_start_map(void *ctx) {
match_init(current_swallow); match_init(current_swallow);
TAILQ_INSERT_TAIL(&(json_node->swallow_head), current_swallow, matches); TAILQ_INSERT_TAIL(&(json_node->swallow_head), current_swallow, matches);
} else { } else {
if (!parsing_rect && !parsing_window_rect && !parsing_geometry) { if (!parsing_rect && !parsing_deco_rect && !parsing_window_rect && !parsing_geometry) {
if (last_key && strcasecmp(last_key, "floating_nodes") == 0) { if (last_key && strcasecmp(last_key, "floating_nodes") == 0) {
DLOG("New floating_node\n"); DLOG("New floating_node\n");
Con *ws = con_get_workspace(json_node); Con *ws = con_get_workspace(json_node);
@ -68,7 +69,7 @@ static int json_start_map(void *ctx) {
static int json_end_map(void *ctx) { static int json_end_map(void *ctx) {
LOG("end of map\n"); LOG("end of map\n");
if (!parsing_swallows && !parsing_rect && !parsing_window_rect && !parsing_geometry) { if (!parsing_swallows && !parsing_rect && !parsing_deco_rect && !parsing_window_rect && !parsing_geometry) {
/* Set a few default values to simplify manually crafted layout files. */ /* Set a few default values to simplify manually crafted layout files. */
if (json_node->layout == L_DEFAULT) { if (json_node->layout == L_DEFAULT) {
DLOG("Setting layout = L_SPLITH\n"); DLOG("Setting layout = L_SPLITH\n");
@ -113,11 +114,6 @@ static int json_end_map(void *ctx) {
/* Set num accordingly so that i3bar will properly sort it. */ /* Set num accordingly so that i3bar will properly sort it. */
json_node->num = ws_name_to_number(json_node->name); json_node->num = ws_name_to_number(json_node->name);
} else {
// TODO: remove this in the “next” branch.
if (json_node->name == NULL || strcmp(json_node->name, "") == 0) {
json_node->name = sstrdup("#ff0000");
}
} }
LOG("attaching\n"); LOG("attaching\n");
@ -126,11 +122,10 @@ static int json_end_map(void *ctx) {
x_con_init(json_node, json_node->depth); x_con_init(json_node, json_node->depth);
json_node = json_node->parent; json_node = json_node->parent;
} }
if (parsing_rect)
parsing_rect = false; parsing_rect = false;
if (parsing_window_rect) parsing_deco_rect = false;
parsing_window_rect = false; parsing_window_rect = false;
if (parsing_geometry)
parsing_geometry = false; parsing_geometry = false;
return 1; return 1;
} }
@ -180,6 +175,9 @@ static int json_key(void *ctx, const unsigned char *val, size_t len) {
if (strcasecmp(last_key, "rect") == 0) if (strcasecmp(last_key, "rect") == 0)
parsing_rect = true; parsing_rect = true;
if (strcasecmp(last_key, "deco_rect") == 0)
parsing_deco_rect = true;
if (strcasecmp(last_key, "window_rect") == 0) if (strcasecmp(last_key, "window_rect") == 0)
parsing_window_rect = true; parsing_window_rect = true;
@ -553,6 +551,7 @@ void tree_append_json(Con *con, const char *filename, char **errormsg) {
to_focus = NULL; to_focus = NULL;
parsing_swallows = false; parsing_swallows = false;
parsing_rect = false; parsing_rect = false;
parsing_deco_rect = false;
parsing_window_rect = false; parsing_window_rect = false;
parsing_geometry = false; parsing_geometry = false;
parsing_focus = false; parsing_focus = false;

View File

@ -36,10 +36,6 @@ int listen_fds;
* temporarily for drag_pointer(). */ * temporarily for drag_pointer(). */
static struct ev_check *xcb_check; static struct ev_check *xcb_check;
static int xkb_event_base;
int xkb_current_group;
extern Con *focused; extern Con *focused;
char **start_argv; char **start_argv;
@ -70,9 +66,6 @@ struct ev_loop *main_loop;
xcb_key_symbols_t *keysyms; xcb_key_symbols_t *keysyms;
/* Those are our connections to X11 for use with libXcursor and XKB */
Display *xlibdpy, *xkbdpy;
/* Default shmlog size if not set by user. */ /* Default shmlog size if not set by user. */
const int default_shmlog_size = 25 * 1024 * 1024; const int default_shmlog_size = 25 * 1024 * 1024;
@ -94,12 +87,6 @@ struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignment
/* We hope that those are supported and set them to true */ /* We hope that those are supported and set them to true */
bool xcursor_supported = true; bool xcursor_supported = true;
bool xkb_supported = true;
/* This will be set to true when -C is used so that functions can behave
* slightly differently. We dont want i3-nagbar to be started when validating
* the config, for example. */
bool only_check_config = false;
/* /*
* This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb. * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
@ -166,73 +153,6 @@ void main_set_x11_cb(bool enable) {
} }
} }
/*
* When using xmodmap to change the keyboard mapping, this event
* is only sent via XKB. Therefore, we need this special handler.
*
*/
static void xkb_got_event(EV_P_ struct ev_io *w, int revents) {
DLOG("Handling XKB event\n");
XkbEvent ev;
/* When using xmodmap, every change (!) gets an own event.
* Therefore, we just read all events and only handle the
* mapping_notify once. */
bool mapping_changed = false;
while (XPending(xkbdpy)) {
XNextEvent(xkbdpy, (XEvent *)&ev);
/* While we should never receive a non-XKB event,
* better do sanity checking */
if (ev.type != xkb_event_base)
continue;
if (ev.any.xkb_type == XkbMapNotify) {
mapping_changed = true;
continue;
}
if (ev.any.xkb_type != XkbStateNotify) {
ELOG("Unknown XKB event received (type %d)\n", ev.any.xkb_type);
continue;
}
/* See The XKB Extension: Library Specification, section 14.1 */
/* We check if the current group (each group contains
* two levels) has been changed. Mode_switch activates
* group XkbGroup2Index */
if (xkb_current_group == ev.state.group)
continue;
xkb_current_group = ev.state.group;
if (ev.state.group == XkbGroup2Index) {
DLOG("Mode_switch enabled\n");
grab_all_keys(conn, true);
}
if (ev.state.group == XkbGroup1Index) {
DLOG("Mode_switch disabled\n");
ungrab_all_keys(conn);
grab_all_keys(conn, false);
}
}
if (!mapping_changed)
return;
DLOG("Keyboard mapping changed, updating keybindings\n");
xcb_key_symbols_free(keysyms);
keysyms = xcb_key_symbols_alloc(conn);
xcb_numlock_mask = aio_get_mod_mask_for(XCB_NUM_LOCK, keysyms);
ungrab_all_keys(conn);
DLOG("Re-grabbing...\n");
translate_keysyms();
grab_all_keys(conn, (xkb_current_group == XkbGroup2Index));
DLOG("Done\n");
}
/* /*
* Exit handler which destroys the main_loop. Will trigger cleanup handlers. * Exit handler which destroys the main_loop. Will trigger cleanup handlers.
* *
@ -276,6 +196,7 @@ int main(int argc, char *argv[]) {
bool force_xinerama = false; bool force_xinerama = false;
char *fake_outputs = NULL; char *fake_outputs = NULL;
bool disable_signalhandler = false; bool disable_signalhandler = false;
bool only_check_config = false;
static struct option long_options[] = { static struct option long_options[] = {
{"no-autostart", no_argument, 0, 'a'}, {"no-autostart", no_argument, 0, 'a'},
{"config", required_argument, 0, 'c'}, {"config", required_argument, 0, 'c'},
@ -441,10 +362,14 @@ int main(int argc, char *argv[]) {
} }
} }
if (only_check_config) {
exit(parse_configuration(override_configpath, false) ? 0 : 1);
}
/* If the user passes more arguments, we act like i3-msg would: Just send /* If the user passes more arguments, we act like i3-msg would: Just send
* the arguments as an IPC message to i3. This allows for nice semantic * the arguments as an IPC message to i3. This allows for nice semantic
* commands such as 'i3 border none'. */ * commands such as 'i3 border none'. */
if (!only_check_config && optind < argc) { if (optind < argc) {
/* We enable verbose mode so that the user knows whats going on. /* We enable verbose mode so that the user knows whats going on.
* This should make it easier to find mistakes when the user passes * This should make it easier to find mistakes when the user passes
* arguments by mistake. */ * arguments by mistake. */
@ -567,10 +492,6 @@ int main(int argc, char *argv[]) {
xcb_query_pointer_cookie_t pointercookie = xcb_query_pointer(conn, root); xcb_query_pointer_cookie_t pointercookie = xcb_query_pointer(conn, root);
load_configuration(conn, override_configpath, false); load_configuration(conn, override_configpath, false);
if (only_check_config) {
LOG("Done checking configuration file. Exiting.\n");
exit(0);
}
if (config.ipc_socket_path == NULL) { if (config.ipc_socket_path == NULL) {
/* Fall back to a file name in /tmp/ based on the PID */ /* Fall back to a file name in /tmp/ based on the PID */
@ -597,21 +518,7 @@ int main(int argc, char *argv[]) {
#include "atoms.xmacro" #include "atoms.xmacro"
#undef xmacro #undef xmacro
/* Initialize the Xlib connection */
xlibdpy = xkbdpy = XOpenDisplay(NULL);
/* Try to load the X cursors and initialize the XKB extension */
if (xlibdpy == NULL) {
ELOG("ERROR: XOpenDisplay() failed, disabling libXcursor/XKB support\n");
xcursor_supported = false;
xkb_supported = false;
} else if (fcntl(ConnectionNumber(xlibdpy), F_SETFD, FD_CLOEXEC) == -1) {
ELOG("Could not set FD_CLOEXEC on xkbdpy\n");
return 1;
} else {
xcursor_load_cursors(); xcursor_load_cursors();
/*init_xkb();*/
}
/* Set a cursor for the root window (otherwise the root window will show no /* Set a cursor for the root window (otherwise the root window will show no
cursor until the first client is launched). */ cursor until the first client is launched). */
@ -620,27 +527,22 @@ int main(int argc, char *argv[]) {
else else
xcb_set_root_cursor(XCURSOR_CURSOR_POINTER); xcb_set_root_cursor(XCURSOR_CURSOR_POINTER);
if (xkb_supported) { const xcb_query_extension_reply_t *extreply;
int errBase, extreply = xcb_get_extension_data(conn, &xcb_xkb_id);
major = XkbMajorVersion, if (!extreply->present) {
minor = XkbMinorVersion; DLOG("xkb is not present on this server\n");
} else {
if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) { DLOG("initializing xcb-xkb\n");
fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n"); xcb_xkb_use_extension(conn, XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION);
return 1; xcb_xkb_select_events(conn,
} XCB_XKB_ID_USE_CORE_KBD,
XCB_XKB_EVENT_TYPE_STATE_NOTIFY | XCB_XKB_EVENT_TYPE_MAP_NOTIFY,
int i1; 0,
if (!XkbQueryExtension(xkbdpy, &i1, &xkb_event_base, &errBase, &major, &minor)) { XCB_XKB_EVENT_TYPE_STATE_NOTIFY | XCB_XKB_EVENT_TYPE_MAP_NOTIFY,
fprintf(stderr, "XKB not supported by X-server\n"); 0xff,
xkb_supported = false; 0xff,
} NULL);
/* end of ugliness */ xkb_base = extreply->first_event;
if (xkb_supported && !XkbSelectEvents(xkbdpy, XkbUseCoreKbd, XkbMapNotifyMask | XkbStateNotifyMask, XkbMapNotifyMask | XkbStateNotifyMask)) {
fprintf(stderr, "Could not set XKB event mask\n");
return 1;
}
} }
restore_connect(); restore_connect();
@ -769,25 +671,19 @@ int main(int argc, char *argv[]) {
x_set_i3_atoms(); x_set_i3_atoms();
ewmh_update_workarea(); ewmh_update_workarea();
/* Set the _NET_CURRENT_DESKTOP property. */ /* Set the ewmh desktop properties. */
ewmh_update_current_desktop(); ewmh_update_current_desktop();
ewmh_update_number_of_desktops();
ewmh_update_desktop_names();
ewmh_update_desktop_viewport();
struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io)); struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io));
struct ev_io *xkb = scalloc(sizeof(struct ev_io));
xcb_check = scalloc(sizeof(struct ev_check)); xcb_check = scalloc(sizeof(struct ev_check));
struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare)); 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_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
ev_io_start(main_loop, xcb_watcher); ev_io_start(main_loop, xcb_watcher);
if (xkb_supported) {
ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ);
ev_io_start(main_loop, xkb);
/* Flush the buffer so that libev can properly get new events */
XFlush(xkbdpy);
}
ev_check_init(xcb_check, xcb_check_cb); ev_check_init(xcb_check, xcb_check_cb);
ev_check_start(main_loop, xcb_check); ev_check_start(main_loop, xcb_check);

View File

@ -436,11 +436,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
if (nc->geometry.width == 0) if (nc->geometry.width == 0)
nc->geometry = (Rect) {geom->x, geom->y, geom->width, geom->height}; nc->geometry = (Rect) {geom->x, geom->y, geom->width, geom->height};
if (want_floating) {
DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height);
floating_enable(nc, true);
}
if (motif_border_style != BS_NORMAL) { if (motif_border_style != BS_NORMAL) {
DLOG("MOTIF_WM_HINTS specifies decorations (border_style = %d)\n", motif_border_style); DLOG("MOTIF_WM_HINTS specifies decorations (border_style = %d)\n", motif_border_style);
if (want_floating) { if (want_floating) {
@ -450,12 +445,18 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
} }
} }
if (nc->border_style == BS_PIXEL) { if (want_floating) {
/* if the border style is BS_PIXEL, explicitly set the border width of DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height);
* the new container */ /* automatically set the border to the default value if a motif border
nc->current_border_width = (want_floating ? config.default_floating_border_width : config.default_border_width); * was not specified */
bool automatic_border = (motif_border_style == BS_NORMAL);
floating_enable(nc, automatic_border);
} }
/* explicitly set the border width to the default */
nc->current_border_width = (want_floating ? config.default_floating_border_width : config.default_border_width);
/* to avoid getting an UnmapNotify event due to reparenting, we temporarily /* to avoid getting an UnmapNotify event due to reparenting, we temporarily
* declare no interest in any state change event of this window */ * declare no interest in any state change event of this window */
values[0] = XCB_NONE; values[0] = XCB_NONE;
@ -511,7 +512,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
/* Defer setting focus after the 'new' event has been sent to ensure the /* Defer setting focus after the 'new' event has been sent to ensure the
* proper window event sequence. */ * proper window event sequence. */
if (set_focus && nc->mapped) { if (set_focus && !nc->window->doesnt_accept_focus && nc->mapped) {
DLOG("Now setting focus.\n"); DLOG("Now setting focus.\n");
con_focus(nc); con_focus(nc);
} }

View File

@ -128,22 +128,21 @@ static void move_to_output_directed(Con *con, direction_t direction) {
tree_flatten(croot); tree_flatten(croot);
ipc_send_workspace_focus_event(ws, old_ws); ipc_send_workspace_event("focus", ws, old_ws);
} }
/* /*
* Moves the current container in the given direction (D_LEFT, D_RIGHT, * Moves the given container in the given direction (D_LEFT, D_RIGHT,
* D_UP, D_DOWN). * D_UP, D_DOWN).
* *
*/ */
void tree_move(int direction) { void tree_move(Con *con, int direction) {
position_t position; position_t position;
Con *target; Con *target;
DLOG("Moving in direction %d\n", direction); DLOG("Moving in direction %d\n", direction);
/* 1: get the first parent with the same orientation */ /* 1: get the first parent with the same orientation */
Con *con = focused;
if (con->type == CT_WORKSPACE) { if (con->type == CT_WORKSPACE) {
DLOG("Not moving workspace\n"); DLOG("Not moving workspace\n");
@ -206,6 +205,7 @@ void tree_move(int direction) {
TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused); TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused);
DLOG("Swapped.\n"); DLOG("Swapped.\n");
ipc_send_window_event("move", con);
return; return;
} }
@ -213,6 +213,7 @@ void tree_move(int direction) {
/* If we couldn't find a place to move it on this workspace, /* If we couldn't find a place to move it on this workspace,
* try to move it to a workspace on a different output */ * try to move it to a workspace on a different output */
move_to_output_directed(con, direction); move_to_output_directed(con, direction);
ipc_send_window_event("move", con);
return; return;
} }
@ -264,4 +265,5 @@ end:
FREE(con->deco_render_params); FREE(con->deco_render_params);
tree_flatten(croot); tree_flatten(croot);
ipc_send_window_event("move", con);
} }

View File

@ -298,6 +298,8 @@ void render_con(Con *con, bool render_fullscreen) {
while (transient_con != NULL && while (transient_con != NULL &&
transient_con->window != NULL && transient_con->window != NULL &&
transient_con->window->transient_for != XCB_NONE) { transient_con->window->transient_for != XCB_NONE) {
DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, fullscreen_id = 0x%08x\n",
transient_con->window->id, transient_con->window->transient_for, fullscreen->window->id);
if (transient_con->window->transient_for == fullscreen->window->id) { if (transient_con->window->transient_for == fullscreen->window->id) {
is_transient_for = true; is_transient_for = true;
break; break;

View File

@ -197,6 +197,7 @@ static void open_placeholder_window(Con *con) {
/* Set the same name as was stored in the layout file. While perhaps /* Set the same name as was stored in the layout file. While perhaps
* slightly confusing in the first instant, this brings additional * slightly confusing in the first instant, this brings additional
* clarity to which placeholder is waiting for which actual window. */ * clarity to which placeholder is waiting for which actual window. */
if (con->name != NULL)
xcb_change_property(restore_conn, XCB_PROP_MODE_REPLACE, placeholder, xcb_change_property(restore_conn, XCB_PROP_MODE_REPLACE, placeholder,
A__NET_WM_NAME, A_UTF8_STRING, 8, strlen(con->name), con->name); A__NET_WM_NAME, A_UTF8_STRING, 8, strlen(con->name), con->name);
DLOG("Created placeholder window 0x%08x for leaf container %p / %s\n", DLOG("Created placeholder window 0x%08x for leaf container %p / %s\n",

View File

@ -123,8 +123,8 @@ void startup_sequence_delete(struct Startup_Sequence *sequence) {
* the application is reparented to init (process-id 1), which correctly handles * the application is reparented to init (process-id 1), which correctly handles
* childs, so we dont have to do it :-). * childs, so we dont have to do it :-).
* *
* The shell is determined by looking for the SHELL environment variable. If it * The shell used to start applications is the system's bourne shell (i.e.,
* does not exist, /bin/sh is used. * /bin/sh).
* *
* The no_startup_id flag determines whether a startup notification context * The no_startup_id flag determines whether a startup notification context
* (and ID) should be created, which is the default and encouraged behavior. * (and ID) should be created, which is the default and encouraged behavior.

View File

@ -255,6 +255,7 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
* X11 Errors are returned when the window was already destroyed */ * X11 Errors are returned when the window was already destroyed */
add_ignore_event(cookie.sequence, 0); add_ignore_event(cookie.sequence, 0);
} }
ipc_send_window_event("close", con);
FREE(con->window->class_class); FREE(con->window->class_class);
FREE(con->window->class_instance); FREE(con->window->class_instance);
i3string_free(con->window->name); i3string_free(con->window->name);
@ -406,8 +407,7 @@ void tree_split(Con *con, orientation_t orientation) {
Con *parent = con->parent; Con *parent = con->parent;
/* Force re-rendering to make the indicator border visible. */ /* Force re-rendering to make the indicator border visible. */
FREE(con->deco_render_params); con_force_split_parents_redraw(con);
FREE(parent->deco_render_params);
/* if we are in a container whose parent contains only one /* if we are in a container whose parent contains only one
* child (its split functionality is unused so far), we just change the * child (its split functionality is unused so far), we just change the
@ -581,6 +581,13 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
return true; return true;
Con *focus = con_descend_direction(workspace, direction); Con *focus = con_descend_direction(workspace, direction);
/* special case: if there was no tiling con to focus and the workspace
* has a floating con in the focus stack, focus the top of the focus
* stack (which may be floating) */
if (focus == workspace)
focus = con_descend_focused(workspace);
if (focus) { if (focus) {
con_focus(focus); con_focus(focus);
x_set_warp_to(&(focus->rect)); x_set_warp_to(&(focus->rect));
@ -591,8 +598,10 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
Con *parent = con->parent; Con *parent = con->parent;
if (con->type == CT_FLOATING_CON) { if (con->type == CT_FLOATING_CON) {
if (orientation != HORIZ)
return false;
/* left/right focuses the previous/next floating container */ /* left/right focuses the previous/next floating container */
if (orientation == HORIZ) {
Con *next; Con *next;
if (way == 'n') if (way == 'n')
next = TAILQ_NEXT(con, floating_windows); next = TAILQ_NEXT(con, floating_windows);
@ -611,13 +620,16 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
if (!next) if (!next)
return false; return false;
/* Raise the floating window on top of other windows preserving
* relative stack order */
while (TAILQ_LAST(&(parent->floating_head), floating_head) != next) {
Con *last = TAILQ_LAST(&(parent->floating_head), floating_head);
TAILQ_REMOVE(&(parent->floating_head), last, floating_windows);
TAILQ_INSERT_HEAD(&(parent->floating_head), last, floating_windows);
}
con_focus(con_descend_focused(next)); con_focus(con_descend_focused(next));
return true; return true;
} else {
/* up/down cycles through the Z-index */
/* TODO: implement cycling through the z-index */
return false;
}
} }
/* If the orientation does not match or there is no other con to focus, we /* If the orientation does not match or there is no other con to focus, we

View File

@ -125,7 +125,8 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bo
*/ */
void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop) { void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
DLOG("CLIENT_LEADER not set.\n"); DLOG("CLIENT_LEADER not set on window 0x%08x.\n", win->id);
win->leader = XCB_NONE;
FREE(prop); FREE(prop);
return; return;
} }
@ -149,7 +150,8 @@ void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop) {
*/ */
void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop) { void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
DLOG("TRANSIENT_FOR not set.\n"); DLOG("TRANSIENT_FOR not set on window 0x%08x.\n", win->id);
win->transient_for = XCB_NONE;
FREE(prop); FREE(prop);
return; return;
} }
@ -160,7 +162,7 @@ void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop)
return; return;
} }
DLOG("Transient for changed to %08x\n", transient_for); DLOG("Transient for changed to 0x%08x (window 0x%08x)\n", transient_for, win->id);
win->transient_for = transient_for; win->transient_for = transient_for;

View File

@ -11,6 +11,7 @@
* *
*/ */
#include "all.h" #include "all.h"
#include "yajl_utils.h"
/* Stores a copy of the name of the last used workspace for the workspace /* Stores a copy of the name of the last used workspace for the workspace
* back-and-forth switching. */ * back-and-forth switching. */
@ -91,7 +92,10 @@ Con *workspace_get(const char *num, bool *created) {
con_attach(workspace, content, false); con_attach(workspace, content, false);
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); ipc_send_workspace_event("init", workspace, NULL);
ewmh_update_number_of_desktops();
ewmh_update_desktop_names();
ewmh_update_desktop_viewport();
if (created != NULL) if (created != NULL)
*created = true; *created = true;
} else if (created != NULL) { } else if (created != NULL) {
@ -123,7 +127,7 @@ Con *create_workspace_on_output(Output *output, Con *content) {
strncasecmp(bind->command, "workspace", strlen("workspace")) != 0) strncasecmp(bind->command, "workspace", strlen("workspace")) != 0)
continue; continue;
DLOG("relevant command = %s\n", bind->command); DLOG("relevant command = %s\n", bind->command);
char *target = bind->command + strlen("workspace "); const char *target = bind->command + strlen("workspace ");
while ((*target == ' ' || *target == '\t') && target != '\0') while ((*target == ' ' || *target == '\t') && target != '\0')
target++; target++;
/* We check if this is the workspace /* We check if this is the workspace
@ -139,16 +143,16 @@ Con *create_workspace_on_output(Output *output, Con *content) {
strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 || strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 ||
strncasecmp(target, "current", strlen("current")) == 0) strncasecmp(target, "current", strlen("current")) == 0)
continue; continue;
if (*target == '"') char *target_name = parse_string(&target, false);
target++; if (target_name == NULL)
if (strncasecmp(target, "__", strlen("__")) == 0) { continue;
if (strncasecmp(target_name, "__", strlen("__")) == 0) {
LOG("Cannot create workspace \"%s\". Names starting with __ are i3-internal.\n", target); LOG("Cannot create workspace \"%s\". Names starting with __ are i3-internal.\n", target);
free(target_name);
continue; continue;
} }
FREE(ws->name); FREE(ws->name);
ws->name = strdup(target); ws->name = target_name;
if (ws->name[strlen(ws->name) - 1] == '"')
ws->name[strlen(ws->name) - 1] = '\0';
DLOG("trying name *%s*\n", ws->name); DLOG("trying name *%s*\n", ws->name);
/* Ensure that this workspace is not assigned to a different output — /* Ensure that this workspace is not assigned to a different output —
@ -176,15 +180,7 @@ Con *create_workspace_on_output(Output *output, Con *content) {
if (!exists) { if (!exists) {
/* Set ->num to the number of the workspace, if the name actually /* Set ->num to the number of the workspace, if the name actually
* is a number or starts with a number */ * is a number or starts with a number */
char *endptr = NULL; ws->num = ws_name_to_number(ws->name);
long parsed_num = strtol(ws->name, &endptr, 10);
if (parsed_num == LONG_MIN ||
parsed_num == LONG_MAX ||
parsed_num < 0 ||
endptr == ws->name)
ws->num = -1;
else
ws->num = parsed_num;
LOG("Used number %d for workspace with name %s\n", ws->num, ws->name); LOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
break; break;
@ -325,11 +321,14 @@ static void workspace_reassign_sticky(Con *con) {
static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents) { static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents) {
Con *con = w->data; Con *con = w->data;
if (con->urgent) {
DLOG("Resetting urgency flag of con %p by timer\n", con); DLOG("Resetting urgency flag of con %p by timer\n", con);
con->urgent = false; con->urgent = false;
con_update_parents_urgency(con); con_update_parents_urgency(con);
workspace_update_urgent_flag(con_get_workspace(con)); workspace_update_urgent_flag(con_get_workspace(con));
ipc_send_window_event("urgent", con);
tree_render(); tree_render();
}
ev_timer_stop(main_loop, con->urgency_timer); ev_timer_stop(main_loop, con->urgency_timer);
FREE(con->urgency_timer); FREE(con->urgency_timer);
@ -385,6 +384,7 @@ static void _workspace_show(Con *workspace) {
* focus and thereby immediately destroy it */ * focus and thereby immediately destroy it */
if (next->urgent && (int)(config.workspace_urgency_timer * 1000) > 0) { if (next->urgent && (int)(config.workspace_urgency_timer * 1000) > 0) {
/* focus for now… */ /* focus for now… */
next->urgent = false;
con_focus(next); con_focus(next);
/* … but immediately reset urgency flags; they will be set to false by /* … but immediately reset urgency flags; they will be set to false by
@ -410,7 +410,7 @@ static void _workspace_show(Con *workspace) {
} else } else
con_focus(next); con_focus(next);
ipc_send_workspace_focus_event(workspace, current); ipc_send_workspace_event("focus", workspace, current);
DLOG("old = %p / %s\n", old, (old ? old->name : "(null)")); DLOG("old = %p / %s\n", old, (old ? old->name : "(null)"));
/* Close old workspace if necessary. This must be done *after* doing /* Close old workspace if necessary. This must be done *after* doing
@ -422,8 +422,19 @@ static void _workspace_show(Con *workspace) {
/* check if this workspace is currently visible */ /* check if this workspace is currently visible */
if (!workspace_is_visible(old)) { if (!workspace_is_visible(old)) {
LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
yajl_gen gen = ipc_marshal_workspace_event("empty", old, NULL);
tree_close(old, DONT_KILL_WINDOW, false, false); tree_close(old, DONT_KILL_WINDOW, false, false);
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
const unsigned char *payload;
ylength length;
y(get_buf, &payload, &length);
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, (const char *)payload);
y(free);
ewmh_update_number_of_desktops();
ewmh_update_desktop_names();
ewmh_update_desktop_viewport();
} }
} }
@ -764,7 +775,7 @@ void workspace_update_urgent_flag(Con *ws) {
DLOG("Workspace urgency flag changed from %d to %d\n", old_flag, ws->urgent); DLOG("Workspace urgency flag changed from %d to %d\n", old_flag, ws->urgent);
if (old_flag != ws->urgent) if (old_flag != ws->urgent)
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}"); ipc_send_workspace_event("urgent", ws, NULL);
} }
/* /*

30
src/x.c
View File

@ -15,9 +15,9 @@
/* Stores the X11 window ID of the currently focused window */ /* Stores the X11 window ID of the currently focused window */
xcb_window_t focused_id = XCB_NONE; xcb_window_t focused_id = XCB_NONE;
/* Because 'focused_id' might be reset to force input focus (after click to /* Because 'focused_id' might be reset to force input focus, we separately keep
* raise), we separately keep track of the X11 window ID to be able to always * track of the X11 window ID to be able to always tell whether the focused
* tell whether the focused window actually changed. */ * window actually changed. */
static xcb_window_t last_focused = XCB_NONE; static xcb_window_t last_focused = XCB_NONE;
/* Stores coordinates to warp mouse pointer to if set */ /* Stores coordinates to warp mouse pointer to if set */
@ -430,16 +430,16 @@ void x_draw_decoration(Con *con) {
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &leftline); xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &leftline);
} }
if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) { if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) {
xcb_rectangle_t rightline = {r->width + br.width + br.x, 0, r->width, r->height}; xcb_rectangle_t rightline = {r->width + (br.width + br.x), 0, -(br.width + br.x), r->height};
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &rightline); xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &rightline);
} }
if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) { if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) {
xcb_rectangle_t bottomline = {0, r->height + br.height + br.y, r->width, r->height}; xcb_rectangle_t bottomline = {br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)};
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &bottomline); xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &bottomline);
} }
/* 1pixel border needs an additional line at the top */ /* 1pixel border needs an additional line at the top */
if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) { if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) {
xcb_rectangle_t topline = {br.x, 0, con->rect.width + br.width + br.x, br.y}; xcb_rectangle_t topline = {br.x, 0, r->width + br.width, br.y};
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &topline); xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &topline);
} }
@ -453,10 +453,10 @@ void x_draw_decoration(Con *con) {
xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) {p->color->indicator}); xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) {p->color->indicator});
if (p->parent_layout == L_SPLITH) if (p->parent_layout == L_SPLITH)
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]) { xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]) {
{r->width + br.width + br.x, br.y, r->width, r->height + br.height}}); {r->width + (br.width + br.x), br.y, -(br.width + br.x), r->height + br.height}});
else if (p->parent_layout == L_SPLITV) else if (p->parent_layout == L_SPLITV)
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]) { xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]) {
{br.x, r->height + br.height + br.y, r->width - (2 * br.x), r->height}}); {br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)}});
} }
} }
@ -473,12 +473,12 @@ void x_draw_decoration(Con *con) {
/* 5: draw two unconnected horizontal lines in border color */ /* 5: draw two unconnected horizontal lines in border color */
xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) {p->color->border}); xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) {p->color->border});
Rect *dr = &(con->deco_rect); Rect *dr = &(con->deco_rect);
int deco_diff_l = 2; adjacent_t borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
int deco_diff_r = 2; int deco_diff_l = borders_to_hide & ADJ_LEFT_SCREEN_EDGE ? 0 : con->current_border_width;
if (parent->layout == L_TABBED) { int deco_diff_r = borders_to_hide & ADJ_RIGHT_SCREEN_EDGE ? 0 : con-> current_border_width;
if (TAILQ_PREV(con, nodes_head, nodes) != NULL) if (parent->layout == L_TABBED ||
(parent->layout == L_STACKED && TAILQ_NEXT(con, nodes) != NULL)) {
deco_diff_l = 0; deco_diff_l = 0;
if (TAILQ_NEXT(con, nodes) != NULL)
deco_diff_r = 0; deco_diff_r = 0;
} }
xcb_segment_t segments[] = { xcb_segment_t segments[] = {
@ -1023,7 +1023,7 @@ void x_push_changes(Con *con) {
values[0] = CHILD_EVENT_MASK & ~(XCB_EVENT_MASK_FOCUS_CHANGE); values[0] = CHILD_EVENT_MASK & ~(XCB_EVENT_MASK_FOCUS_CHANGE);
xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values); xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values);
} }
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, last_timestamp); xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME);
if (focused->window != NULL) { if (focused->window != NULL) {
values[0] = CHILD_EVENT_MASK; values[0] = CHILD_EVENT_MASK;
xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values); xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values);
@ -1041,7 +1041,7 @@ void x_push_changes(Con *con) {
if (focused_id == XCB_NONE) { if (focused_id == XCB_NONE) {
DLOG("Still no window focused, better set focus to the root window\n"); DLOG("Still no window focused, better set focus to the root window\n");
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, last_timestamp); xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
ewmh_update_active_window(XCB_WINDOW_NONE); ewmh_update_active_window(XCB_WINDOW_NONE);
focused_id = root; focused_id = root;
} }

View File

@ -8,4 +8,3 @@ inc
META.yml META.yml
i3-cfg-for-* i3-cfg-for-*
- -
Xdummy.so

View File

@ -11,6 +11,7 @@ WriteMakefile(
'AnyEvent::I3' => '0.15', 'AnyEvent::I3' => '0.15',
'X11::XCB' => '0.09', 'X11::XCB' => '0.09',
'Inline' => 0, 'Inline' => 0,
'Inline::C' => 0,
'ExtUtils::PkgConfig' => 0, 'ExtUtils::PkgConfig' => 0,
'Test::More' => '0.94', 'Test::More' => '0.94',
'IPC::Run' => 0, 'IPC::Run' => 0,

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@ use Time::HiRes qw(time);
use IO::Handle; use IO::Handle;
# these are shipped with the testsuite # these are shipped with the testsuite
use lib qw(lib); use lib qw(lib);
use StartXDummy; use StartXServer;
use StatusLine; use StatusLine;
use TestWorker; use TestWorker;
# the following modules are not shipped with Perl # the following modules are not shipped with Perl
@ -43,7 +43,7 @@ sub Log { say $log "@_" }
my %timings; my %timings;
my $help = 0; my $help = 0;
# Number of tests to run in parallel. Important to know how many Xdummy # Number of tests to run in parallel. Important to know how many Xephyr
# instances we need to start (unless @displays are given). Defaults to # instances we need to start (unless @displays are given). Defaults to
# num_cores * 2. # num_cores * 2.
my $parallel = undef; my $parallel = undef;
@ -55,11 +55,11 @@ my %options = (
coverage => 0, coverage => 0,
restart => 0, restart => 0,
); );
my $keep_xdummy_output = 0; my $keep_xserver_output = 0;
my $result = GetOptions( my $result = GetOptions(
"coverage-testing" => \$options{coverage}, "coverage-testing" => \$options{coverage},
"keep-xdummy-output" => \$keep_xdummy_output, "keep-xserver-output" => \$keep_xserver_output,
"valgrind" => \$options{valgrind}, "valgrind" => \$options{valgrind},
"strace" => \$options{strace}, "strace" => \$options{strace},
"xtrace" => \$options{xtrace}, "xtrace" => \$options{xtrace},
@ -86,6 +86,9 @@ foreach my $binary (@binaries) {
die "$binary is not an executable" unless -x $binary; die "$binary is not an executable" unless -x $binary;
} }
qx(Xephyr -help 2>&1);
die "Xephyr was not found in your path. Please install Xephyr (xserver-xephyr on Debian)." if $?;
@displays = split(/,/, join(',', @displays)); @displays = split(/,/, join(',', @displays));
@displays = map { s/ //g; $_ } @displays; @displays = map { s/ //g; $_ } @displays;
@ -97,9 +100,9 @@ my @testfiles = @ARGV;
my $numtests = scalar @testfiles; my $numtests = scalar @testfiles;
# No displays specified, lets start some Xdummy instances. # No displays specified, lets start some Xephyr instances.
if (@displays == 0) { if (@displays == 0) {
@displays = start_xdummy($parallel, $numtests, $keep_xdummy_output); @displays = start_xserver($parallel, $numtests, $keep_xserver_output);
} }
# 1: create an output directory for this test-run # 1: create an output directory for this test-run
@ -115,8 +118,7 @@ symlink("$outdir", "latest") or die "Could not symlink latest to $outdir";
# connect to all displays for two reasons: # connect to all displays for two reasons:
# 1: check if the display actually works # 1: check if the display actually works
# 2: keep the connection open so that i3 is not the only client. this prevents # 2: keep the connection open so that i3 is not the only client. this prevents
# the X server from exiting (Xdummy will restart it, but not quick enough # the X server from exiting
# sometimes)
my @single_worker; my @single_worker;
for my $display (@displays) { for my $display (@displays) {
my $screen; my $screen;
@ -131,7 +133,7 @@ for my $display (@displays) {
# Read previous timing information, if available. We will be able to roughly # Read previous timing information, if available. We will be able to roughly
# predict the test duration and schedule a good order for the tests. # predict the test duration and schedule a good order for the tests.
my $timingsjson = StartXDummy::slurp('.last_run_timings.json'); my $timingsjson = StartXServer::slurp('.last_run_timings.json');
%timings = %{decode_json($timingsjson)} if length($timingsjson) > 0; %timings = %{decode_json($timingsjson)} if length($timingsjson) > 0;
# Re-order the files so that those which took the longest time in the previous # Re-order the files so that those which took the longest time in the previous
@ -220,7 +222,7 @@ printf("\t%s with %.2f seconds\n", $_, $timings{$_})
if ($numtests == 1) { if ($numtests == 1) {
say ''; say '';
say 'Test output:'; say 'Test output:';
say StartXDummy::slurp($logfile); say StartXServer::slurp($logfile);
} }
END { cleanup() } END { cleanup() }
@ -346,7 +348,7 @@ complete-run.pl [files...]
=head1 EXAMPLE =head1 EXAMPLE
To run the whole testsuite on a reasonable number of Xdummy instances (your To run the whole testsuite on a reasonable number of Xephyr instances (your
running X11 will not be touched), run: running X11 will not be touched), run:
./complete-run.pl ./complete-run.pl
@ -365,11 +367,11 @@ will parallelize the tests:
# Run tests on the second X server # Run tests on the second X server
./complete-run.pl -d :1 ./complete-run.pl -d :1
# Run four tests in parallel on some Xdummy servers # Run four tests in parallel on some Xephyr servers
./complete-run.pl -d :1,:2,:3,:4 ./complete-run.pl -d :1,:2,:3,:4
Note that it is not necessary to specify this anymore. If omitted, Note that it is not necessary to specify this anymore. If omitted,
complete-run.pl will start (num_cores * 2) Xdummy instances. complete-run.pl will start (num_cores * 2) Xephyr instances.
=item B<--valgrind> =item B<--valgrind>
@ -392,8 +394,8 @@ Exits i3 cleanly (instead of kill -9) to make coverage testing work properly.
=item B<--parallel> =item B<--parallel>
Number of Xdummy instances to start (if you dont want to start num_cores * 2 Number of Xephyr instances to start (if you dont want to start num_cores * 2
instances for some reason). instances for some reason).
# Run all tests on a single Xdummy instance # Run all tests on a single Xephyr instance
./complete-run.pl -p 1 ./complete-run.pl -p 1

View File

@ -20,7 +20,7 @@ bindsym Mod1+h split h
bindsym Mod1+v split v bindsym Mod1+v split v
# Fullscreen (Mod1+f) # Fullscreen (Mod1+f)
bindsym Mod1+f fullscreen bindsym Mod1+f fullscreen toggle
# Stacking (Mod1+s) # Stacking (Mod1+s)
bindsym Mod1+s layout stacking bindsym Mod1+s layout stacking

View File

@ -1,4 +1,4 @@
package StartXDummy; package StartXServer;
# vim:ts=4:sw=4:expandtab # vim:ts=4:sw=4:expandtab
use strict; use strict;
@ -7,7 +7,7 @@ use Exporter 'import';
use Time::HiRes qw(sleep); use Time::HiRes qw(sleep);
use v5.10; use v5.10;
our @EXPORT = qw(start_xdummy); our @EXPORT = qw(start_xserver);
my @pids; my @pids;
my $x_socketpath = '/tmp/.X11-unix/X'; my $x_socketpath = '/tmp/.X11-unix/X';
@ -19,15 +19,15 @@ sub slurp {
<$fh>; <$fh>;
} }
# forks an Xdummy or Xdmx process # forks an X server process
sub fork_xserver { sub fork_xserver {
my $keep_xdummy_output = shift; my $keep_xserver_output = shift;
my $displaynum = shift; my $displaynum = shift;
my $pid = fork(); my $pid = fork();
die "Could not fork: $!" unless defined($pid); die "Could not fork: $!" unless defined($pid);
if ($pid == 0) { if ($pid == 0) {
# Child, close stdout/stderr, then start Xdummy. # Child, close stdout/stderr, then start Xephyr
if (!$keep_xdummy_output) { if (!$keep_xserver_output) {
close STDOUT; close STDOUT;
close STDERR; close STDERR;
} }
@ -60,16 +60,17 @@ sub wait_for_x {
} }
} }
=head2 start_xdummy($parallel) =head2 start_xserver($parallel)
Starts C<$parallel> (or number of cores * 2 if undef) Xdummy processes (see Starts C<$parallel> (or number of cores * 2 if undef) Xephyr processes (see
the file ./Xdummy) and returns two arrayrefs: a list of X11 display numbers to http://www.freedesktop.org/wiki/Software/Xephyr/) and returns two arrayrefs: a
the Xdummy processes and a list of PIDs of the processes. list of X11 display numbers to the Xephyr processes and a list of PIDs of the
processes.
=cut =cut
sub start_xdummy { sub start_xserver {
my ($parallel, $numtests, $keep_xdummy_output) = @_; my ($parallel, $numtests, $keep_xserver_output) = @_;
my @displays = (); my @displays = ();
my @childpids = (); my @childpids = ();
@ -78,11 +79,8 @@ sub start_xdummy {
my $child = waitpid -1, POSIX::WNOHANG; my $child = waitpid -1, POSIX::WNOHANG;
@pids = grep { $_ != $child } @pids; @pids = grep { $_ != $child } @pids;
return unless @pids == 0; return unless @pids == 0;
print STDERR "All Xdummy processes died.\n"; print STDERR "All X server processes died.\n";
print STDERR "Use ./complete-run.pl --parallel 1 --keep-xdummy-output\n"; print STDERR "Use ./complete-run.pl --parallel 1 --keep-xserver-output\n";
print STDERR "";
print STDERR "A frequent cause for this is missing the DUMMY Xorg module,\n";
print STDERR "package xserver-xorg-video-dummy on Debian.\n";
exit 1; exit 1;
}; };
@ -104,16 +102,13 @@ sub start_xdummy {
my ($displaynum) = map { /(\d+)$/ } reverse sort glob($x_socketpath . '*'); my ($displaynum) = map { /(\d+)$/ } reverse sort glob($x_socketpath . '*');
$displaynum++; $displaynum++;
say "Starting $parallel Xdummy instances, starting at :$displaynum..."; say "Starting $parallel Xephyr instances, starting at :$displaynum...";
my @sockets_waiting; my @sockets_waiting;
for (1 .. $parallel) { for (1 .. $parallel) {
# We use -config /dev/null to prevent Xdummy from using the system my $socket = fork_xserver($keep_xserver_output, $displaynum,
# Xorg configuration. The tests should be independant from the 'Xephyr', ":$displaynum", '-screen', '1280x800',
# actual system X configuration. '-nolisten', 'tcp');
my $socket = fork_xserver($keep_xdummy_output, $displaynum,
'./Xdummy', ":$displaynum", '-config', '/dev/null',
'-configdir', '/dev/null', '-nolisten', 'tcp');
push(@displays, ":$displaynum"); push(@displays, ":$displaynum");
push(@sockets_waiting, $socket); push(@sockets_waiting, $socket);
$displaynum++; $displaynum++;

View File

@ -337,6 +337,7 @@ sub open_window {
$args{name} //= 'Window ' . counter_window(); $args{name} //= 'Window ' . counter_window();
my $window = $x->root->create_child(%args); my $window = $x->root->create_child(%args);
$window->add_hint('input');
if ($before_map) { if ($before_map) {
# TODO: investigate why _create is not needed # TODO: investigate why _create is not needed

Some files were not shown because too many files have changed in this diff Show More