Merge branch 'next'

This commit is contained in:
Michael Stapelberg 2013-08-07 21:13:46 +02:00
commit f682841ae1
75 changed files with 2207 additions and 413 deletions

5
.gitignore vendored
View File

@ -2,11 +2,16 @@
tags
include/GENERATED_*.h
include/all.h.pch
*~
*.swp
*.gcda
*.gcno
test.commands_parser
test.config_parser
testcases/MYMETA.json
testcases/MYMETA.yml
testcases/blib/
testcases/pm_to_blib
*.output
*.tab.*
*.yy.c

99
RELEASE-NOTES-4.6 Normal file
View File

@ -0,0 +1,99 @@
┌──────────────────────────────┐
│ Release notes for i3 v4.6 │
└──────────────────────────────┘
This is the i3 v4.6. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
The main improvement of this release is increased compatibility. We made a few
tiny code changes and hope that Mathematica and Java applications will work
better with i3 now. i3-nagbar should work with more terminal emulators than
before.
For debugging, the shmlog and debuglog commands can be sent via IPC to enable
shared memory logging while i3 is running. For the large number of users using
a release version (i.e. a version without shared memory logging by default),
this will make debugging their issues much simpler.
i3bar now supports click events and can be hidden/shown via an i3 IPC command.
┌────────────────────────────┐
│ Changes in v4.6 │
└────────────────────────────┘
• docs/userguide: mention forgotten layout splitv/splith
• docs/multi-monitor: nVidia ≥ 302.17 works just fine
• docs/wsbar: update (we have i3bar now, i3-wsbar is just an example)
• docs/testsuite: Document fixes and workarounds for test failures
• man/i3-msg.man: updated man page to include all options
• lib/i3test: clarify how to identify open_window() windows in i3 commands
• Use a saner sanity check for floating_reposition
• tabbed: floor(), put extra pixels into the last tab
• raise fullscreen windows on top of all other X11 windows
• Draw indicator border only for split layouts
• re-shuffle struct members to save a bit of memory
• Add 'NoDisplay=true' to i3.application.desktop
• Store aspect_ratio instead of weird proportional_{width,height}
• Implement shmlog command
• Implement debuglog command
• Implement unmark command
• actively delete _NET_WORKAREA on startup
• Handle the _NET_REQUEST_FRAME_EXTENTS ClientMessage (java compat)
• i3bar: add click events
• i3bar: fix -b parameter, fix usage description
• i3bar: restore compatibility with libyajl version 1
• i3bar: unhide hidden i3bar when mode is active
• i3bar: fix font display height in i3bar
• i3bar: introduced i3 command for changing the hidden state and mode
• i3bar: fix wrong placement of i3bar when connecting/disconnecting outputs
• i3bar: draw workspace buttons at x=0 instead of x=1
• i3-nagbar: take our terminal execution kludge to the next level
• i3-nagbar: Bugfix: -m requires an argument (crashes if none specified)
• i3-dmenu-desktop: run commands when they dont match a .desktop file
(e.g. enter “i3 layout stacking”)
• i3-dmenu-desktop: honor Path= key
• contrib/dump-asy.pl: Fix $ and & in window titles
• contrib/dump-asy.pl: Display nicer double-quotes
• contrib/gtk-tree-watch.pl: Remove bogus default socket path
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• Bugfix: ipc: use correct workspace in workspace change event
• Bugfix: fix floating window size with hide_edge_borders
• Bugfix: Fix parsing of comments in the config file
• Bugfix: Fix error messages for the debug log
• Bugfix: shm_unlink the correct file when handling errors
• Bugfix: Fix shm logging on FreeBSD
• Bugfix: Fix restarting with 32 bit depth windows
• Bugfix: Fix scratchpad_show on non-scratchpad windows
• Bugfix: i3bar: mark IPC fd CLOEXEC
• Bugfix: fix crash when not having tray_output configured
• Bugfix: make sure that resize will take place even if pixel is smaller
than size increments.
• Bugfix: render_con: fix height rounding in aspect ratio computation
• Bugfix: fix problem when moving fullscreen window to scratchpad
• Bugfix: Unmap windows before reparenting them to the root window
(fixes Mathematica)
• Bugfix: update parent urgency hint if a child is removed.
• Bugfix: fix bus error on OpenBSD/sparc64
• Bugfix: fix focus handling in 'floating disable' on non-visible windows
• Bugfix: ignore spaces in front of default workspace name
• Bugfix: call i3-nagbar correctly for configfiles without the font directive
• Bugfix: resize and center a scratchpad even when a criteria is used.
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
Alexander, Alexander Berntsen, Arun Persaud, badboy, Baptiste Daroussin,
Clément Bœsch, Diego Ongaro, Eelis van der Weegen, Eika Enge, enkore, Eric S.
Raymond, Franck Michea, haptix, HedgeMage, koebi, Layus, Mayhem, Merovius,
necoro, oblique, Philippe Virouleau, phillip, psychon, Simon Elsbrock, Simon
Wesp, Thomas Adam, tobiasu, vandannen, xeen, Yuxuan Shui
-- Michael Stapelberg, 2013-08-07

View File

@ -32,13 +32,15 @@ sub dump_node {
my $w = (defined($n->{window}) ? $n->{window} : "N");
my $na = $n->{name};
$na =~ s/#/\\#/g;
$na =~ s/\$/\\\$/g;
$na =~ s/&/\\&/g;
$na =~ s/_/\\_/g;
$na =~ s/~/\\textasciitilde{}/g;
my $type = 'leaf';
if (!defined($n->{window})) {
$type = $n->{orientation} . '-split';
}
my $name = qq|\\"$na\\" ($type)|;
my $name = qq|``$na'' ($type)|;
print $tmp "TreeNode n" . $n->{id} . " = makeNode(";

View File

@ -19,7 +19,7 @@ $window->signal_connect('delete_event' => sub { Gtk2->main_quit; });
my $tree_store = Gtk2::TreeStore->new(qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/);
my $i3 = i3("/tmp/nestedcons");
my $i3 = i3();
my $tree_view = Gtk2::TreeView->new($tree_store);

16
debian/changelog vendored
View File

@ -1,8 +1,20 @@
i3-wm (4.4.1-0) unstable; urgency=low
i3-wm (4.5.2-1) experimental; urgency=low
* NOT YET RELEASED
-- Michael Stapelberg <stapelberg@debian.org> Wed, 12 Dec 2012 00:23:32 +0100
-- Michael Stapelberg <stapelberg@debian.org> Mon, 18 Mar 2013 23:01:30 +0100
i3-wm (4.5.1-1) experimental; urgency=low
* New upstream release
-- Michael Stapelberg <stapelberg@debian.org> Mon, 18 Mar 2013 22:50:12 +0100
i3-wm (4.5-1) experimental; urgency=low
* New upstream release
-- Michael Stapelberg <stapelberg@debian.org> Tue, 12 Mar 2013 13:51:04 +0100
i3-wm (4.4-1) experimental; urgency=low

2
debian/rules vendored
View File

@ -38,7 +38,7 @@ override_dh_auto_build:
$(MAKE) -C docs
override_dh_installchangelogs:
dh_installchangelogs RELEASE-NOTES-4.4
dh_installchangelogs RELEASE-NOTES-4.5.1
override_dh_install:
$(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install

View File

@ -57,7 +57,7 @@ all, most users sooner or later tend to lay out their windows in a way which
corresponds to tiling or stacking mode in i3. Therefore, why not let i3 do this
for you? Certainly, its faster than you could ever do it.
The problem with most tiling window managers is that they are too unflexible.
The problem with most tiling window managers is that they are too inflexible.
In my opinion, a window manager is just another tool, and similar to vim which
can edit all kinds of text files (like source code, HTML, …) and is not limited
to a specific file type, a window manager should not limit itself to a certain
@ -361,7 +361,7 @@ managed at all:
* The override_redirect must not be set. Windows with override_redirect shall
not be managed by a window manager
Afterwards, i3 gets the intial geometry and reparents the window (see
Afterwards, i3 gets the initial geometry and reparents the window (see
`reparent_window()`) if it wasnt already managed.
Reparenting means that for each window which is reparented, a new window,
@ -383,7 +383,7 @@ target workspace is not visible, the window will not be mapped.
== What happens when an application is started?
i3 does not care for applications. All it notices is when new windows are
i3 does not care about applications. All it notices is when new windows are
mapped (see `src/handlers.c`, `handle_map_request()`). The window is then
reparented (see section "Manage windows").
@ -534,7 +534,7 @@ position/size is different: They are placed next to each other on a single line
==== Dock area layout
This is a special case. Users cannot chose the dock area layout, but it will be
This is a special case. Users cannot choose the dock area layout, but it will be
set for the dock area containers. In the dockarea layout (at the moment!),
windows will be placed above each other.
@ -944,9 +944,11 @@ Without much ado, here is the list of cases which need to be considered:
== Using git / sending patches
=== Introduction
For a short introduction into using git, see
http://www.spheredev.org/wiki/Git_for_the_lazy or, for more documentation, see
http://git-scm.com/documentation
http://web.archive.org/web/20121024222556/http://www.spheredev.org/wiki/Git_for_the_lazy
or, for more documentation, see http://git-scm.com/documentation
Please talk to us before working on new features to see whether they will be
accepted. There are a few things which we dont want to see in i3, e.g. a
@ -963,6 +965,17 @@ them in the bugtracker, since all reviews should be done in public at
http://cr.i3wm.org/. In order to make your review go as fast as possible, you
could have a look at previous reviews and see what the common mistakes are.
=== Which branch to use?
Work on i3 generally happens in two branches: “master” and “next”. Since
“master” is what people get when they check out the git repository, its
contents are always stable. That is, it contains the source code of the latest
release, plus any bugfixes that were applied since that release.
New features are only found in the “next” branch. Therefore, if you are working
on a new feature, use the “next” branch. If you are working on a bugfix, use
the “next” branch, too, but make sure your code also works on “master”.
== Thought experiments
In this section, we collect thought experiments, so that we dont forget our

View File

@ -51,7 +51,7 @@ consists of a single JSON hash:
*All features example*:
------------------------------
{ "version": 1, "stop_signal": 10, "cont_signal": 12 }
{ "version": 1, "stop_signal": 10, "cont_signal": 12, "click_events": true }
------------------------------
(Note that before i3 v4.3 the precise format had to be +{"version":1}+,
@ -110,6 +110,9 @@ cont_signal::
Specify to i3bar the signal (as an integer)to send to continue your
processing.
The default value (if none is specified) is SIGCONT.
click_events::
If specified and true i3bar will write a infinite array (same as above)
to your stdin.
=== Blocks in detail
@ -210,3 +213,28 @@ An example of a block which uses all possible entries follows:
"separator_block_width": 9
}
------------------------------------------
=== Click events
If enabled i3bar will send you notifications if the user clicks on a block and
looks like this:
name::
Name of the block, if set
instance::
Instance of the block, if set
x, y::
X11 root window coordinates where the click occured
button:
X11 button ID (for example 1 to 3 for left/middle/right mouse button)
*Example*:
------------------------------------------
{
"name": "ethernet",
"instance": "eth0",
"button": 1,
"x": 1320,
"y": 1400
}
------------------------------------------

View File

@ -458,9 +458,8 @@ JSON dump:
=== MARKS reply
The reply consists of a single array of strings for each container that has a
mark. The order of that array is undefined. If more than one container has the
same mark, it will be represented multiple times in the reply (the array
contents are not unique).
mark. A mark can only be set on one container, so the array is unique.
The order of that array is undefined.
If no window has a mark the response will be the empty array [].
@ -626,6 +625,9 @@ mode (2)::
window (3)::
Sent when a client's window is successfully reparented (that is when i3
has finished fitting it into a container).
barconfig_update (4)::
Sent when the hidden_state or mode field in the barconfig of any bar
instance was updated.
*Example:*
--------------------------------------------------------------------
@ -723,6 +725,24 @@ window title as "urxvt").
}
---------------------------
=== barconfig_update event
This event consists of a single serialized map reporting on options from the
barconfig of the specified bar_id that were updated in i3. The map always
consists of a property +id (string)+, which specifies to which bar instance the
sent config update belongs, a property +hidden_state (string)+, which indicates
the hidden_state of an i3bar instance, and a property +mode (string)+, which
corresponds to the current mode.
*Example:*
---------------------------
{
"id": "bar-0",
"hidden_state": "hide"
"mode": "hide"
}
---------------------------
== See also (existing libraries)
[[libraries]]

View File

@ -1,15 +1,16 @@
The multi-monitor situation
===========================
Michael Stapelberg <michael+i3@stapelberg.de>
September 2011
Michael Stapelberg <michael@i3wm.org>
April 2013
…or: oh no, I have an nVidia graphics card!
Please upgrade your nVidia driver to version 302.17 or newer and i3 will just
work. This document is kept around for historic reasons only.
== The quick fix
If you are using the nVidia binary graphics driver (also known as 'blob')
you need to use the +--force-xinerama+ flag (in your .xsession) when starting
i3, like so:
before version 302.17, you need to use the +--force-xinerama+ flag (in your
.xsession) when starting i3, like so:
.Example:
----------------------------------------------

View File

@ -143,6 +143,16 @@ Result: PASS
$ 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:
---------------------------------------------------
$ ./complete-run.pl --parallel=1 --keep-xdummy-output
---------------------------------------------------
One common cause of failures is not having the X dummy server module
installed. Under Debian and Ubuntu this is the package
+xserver-xorg-video-dummy+.
==== IPC interface
The testsuite makes extensive use of the IPC (Inter-Process Communication)

View File

@ -1,7 +1,7 @@
i3 Users Guide
===============
Michael Stapelberg <michael@i3wm.org>
February 2013
March 2013
This document contains all the information you need to configure and use the i3
window manager. If it does not, please check http://faq.i3wm.org/ first, then
@ -26,8 +26,8 @@ are your homerow.
== Using i3
Throughout this guide, the keyword +$mod+ will be used to refer to the
configured modifier. This is the Alt key (Mod1) by default, with windows (Mod4)
being a popular alternative.
configured modifier. This is the Alt key (Mod1) by default, with the Windows
key (Mod4) being a popular alternative.
=== Opening terminals and moving around
@ -147,7 +147,7 @@ columns/rows with your keyboard.
=== Restarting i3 inplace
To restart i3 inplace (and thus get into a clean state if there is a bug, or
To restart i3 in place (and thus get into a clean state if there is a bug, or
to upgrade to a newer version of i3) you can use +$mod+Shift+r+.
=== Exiting i3
@ -156,11 +156,12 @@ To cleanly exit i3 without killing your X server, you can use +$mod+Shift+e+.
=== Floating
Floating mode is the opposite of tiling mode. The position and size of a window
are not managed by i3, but by you. Using this mode violates the tiling
paradigm but can be useful for some corner cases like "Save as" dialog
windows, or toolbar windows (GIMP or similar). Those windows usually set the
appropriate hint and are opened in floating mode by default.
Floating mode is the opposite of tiling mode. The position and size of
a window are not managed automatically by i3, but manually by
you. Using this mode violates the tiling paradigm but can be useful
for some corner cases like "Save as" dialog windows, or toolbar
windows (GIMP or similar). Those windows usually set the appropriate
hint and are opened in floating mode by default.
You can toggle floating mode for a window by pressing +$mod+Shift+Space+. By
dragging the windows titlebar with your mouse you can move the window
@ -259,7 +260,7 @@ other one being the terminal window you moved down.
[[configuring]]
== Configuring i3
This is where the real fun begins ;-). Most things are very dependant on your
This is where the real fun begins ;-). Most things are very dependent on your
ideal working environment so we cant make reasonable defaults for them.
While not using a programming language for the configuration, i3 stays
@ -761,7 +762,7 @@ from single windows outside of a split container.
=== Interprocess communication
i3 uses unix sockets to provide an IPC interface. This allows third-party
i3 uses Unix sockets to provide an IPC interface. This allows third-party
programs to get information from i3, such as the current workspaces
(to display a workspace bar), and to control i3.
@ -995,20 +996,39 @@ bar {
=== Display mode
You can have i3bar either be visible permanently at one edge of the screen
(+dock+ mode) or make it show up when you press your modifier key (+hide+
You can either have i3bar be visible permanently at one edge of the screen
(+dock+ mode) or make it show up when you press your modifier key (+hide+ mode).
It is also possible to force i3bar to always stay hidden (+invisible+
mode). The modifier key can be configured using the +modifier+ option.
The mode option can be changed during runtime through the +bar mode+ command.
On reload the mode will be reverted to its configured value.
The hide mode maximizes screen space that can be used for actual windows. Also,
i3bar sends the +SIGSTOP+ and +SIGCONT+ signals to the statusline process to
save battery power.
The default is dock mode; in hide mode, the default modifier is Mod4 (usually
the windows key).
Invisible mode allows to permanently maximize screen space, as the bar is never
shown. Thus, you can configure i3bar to not disturb you by popping up because
of an urgency hint or because the modifier key is pressed.
In order to control whether i3bar is hidden or shown in hide mode, there exists
the hidden_state option, which has no effect in dock mode or invisible mode. It
indicates the current hidden_state of the bar: (1) The bar acts like in normal
hide mode, it is hidden and is only unhidden in case of urgency hints or by
pressing the modifier key (+hide+ state), or (2) it is drawn on top of the
currently visible workspace (+show+ state).
Like the mode, the hidden_state can also be controlled through i3, this can be
done by using the +bar hidden_state+ command.
The default mode is dock mode; in hide mode, the default modifier is Mod4 (usually
the windows key). The default value for the hidden_state is hide.
*Syntax*:
----------------
mode <dock|hide>
mode <dock|hide|invisible>
hidden_state <hide|show>
modifier <Modifier>
----------------
@ -1016,12 +1036,31 @@ modifier <Modifier>
----------------
bar {
mode hide
hidden_state hide
modifier Mod1
}
----------------
Available modifiers are Mod1-Mod5, Shift, Control (see +xmodmap(1)+).
=== Bar ID
Specifies the bar ID for the configured bar instance. If this option is missing,
the ID is set to 'bar-x', where x corresponds to the position of the embedding
bar block in the config file ('bar-0', 'bar-1', ...).
*Syntax*:
---------------------
id <bar_id>
---------------------
*Example*:
---------------------
bar {
id bar-1
}
---------------------
[[i3bar_position]]
=== Position
@ -1223,7 +1262,7 @@ bindsym $mod+x move container to workspace 3; workspace 3
[[command_criteria]]
Furthermore, you can change the scope of a command, that is, which containers
Furthermore, you can change the scope of a command - that is, which containers
should be affected by that command, by using various criteria. These are
prefixed in square brackets to every command. If you want to kill all windows
which have the class Firefox, use:
@ -1319,9 +1358,9 @@ bindsym $mod+h split horizontal
=== Manipulating layout
Use +layout toggle split+, +layout stacking+ or +layout tabbed+ to change the
current container layout to splith/splitv, stacking or tabbed layout,
respectively.
Use +layout toggle split+, +layout stacking+, +layout tabbed+, +layout splitv+
or +layout splith+ to change the current container layout to splith/splitv,
stacking, tabbed layout, splitv or splith, respectively.
To make the current window (!) fullscreen, use +fullscreen+, to make
it floating (or tiling again) use +floating enable+ respectively +floating disable+
@ -1329,7 +1368,7 @@ it floating (or tiling again) use +floating enable+ respectively +floating disab
*Syntax*:
--------------
layout <tabbed|stacking>
layout <default|tabbed|stacking|splitv|splith>
layout toggle [split|all]
--------------
@ -1640,9 +1679,10 @@ bindsym $mod+a [class="urxvt" title="VIM"] focus
This feature is like the jump feature: It allows you to directly jump to a
specific window (this means switching to the appropriate workspace and setting
focus to the windows). However, you can directly mark a specific window with
an arbitrary label and use it afterwards. You do not need to ensure that your
windows have unique classes or titles, and you do not need to change your
configuration file.
an arbitrary label and use it afterwards. You can unmark the label in the same
way, using the unmark command. If you don't specify a label, unmark removes all
marks. You do not need to ensure that your windows have unique classes or
titles, and you do not need to change your configuration file.
As the command needs to include the label with which you want to mark the
window, you cannot simply bind it to a key. +i3-input+ is a tool created
@ -1653,12 +1693,14 @@ can also prefix this command and display a custom prompt for the input dialog.
------------------------------
mark identifier
[con_mark="identifier"] focus
unmark identifier
------------------------------
*Example (in a terminal)*:
------------------------------
$ i3-msg mark irssi
$ i3-msg '[con_mark="irssi"] focus'
$ i3-msg unmark irssi
------------------------------
///////////////////////////////////////////////////////////////////
@ -1723,6 +1765,51 @@ stack-limit rows 5
image:stacklimit.png[Container limited to two columns]
///////////////////////////////////////////////////////////////////////////////
[[shmlog]]
=== Enabling shared memory logging
As described in http://i3wm.org/docs/debugging.html, i3 can log to a shared
memory buffer, which you can dump using +i3-dump-log+. The +shmlog+ command
allows you to enable or disable the shared memory logging at runtime.
Note that when using +shmlog <size_in_bytes>+, the current log will be
discarded and a new one will be started.
*Syntax*:
------------------------------
shmlog <size_in_bytes>
shmlog <on|off|toggle>
------------------------------
*Examples*:
---------------
# Enable/disable logging
bindsym $mod+x shmlog toggle
# or, from a terminal:
# increase the shared memory log buffer to 50 MiB
i3-msg shmlog $((50*1024*1024))
---------------
=== Enabling debug logging
The +debuglog+ command allows you to enable or disable debug logging at
runtime. Debug logging is much more verbose than non-debug logging. This
command does not activate shared memory logging (shmlog), and as such is most
likely useful in combination with the above-described <<shmlog>> command.
*Syntax*:
------------------------
debuglog <on|off|toggle>
------------------------
*Examples*:
------------
# Enable/disable logging
bindsym $mod+x debuglog toggle
------------
=== Reloading/Restarting/Exiting
You can make i3 reload its configuration file with +reload+. You can also
@ -1774,6 +1861,38 @@ bindsym $mod+minus scratchpad show
bindsym mod4+s [title="^Sup ::"] scratchpad show
------------------------------------------------
=== i3bar control
There are two options in the configuration of each i3bar instance that can be
changed during runtime by invoking a command through i3. The commands +bar
hidden_state+ and +bar mode+ allow setting the current hidden_state
respectively mode option of each bar. It is also possible to toggle between
hide state and show state as well as between dock mode and hide mode. Each
i3bar instance can be controlled individually by specifying a bar_id, if none
is given, the command is executed for all bar instances.
*Syntax*:
---------------
bar hidden_state hide|show|toggle [<bar_id>]
bar mode dock|hide|invisible|toggle [<bar_id>]
---------------
*Examples*:
------------------------------------------------
# Toggle between hide state and show state
bindsym $mod+m bar hidden_state toggle
# Toggle between dock mode and hide mode
bindsym $mod+n bar mode toggle
# Set the bar instance with id 'bar-1' to switch to hide mode
bindsym $mod+b bar mode hide bar-1
# Set the bar instance with id 'bar-1' to always stay hidden
bindsym $mod+Shift+b bar mode invisible bar-1
------------------------------------------------
[[multi_monitor]]
== Multiple monitors

View File

@ -1,23 +1,18 @@
External workspace bars
=======================
Michael Stapelberg <michael+i3@stapelberg.de>
May 2010
Michael Stapelberg <michael@i3wm.org>
April 2013
This document describes why the internal workspace bar is minimal and how an
external workspace bar can be used. It explains the concepts using +i3-wsbar+
as the reference implementation.
i3 comes with i3bar by default, a simple bar that is sufficient for most users.
In case you are unhappy with it, this document explains how to use a different,
external workspace bar. Note that we do not provide support for external
programs.
== Internal and external bars
The internal workspace bar of i3 is meant to be a reasonable default so that
you can use i3 without having too much hassle when setting it up. It is quite
simple and intended to stay this way. So, there is no way to display your own
information in this bar (unlike dwm, wmii, awesome, …).
We chose not to implement such a mechanism because that would be duplicating
already existing functionality of tools such as dzen2, xmobar and similar.
Instead, you should disable the internal bar and use an external workspace bar
(which communicates with i3 through its IPC interface).
simple and intended to stay this way.
== dock mode
@ -25,10 +20,10 @@ You typically want to see the same workspace bar on every workspace on a
specific screen. Also, you dont want to place the workspace bar somewhere
in your layout by hand. This is where dock mode comes in: When a program sets
the appropriate hint (_NET_WM_WINDOW_TYPE_DOCK), it will be managed in dock
mode by i3. That means it will be placed at the bottom of the screen (while
other edges of the screen are possible in the NetWM standard, this is not yet
implemented in i3), it will not overlap any other window and it will be on
every workspace for the specific screen it was placed on initially.
mode by i3. That means it will be placed at the bottom or top of the screen
(while other edges of the screen are possible in the NetWM standard, this is
not yet implemented in i3), it will not overlap any other window and it will be
on every workspace for the specific screen it was placed on initially.
== The IPC interface
@ -37,8 +32,8 @@ provide the bar program with the current workspaces and output (as in VGA-1,
LVDS-1, …) configuration. In the other direction, the program has to be able
to switch to specific workspaces.
By default, the IPC interface is enabled and places its UNIX socket in
+~/.i3/ipc.sock+.
By default, the IPC interface is enabled and you can get the path to the socket
by calling +i3 --get-socketpath+.
To learn more about the protocol which is used for IPC, see +docs/ipc+.
@ -49,17 +44,17 @@ external workspace bar implementation needs to make sure that when you change
the resolution of any of your screens (or enable/disable an output), the bars
will be adjusted properly.
== i3-wsbar, the reference implementation
== i3-wsbar, an example implementation
Please keep in mind that +i3-wsbar+ is just a reference implementation. It is
shipped with i3 to have a reasonable default. Thus, +i3-wsbar+ is designed to
work well with dzen2 and there are no plans to make it more generic.
+i3-wsbar+ used to be the reference implementation before we had +i3bar+.
Nowadays, it is not shipped with release tarballs, but you can still get it at
http://code.stapelberg.de/git/i3/tree/contrib/i3-wsbar
=== The big picture
The most common reason to use an external workspace bar is to integrate system
information such as what +i3status+ provides into the workspace bar (to save
screen space). So, we have +i3status+ or a similar program, which only provides
information such as what +i3status+ or +conky+ provide into the workspace bar.
So, we have +i3status+ or a similar program, which only provides
text output (formatted in some way). To display this text nicely on the screen,
there are programs such as dzen2, xmobar and similar. We will stick to dzen2
from here on. So, we have the output of i3status, which needs to go into dzen2
@ -89,6 +84,3 @@ To actually get a benefit, you want to give +i3-wsbar+ some input:
------------------------------------------
i3status | i3-wsbar -c "dzen2 -x %x -dock"
------------------------------------------
It is recommended to place the above command in your i3 configuration file
to start it automatically with i3.

View File

@ -13,7 +13,7 @@
#endif
/* For systems without getline, fall back to fgetln */
#if defined(__APPLE__) || (defined(__FreeBSD__) && __FreeBSD_version < 800000)
#if defined(__APPLE__)
#define USE_FGETLN
#elif defined(__FreeBSD__)
/* Defining this macro before including stdio.h is necessary in order to have

View File

@ -45,7 +45,7 @@ my $result = GetOptions(
'dmenu=s' => \$dmenu_cmd,
'entry-type=s' => \@entry_types,
'version' => sub {
say "dmenu-desktop 1.4 © 2012-2013 Michael Stapelberg";
say "dmenu-desktop 1.5 © 2012-2013 Michael Stapelberg";
exit 0;
},
'help' => sub {
@ -175,6 +175,7 @@ for my $file (values %desktops) {
$names{$key} = $value;
} elsif ($key eq 'Exec' ||
$key eq 'TryExec' ||
$key eq 'Path' ||
$key eq 'Type') {
$apps{$base}->{$key} = $value;
} elsif ($key eq 'NoDisplay' ||
@ -346,7 +347,13 @@ if (exists($choices{$choice})) {
last;
}
if (!defined($app)) {
die "Invalid input: “$choice” does not match any application.";
warn "Invalid input: “$choice” does not match any application. Trying to execute nevertheless.";
$app->{Name} = '';
$app->{Exec} = $choice;
# We assume that the app is old and does not support startup
# notifications because it doesnt ship a desktop file.
$app->{StartupNotify} = 0;
$app->{_Location} = '';
}
}
@ -397,6 +404,10 @@ $exec =~ s/%k/$location/g;
# Literal % characters are represented as %%.
$exec =~ s/%%/%/g;
if (exists($app->{Path}) && $app->{Path} ne '') {
$exec = 'cd ' . $app->{Path} . ' && ' . $exec;
}
my $nosn = '';
my $cmd;
if (exists($app->{Terminal}) && $app->{Terminal}) {
@ -501,7 +512,7 @@ command), and "libreoffice-writer" (type = filename).
=head1 VERSION
Version 1.4
Version 1.5
=head1 AUTHOR

View File

@ -164,15 +164,18 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
/* Also closes fd */
fclose(script);
char *link_path;
sasprintf(&link_path, "%s.nagbar_cmd", script_path);
symlink(get_exe_path(argv0), link_path);
char *terminal_cmd;
sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", argv0);
sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path);
printf("argv0 = %s\n", argv0);
printf("terminal_cmd = %s\n", terminal_cmd);
setenv("_I3_NAGBAR_CMD", script_path, 1);
start_application(terminal_cmd);
unsetenv("_I3_NAGBAR_CMD");
free(link_path);
free(terminal_cmd);
free(script_path);
@ -275,23 +278,35 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
}
int main(int argc, char *argv[]) {
/* The following lines are a horrible kludge. Because terminal emulators
* have different ways of interpreting the -e command line argument (some
* need -e "less /etc/fstab", others need -e less /etc/fstab), we need to
* write commands to a script and then just run that script. However, since
* on some machines, $XDG_RUNTIME_DIR and $TMPDIR are mounted with noexec,
* we cannot directly execute the script either.
/* The following lines are a terribly horrible kludge. Because terminal
* emulators have different ways of interpreting the -e command line
* argument (some need -e "less /etc/fstab", others need -e less
* /etc/fstab), we need to write commands to a script and then just run
* that script. However, since on some machines, $XDG_RUNTIME_DIR and
* $TMPDIR are mounted with noexec, we cannot directly execute the script
* either.
*
* Therefore, we run i3-nagbar instead and pass the path to the script in
* the environment variable $_I3_NAGBAR_CMD. i3-nagbar then execs /bin/sh
* with that path in order to run that script.
* Initially, we tried to pass the command via the environment variable
* _I3_NAGBAR_CMD. But turns out that some terminal emulators such as
* xfce4-terminal run all windows from a single master process and only
* pass on the command (not the environment) to that master process.
*
* Therefore, we symlink i3-nagbar (which MUST reside on an executable
* filesystem) with a special name and run that symlink. When i3-nagbar
* recognizes its started as a binary ending in .nagbar_cmd, it strips off
* the .nagbar_cmd suffix and runs /bin/sh on argv[0]. That way, we can run
* a shell script on a noexec filesystem.
*
* From a security point of view, i3-nagbar is just an alias to /bin/sh in
* certain circumstances. This should not open any new security issues, I
* hope. */
char *cmd = NULL;
if ((cmd = getenv("_I3_NAGBAR_CMD")) != NULL) {
unsetenv("_I3_NAGBAR_CMD");
const size_t argv0_len = strlen(argv[0]);
if (argv0_len > strlen(".nagbar_cmd") &&
strcmp(argv[0] + argv0_len - strlen(".nagbar_cmd"), ".nagbar_cmd") == 0) {
unlink(argv[0]);
cmd = strdup(argv[0]);
*(cmd + argv0_len - strlen(".nagbar_cmd")) = '\0';
execl("/bin/sh", "/bin/sh", cmd, NULL);
err(EXIT_FAILURE, "execv(/bin/sh, /bin/sh, %s)", cmd);
}

View File

@ -1,6 +1,7 @@
[Desktop Entry]
Type=Application
Name=i3
NoDisplay=true
Comment=improved dynamic tiling window manager
Exec=i3
X-GNOME-WMName=i3

View File

@ -33,6 +33,12 @@ typedef struct {
* The signal requested by the client to inform it of theun hidden state of i3bar
*/
int cont_signal;
/**
* Enable click events
*/
bool click_events;
bool click_events_init;
} i3bar_child;
/*
@ -68,4 +74,10 @@ void stop_child(void);
*/
void cont_child(void);
/*
* Generates a click event, if enabled.
*
*/
void send_block_clicked(int button, const char *name, const char *instance, int x, int y);
#endif

View File

@ -54,6 +54,10 @@ struct status_block {
uint32_t x_offset;
uint32_t x_append;
/* Optional */
char *name;
char *instance;
TAILQ_ENTRY(status_block) blocks;
};

View File

@ -19,7 +19,6 @@ typedef enum {
} position_t;
typedef struct config_t {
int hide_on_modifier;
int modifier;
position_t position;
int verbose;
@ -31,6 +30,12 @@ typedef struct config_t {
char *tray_output;
int num_outputs;
char **outputs;
/* 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 } hide_on_modifier;
/* The current hidden_state of the bar, which indicates whether it is hidden or shown */
enum { S_HIDE = 0, S_SHOW = 1 } hidden_state;
} config_t;
config_t config;

View File

@ -114,7 +114,7 @@ void realloc_sl_buffer(void);
* Reconfigure all bars and create new for newly activated outputs
*
*/
void reconfig_windows(void);
void reconfig_windows(bool redraw_bars);
/*
* Render the bars, with buttons and statusline

View File

@ -21,6 +21,7 @@
#include <yajl/yajl_common.h>
#include <yajl/yajl_parse.h>
#include <yajl/yajl_version.h>
#include <yajl/yajl_gen.h>
#include "common.h"
@ -35,6 +36,9 @@ ev_child *child_sig;
yajl_callbacks callbacks;
yajl_handle parser;
/* JSON generator for stdout */
yajl_gen gen;
typedef struct parser_ctx {
/* True if one of the parsed blocks was urgent */
bool has_urgent;
@ -53,6 +57,8 @@ parser_ctx parser_context;
struct statusline_head statusline_head = TAILQ_HEAD_INITIALIZER(statusline_head);
char *statusline_buffer = NULL;
int child_stdin;
/*
* Stop and free() the stdin- and sigchild-watchers
*
@ -85,6 +91,8 @@ static int stdin_start_array(void *context) {
first = TAILQ_FIRST(&statusline_head);
I3STRING_FREE(first->full_text);
FREE(first->color);
FREE(first->name);
FREE(first->instance);
TAILQ_REMOVE(&statusline_head, first, blocks);
free(first);
}
@ -152,6 +160,18 @@ static int stdin_string(void *context, const unsigned char *val, unsigned int le
ctx->block.min_width = (uint32_t)predict_text_width(text);
i3string_free(text);
}
if (strcasecmp(ctx->last_map_key, "name") == 0) {
char *copy = (char*)malloc(len+1);
strncpy(copy, (const char *)val, len);
copy[len] = 0;
ctx->block.name = copy;
}
if (strcasecmp(ctx->last_map_key, "instance") == 0) {
char *copy = (char*)malloc(len+1);
strncpy(copy, (const char *)val, len);
copy[len] = 0;
ctx->block.instance = copy;
}
return 1;
}
@ -336,6 +356,21 @@ void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
cleanup();
}
void child_write_output(void) {
if (child.click_events) {
const unsigned char *output;
#if YAJL_MAJOR < 2
unsigned int size;
#else
size_t size;
#endif
yajl_gen_get_buf(gen, &output, &size);
write(child_stdin, output, size);
write(child_stdin, "\n", 1);
yajl_gen_clear(gen);
}
}
/*
* Start a child-process with the specified command and reroute stdin.
* We actually start a $SHELL to execute the command so we don't have to care
@ -357,14 +392,22 @@ void start_child(char *command) {
yajl_parser_config parse_conf = { 0, 0 };
parser = yajl_alloc(&callbacks, &parse_conf, NULL, (void*)&parser_context);
gen = yajl_gen_alloc(NULL, NULL);
#else
parser = yajl_alloc(&callbacks, NULL, &parser_context);
gen = yajl_gen_alloc(NULL);
#endif
if (command != NULL) {
int fd[2];
if (pipe(fd) == -1)
err(EXIT_FAILURE, "pipe(fd)");
int pipe_in[2]; /* pipe we read from */
int pipe_out[2]; /* pipe we write to */
if (pipe(pipe_in) == -1)
err(EXIT_FAILURE, "pipe(pipe_in)");
if (pipe(pipe_out) == -1)
err(EXIT_FAILURE, "pipe(pipe_out)");
child.pid = fork();
switch (child.pid) {
@ -372,10 +415,13 @@ void start_child(char *command) {
ELOG("Couldn't fork(): %s\n", strerror(errno));
exit(EXIT_FAILURE);
case 0:
/* Child-process. Reroute stdout and start shell */
close(fd[0]);
/* Child-process. Reroute streams and start shell */
dup2(fd[1], STDOUT_FILENO);
close(pipe_in[0]);
close(pipe_out[1]);
dup2(pipe_in[1], STDOUT_FILENO);
dup2(pipe_out[0], STDIN_FILENO);
static const char *shell = NULL;
@ -385,10 +431,13 @@ void start_child(char *command) {
execl(shell, shell, "-c", command, (char*) NULL);
return;
default:
/* Parent-process. Rerout stdin */
close(fd[1]);
/* Parent-process. Reroute streams */
dup2(fd[0], STDIN_FILENO);
close(pipe_in[1]);
close(pipe_out[0]);
dup2(pipe_in[0], STDIN_FILENO);
child_stdin = pipe_out[1];
break;
}
@ -409,6 +458,52 @@ void start_child(char *command) {
atexit(kill_child_at_exit);
}
void child_click_events_initialize(void) {
if (!child.click_events_init) {
yajl_gen_array_open(gen);
child_write_output();
child.click_events_init = true;
}
}
void child_click_events_key(const char *key) {
yajl_gen_string(gen, (const unsigned char *)key, strlen(key));
}
/*
* Generates a click event, if enabled.
*
*/
void send_block_clicked(int button, const char *name, const char *instance, int x, int y) {
if (child.click_events) {
child_click_events_initialize();
yajl_gen_map_open(gen);
if (name) {
child_click_events_key("name");
yajl_gen_string(gen, (const unsigned char *)name, strlen(name));
}
if (instance) {
child_click_events_key("instance");
yajl_gen_string(gen, (const unsigned char *)instance, strlen(instance));
}
child_click_events_key("button");
yajl_gen_integer(gen, button);
child_click_events_key("x");
yajl_gen_integer(gen, x);
child_click_events_key("y");
yajl_gen_integer(gen, y);
yajl_gen_map_close(gen);
child_write_output();
}
}
/*
* kill()s the child-process (if any). Called when exit()ing.
*

View File

@ -73,7 +73,15 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in
if (!strcmp(cur_key, "mode")) {
DLOG("mode = %.*s, len = %d\n", len, val, len);
config.hide_on_modifier = (len == 4 && !strncmp((const char*)val, "hide", strlen("hide")));
config.hide_on_modifier = (len == 4 && !strncmp((const char*)val, "dock", strlen("dock")) ? M_DOCK
: (len == 4 && !strncmp((const char*)val, "hide", strlen("hide")) ? M_HIDE
: M_INVISIBLE));
return 1;
}
if (!strcmp(cur_key, "hidden_state")) {
DLOG("hidden_state = %.*s, len = %d\n", len, val, len);
config.hidden_state = (len == 4 && !strncmp((const char*)val, "hide", strlen("hide")) ? S_HIDE : S_SHOW);
return 1;
}

View File

@ -64,7 +64,7 @@ void got_output_reply(char *reply) {
parse_outputs_json(reply);
DLOG("Reconfiguring Windows...\n");
realloc_sl_buffer();
reconfig_windows();
reconfig_windows(false);
i3_output *o_walk;
SLIST_FOREACH(o_walk, outputs, slist) {
@ -149,12 +149,37 @@ void got_mode_event(char *event) {
draw_bars(false);
}
/*
* Called, when a barconfig_update event arrives (i.e. i3 changed the bar hidden_state or mode)
*
*/
void got_bar_config_update(char *event) {
/* check whether this affect this bar instance by checking the bar_id */
char *expected_id;
sasprintf(&expected_id, "\"id\":\"%s\"", config.bar_id);
char *found_id = strstr(event, expected_id);
FREE(expected_id);
if (found_id == NULL)
return;
/* Data-structure to easily call the reply-handlers later */
/* update the configuration with the received settings */
DLOG("Received bar config update \"%s\"\n", event);
int old_mode = config.hide_on_modifier;
parse_config_json(event);
if (old_mode != config.hide_on_modifier) {
reconfig_windows(true);
}
draw_bars(false);
}
/* Data-structure to easily call the event-handlers later */
handler_t event_handlers[] = {
&got_workspace_event,
&got_output_event,
&got_mode_event
&got_mode_event,
NULL,
&got_bar_config_update,
};
/*
@ -310,8 +335,8 @@ void destroy_connection(void) {
*/
void subscribe_events(void) {
if (config.disable_ws) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\" ]");
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\", \"barconfig_update\" ]");
} else {
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\", \"mode\" ]");
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\", \"mode\", \"barconfig_update\" ]");
}
}

View File

@ -53,12 +53,12 @@ char *expand_path(char *path) {
}
void print_usage(char *elf_name) {
printf("Usage: %s [-b bar_id] [-s sock_path] [-h] [-v]\n", elf_name);
printf("Usage: %s -b bar_id [-s sock_path] [-h] [-v]\n", elf_name);
printf("\n");
printf("--bar_id <bar_id>\tBar ID for which to get the configuration\n");
printf("-s <sock_path>\tConnect to i3 via <sock_path>\n");
printf("-h\t\tDisplay this help-message and exit\n");
printf("-v\t\tDisplay version number and exit\n");
printf("-b, --bar_id <bar_id>\tBar ID for which to get the configuration\n");
printf("-s, --socket <sock_path>\tConnect to i3 via <sock_path>\n");
printf("-h, --help Display this help-message and exit\n");
printf("-v, --version Display version number and exit\n");
printf("\n");
printf(" PLEASE NOTE that i3bar will be automatically started by i3\n"
" as soon as there is a 'bar' configuration block in your\n"
@ -97,13 +97,13 @@ int main(int argc, char **argv) {
static struct option long_opt[] = {
{ "socket", required_argument, 0, 's' },
{ "bar_id", required_argument, 0, 0 },
{ "bar_id", required_argument, 0, 'b' },
{ "help", no_argument, 0, 'h' },
{ "version", no_argument, 0, 'v' },
{ NULL, 0, 0, 0}
};
while ((opt = getopt_long(argc, argv, "s:hv", long_opt, &option_index)) != -1) {
while ((opt = getopt_long(argc, argv, "b:s:hv", long_opt, &option_index)) != -1) {
switch (opt) {
case 's':
socket_path = expand_path(optarg);
@ -112,11 +112,8 @@ int main(int argc, char **argv) {
printf("i3bar version " I3_VERSION " © 2010-2011 Axel Wagner and contributors\n");
exit(EXIT_SUCCESS);
break;
case 0:
if (!strcmp(long_opt[option_index].name, "bar_id")) {
FREE(config.bar_id);
config.bar_id = sstrdup(optarg);
}
case 'b':
config.bar_id = sstrdup(optarg);
break;
default:
print_usage(argv[0]);

View File

@ -31,6 +31,7 @@ static enum {
KEY_VERSION,
KEY_STOP_SIGNAL,
KEY_CONT_SIGNAL,
KEY_CLICK_EVENTS,
NO_KEY
} current_key;
@ -54,6 +55,21 @@ static int header_integer(void *ctx, long val) {
default:
break;
}
return 1;
}
static int header_boolean(void *ctx, int val) {
i3bar_child *child = ctx;
switch (current_key) {
case KEY_CLICK_EVENTS:
child->click_events = val;
break;
default:
break;
}
return 1;
}
@ -71,13 +87,15 @@ static int header_map_key(void *ctx, const unsigned char *stringval, unsigned in
current_key = KEY_STOP_SIGNAL;
} else if (CHECK_KEY("cont_signal")) {
current_key = KEY_CONT_SIGNAL;
} else if (CHECK_KEY("click_events")) {
current_key = KEY_CLICK_EVENTS;
}
return 1;
}
static yajl_callbacks version_callbacks = {
NULL, /* null */
NULL, /* boolean */
&header_boolean, /* boolean */
&header_integer,
NULL, /* double */
NULL, /* number */

View File

@ -80,6 +80,9 @@ ev_io *xkb_io;
/* The name of current binding mode */
static mode binding;
/* Indicates whether a new binding mode was recently activated */
bool activated_mode = false;
/* The parsed colors */
struct xcb_colors_t {
uint32_t bar_fg;
@ -162,7 +165,7 @@ void refresh_statusline(void) {
realloc_sl_buffer();
/* Clear the statusline pixmap. */
xcb_rectangle_t rect = { 0, 0, root_screen->width_in_pixels, font.height };
xcb_rectangle_t rect = { 0, 0, root_screen->width_in_pixels, font.height + 2 };
xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect);
/* Draw the text of each block. */
@ -195,7 +198,7 @@ void refresh_statusline(void) {
*
*/
void hide_bars(void) {
if (!config.hide_on_modifier) {
if ((config.hide_on_modifier == M_DOCK) || (config.hidden_state == S_SHOW && config.hide_on_modifier == M_HIDE)) {
return;
}
@ -214,7 +217,7 @@ void hide_bars(void) {
*
*/
void unhide_bars(void) {
if (!config.hide_on_modifier) {
if (config.hide_on_modifier != M_HIDE) {
return;
}
@ -320,24 +323,11 @@ void handle_button(xcb_button_press_event_t *event) {
}
int32_t x = event->event_x >= 0 ? event->event_x : 0;
int32_t original_x = x;
DLOG("Got Button %d\n", event->detail);
switch (event->detail) {
case 1:
/* Left Mousbutton. We determine, which button was clicked
* and set cur_ws accordingly */
TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) {
DLOG("x = %d\n", x);
if (x >= 0 && x < cur_ws->name_width + 10) {
break;
}
x -= cur_ws->name_width + 11;
}
if (cur_ws == NULL) {
return;
}
break;
case 4:
/* Mouse wheel up. We select the previous ws, if any.
* If there is no more workspace, dont even send the workspace
@ -358,6 +348,52 @@ void handle_button(xcb_button_press_event_t *event) {
cur_ws = TAILQ_NEXT(cur_ws, tailq);
break;
default:
/* Check if this event regards a workspace button */
TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) {
DLOG("x = %d\n", x);
if (x >= 0 && x < cur_ws->name_width + 10) {
break;
}
x -= cur_ws->name_width + 11;
}
if (cur_ws == NULL) {
/* No workspace button was pressed.
* Check if a status block has been clicked.
* This of course only has an effect,
* if the child reported bidirectional protocol usage. */
/* First calculate width of tray area */
trayclient *trayclient;
int tray_width = 0;
TAILQ_FOREACH_REVERSE(trayclient, walk->trayclients, tc_head, tailq) {
if (!trayclient->mapped)
continue;
tray_width += (font.height + 2);
}
int block_x = 0, last_block_x;
int offset = (walk->rect.w - (statusline_width + tray_width)) - 10;
x = original_x - offset;
if (x < 0)
return;
struct status_block *block;
TAILQ_FOREACH(block, &statusline_head, blocks) {
last_block_x = block_x;
block_x += block->width + block->x_offset + block->x_append;
if (x <= block_x && x >= last_block_x) {
send_block_clicked(event->detail, block->name, block->instance, event->root_x, event->root_y);
return;
}
}
return;
}
if (event->detail != 1)
return;
}
/* To properly handle workspace names with double quotes in them, we need
@ -812,7 +848,7 @@ void xkb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
modstate = mods & config.modifier;
}
#define DLOGMOD(modmask, status, barfunc) \
#define DLOGMOD(modmask, status) \
do { \
switch (modmask) { \
case ShiftMask: \
@ -837,14 +873,17 @@ void xkb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
DLOG("Mod5Mask got " #status "!\n"); \
break; \
} \
barfunc(); \
} while (0)
if (modstate != mod_pressed) {
if (modstate == 0) {
DLOGMOD(config.modifier, released, hide_bars);
DLOGMOD(config.modifier, released);
if (!activated_mode)
hide_bars();
} else {
DLOGMOD(config.modifier, pressed, unhide_bars);
DLOGMOD(config.modifier, pressed);
activated_mode = false;
unhide_bars();
}
mod_pressed = modstate;
}
@ -949,25 +988,13 @@ char *init_xcb_early() {
}
/*
* Initialization which depends on 'config' being usable. Called after the
* configuration has arrived.
* Register for xkb keyevents. To grab modifiers without blocking other applications from receiving key-events
* involving that modifier, we sadly have to use xkb which is not yet fully supported
* in xcb.
*
*/
void init_xcb_late(char *fontname) {
if (fontname == NULL)
fontname = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
/* Load the font */
font = load_font(fontname, true);
set_font(&font);
DLOG("Calculated Font-height: %d\n", font.height);
xcb_flush(xcb_connection);
/* To grab modifiers without blocking other applications from receiving key-events
* involving that modifier, we sadly have to use xkb which is not yet fully supported
* in xcb */
if (config.hide_on_modifier) {
void register_xkb_keyevents() {
if (xkb_dpy == NULL) {
int xkb_major, xkb_minor, xkb_errbase, xkb_err;
xkb_major = XkbMajorVersion;
xkb_minor = XkbMinorVersion;
@ -1007,6 +1034,40 @@ void init_xcb_late(char *fontname) {
}
}
/*
* Deregister from xkb keyevents.
*
*/
void deregister_xkb_keyevents() {
if (xkb_dpy != NULL) {
ev_io_stop (main_loop, xkb_io);
XCloseDisplay(xkb_dpy);
close(xkb_io->fd);
FREE(xkb_io);
xkb_dpy = NULL;
}
}
/*
* Initialization which depends on 'config' being usable. Called after the
* configuration has arrived.
*
*/
void init_xcb_late(char *fontname) {
if (fontname == NULL)
fontname = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
/* Load the font */
font = load_font(fontname, true);
set_font(&font);
DLOG("Calculated Font-height: %d\n", font.height);
xcb_flush(xcb_connection);
if (config.hide_on_modifier == M_HIDE)
register_xkb_keyevents();
}
/*
* Inform clients waiting for a new _NET_SYSTEM_TRAY that we took the
* selection.
@ -1307,7 +1368,7 @@ void realloc_sl_buffer(void) {
* Reconfigure all bars and create new bars for recently activated outputs
*
*/
void reconfig_windows(void) {
void reconfig_windows(bool redraw_bars) {
uint32_t mask;
uint32_t values[5];
static bool tray_configured = false;
@ -1329,8 +1390,8 @@ void reconfig_windows(void) {
mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
/* Black background */
values[0] = colors.bar_bg;
/* If hide_on_modifier is set, i3 is not supposed to manage our bar-windows */
values[1] = config.hide_on_modifier;
/* If hide_on_modifier is set to hide or invisible mode, i3 is not supposed to manage our bar-windows */
values[1] = (config.hide_on_modifier == M_DOCK ? 0 : 1);
/* We enable the following EventMask fields:
* EXPOSURE, to get expose events (we have to re-draw then)
* SUBSTRUCTURE_REDIRECT, to get ConfigureRequests when the tray
@ -1451,7 +1512,7 @@ void reconfig_windows(void) {
/* We finally map the bar (display it on screen), unless the modifier-switch is on */
xcb_void_cookie_t map_cookie;
if (!config.hide_on_modifier) {
if (config.hide_on_modifier == M_DOCK) {
map_cookie = xcb_map_window_checked(xcb_connection, walk->bar);
}
@ -1462,7 +1523,7 @@ void reconfig_windows(void) {
xcb_request_failed(name_cookie, "Could not set WM_NAME") ||
xcb_request_failed(strut_cookie, "Could not set strut") ||
xcb_request_failed(gc_cookie, "Could not create graphical context") ||
(!config.hide_on_modifier && xcb_request_failed(map_cookie, "Could not map window"))) {
((config.hide_on_modifier == M_DOCK) && xcb_request_failed(map_cookie, "Could not map window"))) {
exit(EXIT_FAILURE);
}
@ -1494,6 +1555,14 @@ void reconfig_windows(void) {
mask,
values);
mask = XCB_CW_OVERRIDE_REDIRECT;
values[0] = (config.hide_on_modifier == M_DOCK ? 0 : 1);
DLOG("Changing Window attribute override_redirect for output %s to %d\n", walk->name, values[0]);
xcb_void_cookie_t chg_cookie = xcb_change_window_attributes(xcb_connection,
walk->bar,
mask,
values);
DLOG("Recreating buffer for output %s\n", walk->name);
xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
root_screen->root_depth,
@ -1502,10 +1571,31 @@ void reconfig_windows(void) {
walk->rect.w,
walk->rect.h);
if (xcb_request_failed(cfg_cookie, "Could not reconfigure window")) {
exit(EXIT_FAILURE);
xcb_void_cookie_t map_cookie, umap_cookie;
if (redraw_bars) {
/* Unmap the window, and draw it again when in dock mode */
umap_cookie = xcb_unmap_window_checked(xcb_connection, walk->bar);
if (config.hide_on_modifier == M_DOCK) {
cont_child();
map_cookie = xcb_map_window_checked(xcb_connection, walk->bar);
} else {
stop_child();
}
if (config.hide_on_modifier == M_HIDE) {
/* Switching to hide mode, register for keyevents */
register_xkb_keyevents();
} else {
/* Switching to dock/invisible mode, deregister from keyevents */
deregister_xkb_keyevents();
}
}
if (xcb_request_failed(pm_cookie, "Could not create pixmap")) {
if (xcb_request_failed(cfg_cookie, "Could not reconfigure window") ||
xcb_request_failed(chg_cookie, "Could not change window") ||
xcb_request_failed(pm_cookie, "Could not create pixmap") ||
(redraw_bars && (xcb_request_failed(umap_cookie, "Could not unmap window") ||
(config.hide_on_modifier == M_DOCK && xcb_request_failed(map_cookie, "Could not map window"))))) {
exit(EXIT_FAILURE);
}
}
@ -1518,7 +1608,7 @@ void reconfig_windows(void) {
*/
void draw_bars(bool unhide) {
DLOG("Drawing Bars...\n");
int i = 1;
int i = 0;
refresh_statusline();
@ -1533,7 +1623,7 @@ void draw_bars(bool unhide) {
}
if (outputs_walk->bar == XCB_NONE) {
/* Oh shit, an active output without an own bar. Create it now! */
reconfig_windows();
reconfig_windows(false);
}
/* First things first: clear the backbuffer */
uint32_t color = colors.bar_bg;
@ -1573,7 +1663,7 @@ void draw_bars(bool unhide) {
outputs_walk->bargc,
MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0,
MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3,
MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font.height);
MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font.height + 2);
}
if (config.disable_ws) {
@ -1672,19 +1762,22 @@ void draw_bars(bool unhide) {
set_font_colors(outputs_walk->bargc, fg_color, bg_color);
draw_text(binding.name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 3, binding.width);
unhide = true;
}
i = 0;
}
if (!mod_pressed) {
if (unhide) {
/* The urgent-hint should get noticed, so we unhide the bars shortly */
unhide_bars();
} else if (walks_away) {
FREE(last_urgent_ws);
hide_bars();
}
/* Assure the bar is hidden/unhidden according to the specified hidden_state and mode */
bool should_unhide = (config.hidden_state == S_SHOW || (unhide && config.hidden_state == S_HIDE));
bool should_hide = (config.hide_on_modifier == M_INVISIBLE);
if (mod_pressed || (should_unhide && !should_hide)) {
unhide_bars();
} else if (!mod_pressed && (walks_away || should_hide)) {
FREE(last_urgent_ws);
hide_bars();
}
redraw_bars();
@ -1719,5 +1812,6 @@ void redraw_bars(void) {
void set_current_mode(struct mode *current) {
I3STRING_FREE(binding.name);
binding = *current;
activated_mode = binding.name != NULL;
return;
}

View File

@ -15,8 +15,8 @@ xmacro(_NET_WM_STRUT_PARTIAL)
xmacro(_NET_CLIENT_LIST_STACKING)
xmacro(_NET_CURRENT_DESKTOP)
xmacro(_NET_ACTIVE_WINDOW)
xmacro(_NET_WORKAREA)
xmacro(_NET_STARTUP_ID)
xmacro(_NET_WORKAREA)
xmacro(WM_PROTOCOLS)
xmacro(WM_DELETE_WINDOW)
xmacro(UTF8_STRING)
@ -29,3 +29,5 @@ xmacro(I3_CONFIG_PATH)
xmacro(I3_SYNC)
xmacro(I3_SHMLOG_PATH)
xmacro(I3_PID)
xmacro(_NET_REQUEST_FRAME_EXTENTS)
xmacro(_NET_FRAME_EXTENTS)

View File

@ -115,6 +115,12 @@ void cmd_workspace_name(I3_CMD, char *name);
*/
void cmd_mark(I3_CMD, char *mark);
/**
* Implementation of 'unmark [mark]'
*
*/
void cmd_unmark(I3_CMD, char *mark);
/**
* Implementation of 'mode <string>'.
*
@ -265,4 +271,22 @@ void cmd_scratchpad_show(I3_CMD);
*/
void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name);
/**
* Implementation of 'bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) [<bar_id>]'
*
*/
void cmd_bar(I3_CMD, char *bar_type, char *bar_value, char *bar_id);
/*
* Implementation of 'shmlog <size>|toggle|on|off'
*
*/
void cmd_shmlog(I3_CMD, char *argument);
/*
* Implementation of 'debuglog toggle|on|off'
*
*/
void cmd_debuglog(I3_CMD, char *argument);
#endif

View File

@ -25,13 +25,13 @@ struct CommandResult {
/* The JSON generator to append a reply to. */
yajl_gen json_gen;
/* Whether the command requires calling tree_render. */
bool needs_tree_render;
/* The next state to transition to. Passed to the function so that we can
* determine the next state as a result of a function call, like
* cfg_criteria_pop_state() does. */
int next_state;
/* Whether the command requires calling tree_render. */
bool needs_tree_render;
};
struct CommandResult *parse_command(const char *input);

View File

@ -14,8 +14,13 @@
/**
* Create a new container (and attach it to the given parent, if not NULL).
* This function initializes the data structures and creates the appropriate
* X11 IDs using x_con_init().
* This function only initializes the data structures.
*
*/
Con *con_new_skeleton(Con *parent, i3Window *window);
/* A wrapper for con_new_skeleton, to retain the old con_new behaviour
*
*/
Con *con_new(Con *parent, i3Window *window);
@ -270,7 +275,7 @@ void con_set_border_style(Con *con, int border_style, int border_width);
* new split container before).
*
*/
void con_set_layout(Con *con, int layout);
void con_set_layout(Con *con, layout_t layout);
/**
* This function toggles the layout of a given container. toggle_mode can be

View File

@ -95,7 +95,7 @@ struct Config {
char *ipc_socket_path;
const char *restart_state_path;
int default_layout;
layout_t default_layout;
int container_stack_limit;
int container_stack_limit_value;
int default_border_width;
@ -199,6 +199,9 @@ struct Config {
/* just ignore the popup, that is, dont map it */
PDF_IGNORE = 2,
} popup_during_fullscreen;
/* The number of currently parsed barconfigs */
int number_barconfigs;
};
/**
@ -226,8 +229,11 @@ struct Barconfig {
* root window! */
char *socket_path;
/** Bar display mode (hide unless modifier is pressed or show in dock mode) */
enum { M_DOCK = 0, M_HIDE = 1 } 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;
/* The current hidden_state of the bar, which indicates whether it is hidden or shown */
enum { S_HIDE = 0, S_SHOW = 1 } hidden_state;
/** Bar modifier (to show bar when in hide mode). */
enum {
@ -323,6 +329,12 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch);
*/
void switch_mode(const char *new_mode);
/**
* Sends the current bar configuration as an event to all barconfig_update listeners.
* This update mechnism currently only includes the hidden_state and the mode in the config.
*
*/void update_barconfig();
/**
* Returns a pointer to the Binding with the specified modifiers and keycode
* or NULL if no such binding exists.

View File

@ -62,6 +62,8 @@ CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *ke
CFGFUN(bar_font, const char *font);
CFGFUN(bar_mode, const char *mode);
CFGFUN(bar_hidden_state, const char *hidden_state);
CFGFUN(bar_id, const char *bar_id);
CFGFUN(bar_output, const char *output);
CFGFUN(bar_verbose, const char *verbose);
CFGFUN(bar_modifier, const char *modifier);

View File

@ -79,6 +79,19 @@ enum {
BIND_MODE_SWITCH = (1 << 8)
};
/**
* Container layouts. See Con::layout.
*/
typedef enum {
L_DEFAULT = 0,
L_STACKED = 1,
L_TABBED = 2,
L_DOCKAREA = 3,
L_OUTPUT = 4,
L_SPLITV = 5,
L_SPLITH = 6
} layout_t;
/**
* Stores a rectangle, for example the size of a window, the child window etc.
* It needs to be packed so that the compiler will not add any padding bytes.
@ -133,8 +146,8 @@ struct deco_render_params {
struct width_height con_window_rect;
Rect con_deco_rect;
uint32_t background;
layout_t parent_layout;
bool con_is_leaf;
orientation_t parent_orientation;
};
/**
@ -215,6 +228,14 @@ struct Binding {
B_UPON_KEYRELEASE_IGNORE_MODS = 2,
} release;
uint32_t number_keycodes;
/** Keycode to bind */
uint32_t keycode;
/** Bitmask consisting of BIND_MOD_1, BIND_MODE_SWITCH, … */
uint32_t mods;
/** Symbol the user specified in configfile, if any. This needs to be
* stored with the binding to be able to re-convert it into a keycode
* if the keyboard mapping changes (using Xmodmap for example) */
@ -227,13 +248,6 @@ struct Binding {
* This is an array of number_keycodes size. */
xcb_keycode_t *translated_to;
uint32_t number_keycodes;
/** Keycode to bind */
uint32_t keycode;
/** Bitmask consisting of BIND_MOD_1, BIND_MODE_SWITCH, … */
uint32_t mods;
/** Command, like in command mode */
char *command;
@ -268,11 +282,6 @@ struct Autostart {
struct xoutput {
/** Output id, so that we can requery the output directly later */
xcb_randr_output_t id;
/** Name of the output */
char *name;
/** Pointer to the Con which represents this output */
Con *con;
/** Whether the output is currently active (has a CRTC attached with a
* valid mode) */
@ -284,6 +293,12 @@ struct xoutput {
bool to_be_disabled;
bool primary;
/** Name of the output */
char *name;
/** Pointer to the Con which represents this output */
Con *con;
/** x, y, width, height */
Rect rect;
@ -303,6 +318,11 @@ struct Window {
xcb_window_t leader;
xcb_window_t transient_for;
/** Pointers to the Assignments which were already ran for this Window
* (assignments run only once) */
uint32_t nr_assignments;
Assignment **ran_assignments;
char *class_class;
char *class_instance;
@ -323,9 +343,6 @@ struct Window {
/** Whether the application needs to receive WM_TAKE_FOCUS */
bool needs_take_focus;
/** When this window was marked urgent. 0 means not urgent */
struct timeval urgent;
/** Whether this window accepts focus. We store this inverted so that the
* default will be 'accepts focus'. */
bool doesnt_accept_focus;
@ -333,14 +350,12 @@ struct Window {
/** Whether the window says it is a dock window */
enum { W_NODOCK = 0, W_DOCK_TOP = 1, W_DOCK_BOTTOM = 2 } dock;
/** When this window was marked urgent. 0 means not urgent */
struct timeval urgent;
/** Pixels the window reserves. left/right/top/bottom */
struct reservedpx reserved;
/** Pointers to the Assignments which were already ran for this Window
* (assignments run only once) */
uint32_t nr_assignments;
Assignment **ran_assignments;
/** Depth of the window */
uint16_t depth;
};
@ -373,8 +388,8 @@ struct Match {
M_DOCK_BOTTOM = 3
} dock;
xcb_window_t id;
Con *con_id;
enum { M_ANY = 0, M_TILING, M_FLOATING } floating;
Con *con_id;
/* Where the window looking for a match should be inserted:
*
@ -387,12 +402,12 @@ struct Match {
*/
enum { M_HERE = 0, M_ASSIGN_WS, M_BELOW } insert_where;
TAILQ_ENTRY(Match) matches;
/* Whether this match was generated when restarting i3 inplace.
* Leads to not setting focus when managing a new window, because the old
* focus stack should be restored. */
bool restart_mode;
TAILQ_ENTRY(Match) matches;
};
/**
@ -441,6 +456,24 @@ struct Assignment {
*/
struct Con {
bool mapped;
/* Should this container be marked urgent? This gets set when the window
* inside this container (if any) sets the urgency hint, for example. */
bool urgent;
/** This counter contains the number of UnmapNotify events for this
* container (or, more precisely, for its ->frame) which should be ignored.
* UnmapNotify events need to be ignored when they are caused by i3 itself,
* for example when reparenting or when unmapping the window on a workspace
* change. */
uint8_t ignore_unmap;
/* ids/pixmap/graphics context for the frame window */
bool pixmap_recreated;
xcb_window_t frame;
xcb_pixmap_t pixmap;
xcb_gcontext_t pm_gc;
enum {
CT_ROOT = 0,
CT_OUTPUT = 1,
@ -449,6 +482,11 @@ struct Con {
CT_WORKSPACE = 4,
CT_DOCKAREA = 5
} type;
/** the workspace number, if this Con is of type CT_WORKSPACE and the
* workspace is not a named workspace (for named workspaces, num == -1) */
int num;
struct Con *parent;
struct Rect rect;
@ -459,10 +497,6 @@ struct Con {
char *name;
/** the workspace number, if this Con is of type CT_WORKSPACE and the
* workspace is not a named workspace (for named workspaces, num == -1) */
int num;
/* a sticky-group is an identifier which bundles several containers to a
* group. The contents are shared between all of them, that is they are
* displayed on whichever of the containers is currently visible */
@ -473,10 +507,8 @@ struct Con {
double percent;
/* proportional width/height, calculated from WM_NORMAL_HINTS, used to
* apply an aspect ratio to windows (think of MPlayer) */
int proportional_width;
int proportional_height;
/* aspect ratio from WM_NORMAL_HINTS (MPlayer uses this for example) */
double aspect_ratio;
/* the wanted size of the window, used in combination with size
* increments (see below). */
int base_width;
@ -492,19 +524,9 @@ struct Con {
struct Window *window;
/* Should this container be marked urgent? This gets set when the window
* inside this container (if any) sets the urgency hint, for example. */
bool urgent;
/* timer used for disabling urgency */
struct ev_timer *urgency_timer;
/* ids/pixmap/graphics context for the frame window */
xcb_window_t frame;
xcb_pixmap_t pixmap;
xcb_gcontext_t pm_gc;
bool pixmap_recreated;
/** Cache for the decoration rendering */
struct deco_render_params *deco_render_params;
@ -531,15 +553,7 @@ struct Con {
* parent and opening new containers). Instead, it stores the requested
* layout in workspace_layout and creates a new split container with that
* layout whenever a new container is attached to the workspace. */
enum {
L_DEFAULT = 0,
L_STACKED = 1,
L_TABBED = 2,
L_DOCKAREA = 3,
L_OUTPUT = 4,
L_SPLITV = 5,
L_SPLITH = 6
} layout, last_split_layout, workspace_layout;
layout_t layout, last_split_layout, workspace_layout;
border_style_t border_style;
/** floating? (= not in tiling layout) This cannot be simply a bool
* because we want to keep track of whether the status was set by the
@ -554,13 +568,6 @@ struct Con {
FLOATING_USER_ON = 3
} floating;
/** This counter contains the number of UnmapNotify events for this
* container (or, more precisely, for its ->frame) which should be ignored.
* UnmapNotify events need to be ignored when they are caused by i3 itself,
* for example when reparenting or when unmapping the window on a workspace
* change. */
uint8_t ignore_unmap;
TAILQ_ENTRY(Con) nodes;
TAILQ_ENTRY(Con) focused;
TAILQ_ENTRY(Con) all_cons;
@ -584,6 +591,9 @@ struct Con {
/* The ID of this container before restarting. Necessary to correctly
* interpret back-references in the JSON (such as the focus stack). */
int old_id;
/* Depth of the container window */
uint16_t depth;
};
#endif

View File

@ -46,4 +46,21 @@ void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows);
*/
void ewmh_setup_hints(void);
/**
* i3 currently does not support _NET_WORKAREA, because it does not correspond
* to i3s concept of workspaces. See also:
* http://bugs.i3wm.org/539
* http://bugs.i3wm.org/301
* http://bugs.i3wm.org/1038
*
* We need to actively delete this property because some display managers (e.g.
* LightDM) set it.
*
* EWMH: Contains a geometry for each desktop. These geometries specify an area
* that is completely contained within the viewport. Work area SHOULD be used by
* desktop applications to place desktop icons appropriately.
*
*/
void ewmh_update_workarea(void);
#endif

View File

@ -99,4 +99,7 @@ typedef struct i3_ipc_header {
/* The window event will be triggered upon window changes */
#define I3_IPC_EVENT_WINDOW (I3_IPC_EVENT_MASK | 3)
/** Bar config update will be triggered to update the bar config */
#define I3_IPC_EVENT_BARCONFIG_UPDATE (I3_IPC_EVENT_MASK | 4)
#endif

View File

@ -364,4 +364,12 @@ bool is_debug_build() __attribute__((const));
*/
char *get_process_filename(const char *prefix);
/**
* This function returns the absolute path to the executable it is running in.
*
* The implementation follows http://stackoverflow.com/a/933996/712014
*
*/
const char *get_exe_path(const char *argv0);
#endif

View File

@ -38,6 +38,24 @@ extern int shmlog_size;
*/
void init_logging(void);
/**
* Opens the logbuffer.
*
*/
void open_logbuffer(void);
/**
* Closes the logbuffer.
*
*/
void close_logbuffer(void);
/**
* Checks if debug logging is active.
*
*/
bool get_debug_logging(void);
/**
* Set debug logging.
*

View File

@ -87,6 +87,16 @@ Output *get_output_by_name(const char *name);
*/
Output *get_output_containing(int x, int y);
/*
* In contained_by_output, we check if any active output contains part of the container.
* We do this by checking if the output rect is intersected by the Rect.
* This is the 2-dimensional counterpart of get_output_containing.
* Since we don't actually need the outputs intersected by the given Rect (There could
* be many), we just return true or false for convenience.
*
*/
bool contained_by_output(Rect rect);
/**
* Gets the output which is the next one in the given direction.
*

View File

@ -14,6 +14,9 @@
#include <stdint.h>
#include <pthread.h>
/* Default shmlog size if not set by user. */
extern const int default_shmlog_size;
/*
* Header of the shmlog file. Used by i3/src/log.c and i3/i3-dump-log/main.c.
*

View File

@ -93,8 +93,12 @@ void x_push_changes(Con *con);
* Raises the specified container in the internal stack of X windows. The
* next call to x_push_changes() will make the change visible in X11.
*
* If above_all is true, the X11 window will be raised to the top
* of the stack. This should only be used for precisely one fullscreen
* window per output.
*
*/
void x_raise_con(Con *con);
void x_raise_con(Con *con, bool above_all);
/**
* Sets the WM_NAME property (so, no UTF8, but used only for debugging anyways)
@ -104,6 +108,12 @@ void x_raise_con(Con *con);
*/
void x_set_name(Con *con, const char *name);
/**
* Set up the SHMLOG_PATH atom.
*
*/
void update_shmlog_atom(void);
/**
* Sets up i3 specific atoms (I3_SOCKET_PATH and I3_CONFIG_PATH)
*

74
libi3/get_exe_path.c Normal file
View File

@ -0,0 +1,74 @@
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include "libi3.h"
/*
* This function returns the absolute path to the executable it is running in.
*
* The implementation follows http://stackoverflow.com/a/933996/712014
*
*/
const char *get_exe_path(const char *argv0) {
static char destpath[PATH_MAX];
char tmp[PATH_MAX];
#if defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
/* Linux and Debian/kFreeBSD provide /proc/self/exe */
#if defined(__linux__) || defined(__FreeBSD_kernel__)
const char *exepath = "/proc/self/exe";
#elif defined(__FreeBSD__)
const char *exepath = "/proc/curproc/file";
#endif
ssize_t linksize;
if ((linksize = readlink(exepath, destpath, sizeof(destpath) - 1)) != -1) {
/* readlink() does not NULL-terminate strings, so we have to. */
destpath[linksize] = '\0';
return destpath;
}
#endif
/* argv[0] is most likely a full path if it starts with a slash. */
if (argv0[0] == '/')
return argv0;
/* if argv[0] contains a /, prepend the working directory */
if (strchr(argv0, '/') != NULL &&
getcwd(tmp, sizeof(tmp)) != NULL) {
snprintf(destpath, sizeof(destpath), "%s/%s", tmp, argv0);
return destpath;
}
/* Fall back to searching $PATH (or _CS_PATH in absence of $PATH). */
char *path = getenv("PATH");
if (path == NULL) {
/* _CS_PATH is typically something like "/bin:/usr/bin" */
confstr(_CS_PATH, tmp, sizeof(tmp));
sasprintf(&path, ":%s", tmp);
} else {
path = strdup(path);
}
const char *component;
char *str = path;
while (1) {
if ((component = strtok(str, ":")) == NULL)
break;
str = NULL;
snprintf(destpath, sizeof(destpath), "%s/%s", component, argv0);
/* Of course this is not 100% equivalent to actually exec()ing the
* binary, but meh. */
if (access(destpath, X_OK) == 0) {
free(path);
return destpath;
}
}
free(path);
/* Last resort: maybe its in /usr/bin? */
return "/usr/bin/i3-nagbar";
}

View File

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

View File

@ -9,7 +9,30 @@ i3-msg - send messages to i3 window manager
== SYNOPSIS
i3-msg [-t type] [message]
i3-msg [-q] [-v] [-h] [-s socket] [-t type] [message]
== OPTIONS
*-q, --quiet*::
Only send ipc message and suppress the output of the response.
*-v, --version*::
Display version number and exit.
*-h, --help*::
Display a short help-message and exit.
*-s, --socket* 'sock_path'::
i3-msg will use the environment variable I3SOCK or the socket path
given here. If both fail, it will try to get the socket information
from the root window and then try /tmp/i3-ipc.sock before exiting
with an error.
*-t* 'type'::
Send ipc message, see below.
*message*::
Send ipc message, see below.
== IPC MESSAGE TYPES

View File

@ -19,6 +19,8 @@ state INITIAL:
'exit' -> call cmd_exit()
'restart' -> call cmd_restart()
'reload' -> call cmd_reload()
'shmlog' -> SHMLOG
'debuglog' -> DEBUGLOG
'border' -> BORDER
'layout' -> LAYOUT
'append_layout' -> APPEND_LAYOUT
@ -30,11 +32,13 @@ state INITIAL:
'split' -> SPLIT
'floating' -> FLOATING
'mark' -> MARK
'unmark' -> UNMARK
'resize' -> RESIZE
'rename' -> RENAME
'nop' -> NOP
'scratchpad' -> SCRATCHPAD
'mode' -> MODE
'bar' -> BAR
state CRITERIA:
ctype = 'class' -> CRITERION
@ -61,6 +65,17 @@ state EXEC:
command = string
-> call cmd_exec($nosn, $command)
# shmlog <size>|toggle|on|off
state SHMLOG:
# argument may be a number
argument = string
-> call cmd_shmlog($argument)
# debuglog toggle|on|off
state DEBUGLOG:
argument = 'toggle', 'on', 'off'
-> call cmd_debuglog($argument)
# border normal|none|1pixel|toggle|1pixel
state BORDER:
border_style = 'normal', 'pixel'
@ -163,6 +178,13 @@ state MARK:
mark = string
-> call cmd_mark($mark)
# unmark [mark]
state UNMARK:
end
-> call cmd_unmark($mark)
mark = string
-> call cmd_unmark($mark)
# resize
state RESIZE:
way = 'grow', 'shrink'
@ -319,3 +341,24 @@ state NOP:
state SCRATCHPAD:
'show'
-> call cmd_scratchpad_show()
# bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) [<bar_id>]
state BAR:
bar_type = 'hidden_state'
-> BAR_HIDDEN_STATE
bar_type = 'mode'
-> BAR_MODE
state BAR_HIDDEN_STATE:
bar_value = 'hide', 'show', 'toggle'
-> BAR_W_ID
state BAR_MODE:
bar_value = 'dock', 'hide', 'invisible', 'toggle'
-> BAR_W_ID
state BAR_W_ID:
bar_id = word
->
end
-> call cmd_bar($bar_type, $bar_value, $bar_id)

View File

@ -49,7 +49,7 @@ state INITIAL:
# We ignore comments and 'set' lines (variables).
state IGNORE_LINE:
end, string
line
-> INITIAL
# floating_minimum_size <width> x <height>
@ -311,7 +311,7 @@ state MODE:
# We ignore comments and 'set' lines (variables).
state MODE_IGNORE_LINE:
end, string
line
-> MODE
state MODE_BINDING:
@ -349,6 +349,8 @@ state BAR:
'status_command' -> BAR_STATUS_COMMAND
'socket_path' -> BAR_SOCKET_PATH
'mode' -> BAR_MODE
'hidden_state' -> BAR_HIDDEN_STATE
'id' -> BAR_ID
'modifier' -> BAR_MODIFIER
'position' -> BAR_POSITION
'output' -> BAR_OUTPUT
@ -362,7 +364,7 @@ state BAR:
# We ignore comments and 'set' lines (variables).
state BAR_IGNORE_LINE:
end, string
line
-> BAR
state BAR_BAR_COMMAND:
@ -378,9 +380,17 @@ state BAR_SOCKET_PATH:
-> call cfg_bar_socket_path($path); BAR
state BAR_MODE:
mode = 'dock', 'hide'
mode = 'dock', 'hide', 'invisible'
-> call cfg_bar_mode($mode); BAR
state BAR_HIDDEN_STATE:
hidden_state = 'hide', 'show'
-> call cfg_bar_hidden_state($hidden_state); BAR
state BAR_ID:
bar_id = word
-> call cfg_bar_id($bar_id); BAR
state BAR_MODIFIER:
modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift'
-> call cfg_bar_modifier($modifier); BAR
@ -428,7 +438,7 @@ state BAR_COLORS:
# We ignore comments and 'set' lines (variables).
state BAR_COLORS_IGNORE_LINE:
end, string
line
-> BAR_COLORS
state BAR_COLORS_SINGLE:

View File

@ -13,6 +13,7 @@
#include <stdarg.h>
#include "all.h"
#include "shmlog.h"
// Macros to make the YAJL API a bit easier to use.
#define y(x, ...) yajl_gen_ ## x (cmd_output->json_gen, ##__VA_ARGS__)
@ -1030,6 +1031,31 @@ void cmd_mark(I3_CMD, char *mark) {
ysuccess(true);
}
/*
* Implementation of 'unmark [mark]'
*
*/
void cmd_unmark(I3_CMD, char *mark) {
if (mark == NULL) {
Con *con;
TAILQ_FOREACH(con, &all_cons, all_cons) {
FREE(con->mark);
}
DLOG("removed all window marks");
} else {
Con *con;
TAILQ_FOREACH(con, &all_cons, all_cons) {
if (con->mark && strcmp(con->mark, mark) == 0)
FREE(con->mark);
}
DLOG("removed window mark %s\n", mark);
}
cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply
ysuccess(true);
}
/*
* Implementation of 'mode <string>'.
*
@ -1547,7 +1573,7 @@ void cmd_layout(I3_CMD, char *layout_str) {
if (strcmp(layout_str, "stacking") == 0)
layout_str = "stacked";
owindow *current;
int layout;
layout_t layout;
/* default is a special case which will be handled in con_set_layout(). */
if (strcmp(layout_str, "default") == 0)
layout = L_DEFAULT;
@ -1632,6 +1658,8 @@ void cmd_reload(I3_CMD) {
x_set_i3_atoms();
/* Send an IPC event just in case the ws names have changed */
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}");
/* Send an update event for the barconfig just in case it has changed */
update_barconfig();
// XXX: default reply for now, make this a better reply
ysuccess(true);
@ -1915,3 +1943,164 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"rename\"}");
}
/*
* Implementation of 'bar mode dock|hide|invisible|toggle [<bar_id>]'
*
*/
bool cmd_bar_mode(char *bar_mode, char *bar_id) {
int mode;
bool toggle = false;
if (strcmp(bar_mode, "dock") == 0)
mode = M_DOCK;
else if (strcmp(bar_mode, "hide") == 0)
mode = M_HIDE;
else if (strcmp(bar_mode, "invisible") == 0)
mode = M_INVISIBLE;
else if (strcmp(bar_mode, "toggle") == 0)
toggle = true;
else {
ELOG("Unknown bar mode \"%s\", this is a mismatch between code and parser spec.\n", bar_mode);
return false;
}
bool changed_sth = false;
Barconfig *current = NULL;
TAILQ_FOREACH(current, &barconfigs, configs) {
if (bar_id && strcmp(current->id, bar_id) != 0)
continue;
if (toggle)
mode = (current->mode + 1) % 2;
DLOG("Changing bar mode of bar_id '%s' to '%s (%d)'\n", current->id, bar_mode, mode);
current->mode = mode;
changed_sth = true;
if (bar_id)
break;
}
if (bar_id && !changed_sth) {
DLOG("Changing bar mode of bar_id %s failed, bar_id not found.\n", bar_id);
return false;
}
return true;
}
/*
* Implementation of 'bar hidden_state hide|show|toggle [<bar_id>]'
*
*/
bool cmd_bar_hidden_state(char *bar_hidden_state, char *bar_id) {
int hidden_state;
bool toggle = false;
if (strcmp(bar_hidden_state, "hide") == 0)
hidden_state = S_HIDE;
else if (strcmp(bar_hidden_state, "show") == 0)
hidden_state = S_SHOW;
else if (strcmp(bar_hidden_state, "toggle") == 0)
toggle = true;
else {
ELOG("Unknown bar state \"%s\", this is a mismatch between code and parser spec.\n", bar_hidden_state);
return false;
}
bool changed_sth = false;
Barconfig *current = NULL;
TAILQ_FOREACH(current, &barconfigs, configs) {
if (bar_id && strcmp(current->id, bar_id) != 0)
continue;
if (toggle)
hidden_state = (current->hidden_state + 1) % 2;
DLOG("Changing bar hidden_state of bar_id '%s' to '%s (%d)'\n", current->id, bar_hidden_state, hidden_state);
current->hidden_state = hidden_state;
changed_sth = true;
if (bar_id)
break;
}
if (bar_id && !changed_sth) {
DLOG("Changing bar hidden_state of bar_id %s failed, bar_id not found.\n", bar_id);
return false;
}
return true;
}
/*
* Implementation of 'bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) [<bar_id>]'
*
*/
void cmd_bar(I3_CMD, char *bar_type, char *bar_value, char *bar_id) {
bool ret;
if (strcmp(bar_type, "mode") == 0)
ret = cmd_bar_mode(bar_value, bar_id);
else if (strcmp(bar_type, "hidden_state") == 0)
ret = cmd_bar_hidden_state(bar_value, bar_id);
else {
ELOG("Unknown bar option type \"%s\", this is a mismatch between code and parser spec.\n", bar_type);
ret = false;
}
ysuccess(ret);
if (!ret)
return;
update_barconfig();
}
/*
* Implementation of 'shmlog <size>|toggle|on|off'
*
*/
void cmd_shmlog(I3_CMD, char *argument) {
if (!strcmp(argument,"toggle"))
/* Toggle shm log, if size is not 0. If it is 0, set it to default. */
shmlog_size = shmlog_size ? -shmlog_size : default_shmlog_size;
else if (!strcmp(argument, "on"))
shmlog_size = default_shmlog_size;
else if (!strcmp(argument, "off"))
shmlog_size = 0;
else {
/* If shm logging now, restart logging with the new size. */
if (shmlog_size > 0) {
shmlog_size = 0;
LOG("Restarting shm logging...\n");
init_logging();
}
shmlog_size = atoi(argument);
/* Make a weakly attempt at ensuring the argument is valid. */
if (shmlog_size <= 0)
shmlog_size = default_shmlog_size;
}
LOG("%s shm logging\n", shmlog_size > 0 ? "Enabling" : "Disabling");
init_logging();
update_shmlog_atom();
// XXX: default reply for now, make this a better reply
ysuccess(true);
}
/*
* Implementation of 'debuglog toggle|on|off'
*
*/
void cmd_debuglog(I3_CMD, char *argument) {
bool logging = get_debug_logging();
if (!strcmp(argument,"toggle")) {
LOG("%s debug logging\n", logging ? "Disabling" : "Enabling");
set_debug_logging(!logging);
} else if (!strcmp(argument, "on") && !logging) {
LOG("Enabling debug logging\n");
set_debug_logging(true);
} else if (!strcmp(argument, "off") && logging) {
LOG("Disabling debug logging\n");
set_debug_logging(false);
}
// XXX: default reply for now, make this a better reply
ysuccess(true);
}

View File

@ -13,7 +13,7 @@
* We use a hand-written parser instead of lex/yacc because our commands are
* easy for humans, not for computers. Thus, its quite hard to specify a
* context-free grammar for the commands. A PEG grammar would be easier, but
* theres downsides to every PEG parser generator I have come accross so far.
* theres downsides to every PEG parser generator I have come across so far.
*
* This parser is basically a state machine which looks for literals or strings
* and can push either on a stack. After identifying a literal or string, it

View File

@ -44,18 +44,22 @@ static void con_force_split_parents_redraw(Con *con) {
/*
* Create a new container (and attach it to the given parent, if not NULL).
* This function initializes the data structures and creates the appropriate
* X11 IDs using x_con_init().
* This function only initializes the data structures.
*
*/
Con *con_new(Con *parent, i3Window *window) {
Con *con_new_skeleton(Con *parent, i3Window *window) {
Con *new = scalloc(sizeof(Con));
new->on_remove_child = con_on_remove_child;
TAILQ_INSERT_TAIL(&all_cons, new, all_cons);
new->aspect_ratio = 0.0;
new->type = CT_CON;
new->window = window;
new->border_style = config.default_border;
new->current_border_width = -1;
if (window)
new->depth = window->depth;
else
new->depth = XCB_COPY_FROM_PARENT;
static int cnt = 0;
DLOG("opening window %d\n", cnt);
@ -66,10 +70,6 @@ Con *con_new(Con *parent, i3Window *window) {
cnt++;
if ((cnt % (sizeof(colors) / sizeof(char*))) == 0)
cnt = 0;
if (window)
x_con_init(new, window->depth);
else
x_con_init(new, XCB_COPY_FROM_PARENT);
TAILQ_INIT(&(new->floating_head));
TAILQ_INIT(&(new->nodes_head));
@ -82,6 +82,15 @@ Con *con_new(Con *parent, i3Window *window) {
return new;
}
/* A wrapper for con_new_skeleton, to retain the old con_new behaviour
*
*/
Con *con_new(Con *parent, i3Window *window) {
Con *new = con_new_skeleton(parent, window);
x_con_init(new, new->depth);
return new;
}
/*
* Attaches the given container to the given parent. This happens when moving
* a container or when inserting a new container at a specific place in the
@ -1201,7 +1210,7 @@ void con_set_border_style(Con *con, int border_style, int border_width) {
* new split container before).
*
*/
void con_set_layout(Con *con, int layout) {
void con_set_layout(Con *con, layout_t layout) {
DLOG("con_set_layout(%p, %d), con->type = %d\n",
con, layout, con->type);
@ -1361,6 +1370,8 @@ static void con_on_remove_child(Con *con) {
}
con_force_split_parents_redraw(con);
con->urgent = con_has_urgent_child(con);
con_update_parents_urgency(con);
/* TODO: check if this container would swallow any other client and
* dont close it automatically. */

View File

@ -210,6 +210,49 @@ void switch_mode(const char *new_mode) {
ELOG("ERROR: Mode not found\n");
}
/*
* Sends the current bar configuration as an event to all barconfig_update listeners.
* This update mechnism currently only includes the hidden_state and the mode in the config.
*
*/
void update_barconfig() {
Barconfig *current;
TAILQ_FOREACH(current, &barconfigs, configs) {
/* Build json message */
char *hidden_state;
switch (current->hidden_state) {
case S_SHOW:
hidden_state ="show";
break;
case S_HIDE:
default:
hidden_state = "hide";
break;
}
char *mode;
switch (current->mode) {
case M_HIDE:
mode ="hide";
break;
case M_INVISIBLE:
mode ="invisible";
break;
case M_DOCK:
default:
mode = "dock";
break;
}
/* Send an event to all barconfig listeners*/
char *event_msg;
sasprintf(&event_msg, "{ \"id\":\"%s\", \"hidden_state\":\"%s\", \"mode\":\"%s\" }", current->id, hidden_state, mode);
ipc_send_event("barconfig_update", I3_IPC_EVENT_BARCONFIG_UPDATE, event_msg);
FREE(event_msg);
}
}
/*
* Get the path of the first configuration file found. If override_configpath
* is specified, that path is returned and saved for further calls. Otherwise,

View File

@ -452,7 +452,15 @@ CFGFUN(bar_font, const char *font) {
}
CFGFUN(bar_mode, const char *mode) {
current_bar.mode = (strcmp(mode, "hide") == 0 ? M_HIDE : M_DOCK);
current_bar.mode = (strcmp(mode, "dock") == 0 ? M_DOCK : (strcmp(mode, "hide") == 0 ? M_HIDE : M_INVISIBLE));
}
CFGFUN(bar_hidden_state, const char *hidden_state) {
current_bar.hidden_state = (strcmp(hidden_state, "hide") == 0 ? S_HIDE : S_SHOW);
}
CFGFUN(bar_id, const char *bar_id) {
current_bar.id = sstrdup(bar_id);
}
CFGFUN(bar_output, const char *output) {
@ -548,15 +556,11 @@ CFGFUN(bar_workspace_buttons, const char *value) {
CFGFUN(bar_finish) {
DLOG("\t new bar configuration finished, saving.\n");
/* Generate a unique ID for this bar */
current_bar.id = sstrdup("bar-XXXXXX");
/* This works similar to mktemp in that it replaces the last six X with
* random letters, but without the restriction that the given buffer
* has to contain a valid path name. */
char *x = current_bar.id + strlen("bar-");
while (*x != '\0') {
*(x++) = (rand() % 26) + 'a';
}
/* Generate a unique ID for this bar if not already configured */
if (!current_bar.id)
sasprintf(&current_bar.id, "bar-%d", config.number_barconfigs);
config.number_barconfigs++;
/* If no font was explicitly set, we use the i3 font as default */
if (!current_bar.font && font_pattern)

View File

@ -446,6 +446,16 @@ struct ConfigResult *parse_config(const char *input, struct context *context) {
}
}
if (strcmp(token->name, "line") == 0) {
while (*walk != '\0' && *walk != '\n' && *walk != '\r')
walk++;
next_state(token);
token_handled = true;
linecnt++;
walk++;
break;
}
if (strcmp(token->name, "end") == 0) {
//printf("checking for end: *%s*\n", walk);
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {

View File

@ -50,11 +50,14 @@ void ewmh_update_active_window(xcb_window_t window) {
}
/*
* Updates the workarea for each desktop.
*
* This function is not called at the moment due to:
* i3 currently does not support _NET_WORKAREA, because it does not correspond
* to i3s concept of workspaces. See also:
* http://bugs.i3wm.org/539
* http://bugs.i3wm.org/301
* http://bugs.i3wm.org/1038
*
* We need to actively delete this property because some display managers (e.g.
* LightDM) set it.
*
* EWMH: Contains a geometry for each desktop. These geometries specify an area
* that is completely contained within the viewport. Work area SHOULD be used by
@ -62,55 +65,7 @@ void ewmh_update_active_window(xcb_window_t window) {
*
*/
void ewmh_update_workarea(void) {
int num_workspaces = 0, count = 0;
Rect last_rect = {0, 0, 0, 0};
Con *output;
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *ws;
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
/* Check if we need to initialize last_rect. The case that the
* first workspace is all-zero may happen when the user
* assigned workspace 2 for his first screen, for example. Thus
* we need an initialized last_rect in the very first run of
* the following loop. */
if (last_rect.width == 0 && last_rect.height == 0 &&
ws->rect.width != 0 && ws->rect.height != 0) {
memcpy(&last_rect, &(ws->rect), sizeof(Rect));
}
num_workspaces++;
}
}
DLOG("Got %d workspaces\n", num_workspaces);
uint8_t *workarea = smalloc(sizeof(Rect) * num_workspaces);
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *ws;
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
DLOG("storing %d: %dx%d with %d x %d\n", count, ws->rect.x,
ws->rect.y, ws->rect.width, ws->rect.height);
/* If a workspace is not yet initialized and thus its
* dimensions are zero, we will instead put the dimensions
* of the last workspace in the list. For example firefox
* intersects all workspaces and does not cope so well with
* an all-zero workspace. */
if (ws->rect.width == 0 || ws->rect.height == 0) {
DLOG("re-using last_rect (%dx%d, %d, %d)\n",
last_rect.x, last_rect.y, last_rect.width,
last_rect.height);
memcpy(workarea + (sizeof(Rect) * count++), &last_rect, sizeof(Rect));
continue;
}
memcpy(workarea + (sizeof(Rect) * count++), &(ws->rect), sizeof(Rect));
memcpy(&last_rect, &(ws->rect), sizeof(Rect));
}
}
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
A__NET_WORKAREA, XCB_ATOM_CARDINAL, 32,
num_workspaces * (sizeof(Rect) / sizeof(uint32_t)),
workarea);
free(workarea);
xcb_flush(conn);
xcb_delete_property(conn, root, A__NET_WORKAREA);
}
/*
@ -164,5 +119,5 @@ void ewmh_setup_hints(void) {
/* 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_SUPPORTED, XCB_ATOM_ATOM, 32, 19, supported_atoms);
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 18, supported_atoms);
}

View File

@ -662,11 +662,7 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
void floating_reposition(Con *con, Rect newrect) {
/* Sanity check: Are the new coordinates on any output? If not, we
* ignore that request. */
Output *output = get_output_containing(
newrect.x + (newrect.width / 2),
newrect.y + (newrect.height / 2));
if (!output) {
if (!contained_by_output(newrect)) {
ELOG("No output found at destination coordinates. Not repositioning.\n");
return;
}

View File

@ -158,7 +158,7 @@ static void handle_enter_notify(xcb_enter_notify_event_t *event) {
}
/* see if the user entered the window on a certain window decoration */
int layout = (enter_child ? con->parent->layout : con->layout);
layout_t layout = (enter_child ? con->parent->layout : con->layout);
if (layout == L_DEFAULT) {
Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes)
@ -691,6 +691,45 @@ static void handle_client_message(xcb_client_message_event_t *event) {
xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char*)ev);
xcb_flush(conn);
free(reply);
} else if (event->type == A__NET_REQUEST_FRAME_EXTENTS) {
// A client can request an estimate for the frame size which the window
// manager will put around it before actually mapping its window. Java
// does this (as of openjdk-7).
//
// Note that the calculation below is not entirely accurate — once you
// set a different border type, its off. We _could_ request all the
// window properties (which have to be set up at this point according
// to EWMH), but that seems rather elaborate. The standard explicitly
// says the application must cope with an estimate that is not entirely
// accurate.
DLOG("_NET_REQUEST_FRAME_EXTENTS for window 0x%08x\n", event->window);
xcb_get_geometry_reply_t *geometry;
xcb_get_geometry_cookie_t cookie = xcb_get_geometry(conn, event->window);
if (!(geometry = xcb_get_geometry_reply(conn, cookie, NULL))) {
ELOG("Could not get geometry of X11 window 0x%08x while handling "
"the _NET_REQUEST_FRAME_EXTENTS ClientMessage\n",
event->window);
return;
}
DLOG("Current geometry = x=%d, y=%d, width=%d, height=%d\n",
geometry->x, geometry->y, geometry->width, geometry->height);
Rect r = {
0, // left
geometry->width + 4, // right
0, // top
geometry->height + config.font.height + 5, // bottom
};
xcb_change_property(
conn,
XCB_PROP_MODE_REPLACE,
event->window,
A__NET_FRAME_EXTENTS,
XCB_ATOM_CARDINAL, 32, 4,
&r);
xcb_flush(conn);
} else {
DLOG("unhandled clientmessage\n");
return;
@ -799,22 +838,18 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
goto render_and_return;
/* Check if we need to set proportional_* variables using the correct ratio */
double aspect_ratio = 0.0;
if ((width / height) < min_aspect) {
if (con->proportional_width != width ||
con->proportional_height != (width / min_aspect)) {
con->proportional_width = width;
con->proportional_height = width / min_aspect;
changed = true;
}
aspect_ratio = min_aspect;
} else if ((width / height) > max_aspect) {
if (con->proportional_width != width ||
con->proportional_height != (width / max_aspect)) {
con->proportional_width = width;
con->proportional_height = width / max_aspect;
changed = true;
}
aspect_ratio = max_aspect;
} else goto render_and_return;
if (fabs(con->aspect_ratio - aspect_ratio) > DBL_EPSILON) {
con->aspect_ratio = aspect_ratio;
changed = true;
}
render_and_return:
if (changed)
tree_render();

View File

@ -354,6 +354,11 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
}
y(array_close);
if (inplace_restart && con->window != NULL) {
ystr("depth");
y(integer, con->depth);
}
y(map_close);
}
@ -616,9 +621,29 @@ IPC_HANDLER(get_bar_config) {
YSTR_IF_SET(socket_path);
ystr("mode");
if (config->mode == M_HIDE)
ystr("hide");
else ystr("dock");
switch (config->mode) {
case M_HIDE:
ystr("hide");
break;
case M_INVISIBLE:
ystr("invisible");
break;
case M_DOCK:
default:
ystr("dock");
break;
}
ystr("hidden_state");
switch (config->hidden_state) {
case S_SHOW:
ystr("show");
break;
case S_HIDE:
default:
ystr("hide");
break;
}
ystr("modifier");
switch (config->modifier) {

View File

@ -51,12 +51,12 @@ static int json_start_map(void *ctx) {
if (last_key && strcasecmp(last_key, "floating_nodes") == 0) {
DLOG("New floating_node\n");
Con *ws = con_get_workspace(json_node);
json_node = con_new(NULL, NULL);
json_node = con_new_skeleton(NULL, NULL);
json_node->parent = ws;
DLOG("Parent is workspace = %p\n", ws);
} else {
Con *parent = json_node;
json_node = con_new(NULL, NULL);
json_node = con_new_skeleton(NULL, NULL);
json_node->parent = parent;
}
}
@ -69,6 +69,8 @@ static int json_end_map(void *ctx) {
if (!parsing_swallows && !parsing_rect && !parsing_window_rect && !parsing_geometry) {
LOG("attaching\n");
con_attach(json_node, json_node->parent, true);
LOG("Creating window\n");
x_con_init(json_node, json_node->depth);
json_node = json_node->parent;
}
if (parsing_rect)
@ -277,6 +279,9 @@ static int json_int(void *ctx, long val) {
if (strcasecmp(last_key, "current_border_width") == 0)
json_node->current_border_width = val;
if (strcasecmp(last_key, "depth") == 0)
json_node->depth = val;
if (!parsing_swallows && strcasecmp(last_key, "id") == 0)
json_node->old_id = val;

View File

@ -81,18 +81,29 @@ static void store_log_markers(void) {
void init_logging(void) {
if (!errorfilename) {
if (!(errorfilename = get_process_filename("errorlog")))
ELOG("Could not initialize errorlog\n");
fprintf(stderr, "Could not initialize errorlog\n");
else {
errorfile = fopen(errorfilename, "w");
if (fcntl(fileno(errorfile), F_SETFD, FD_CLOEXEC)) {
ELOG("Could not set close-on-exec flag\n");
fprintf(stderr, "Could not set close-on-exec flag\n");
}
}
}
/* Start SHM logging if shmlog_size is > 0. shmlog_size is SHMLOG_SIZE by
* default on development versions, and 0 on release versions. If it is
* not > 0, the user has turned it off, so let's close the logbuffer. */
if (shmlog_size > 0 && logbuffer == NULL)
open_logbuffer();
else if (shmlog_size <= 0 && logbuffer)
close_logbuffer();
atexit(purge_zerobyte_logfile);
}
/* If this is a debug build (not a release version), we will enable SHM
* logging by default, unless the user turned it off explicitly. */
if (logbuffer == NULL && shmlog_size > 0) {
/*
* Opens the logbuffer.
*
*/
void open_logbuffer(void) {
/* Reserve 1% of the RAM for the logfile, but at max 25 MiB.
* For 512 MiB of RAM this will lead to a 5 MiB log buffer.
* At the moment (2011-12-10), no testcase leads to an i3 log
@ -107,26 +118,29 @@ void init_logging(void) {
sysconf(_SC_PAGESIZE);
#endif
logbuffer_size = min(physical_mem_bytes * 0.01, shmlog_size);
#if defined(__FreeBSD__)
sasprintf(&shmlogname, "/tmp/i3-log-%d", getpid());
#else
sasprintf(&shmlogname, "/i3-log-%d", getpid());
#endif
logbuffer_shm = shm_open(shmlogname, O_RDWR | O_CREAT, S_IREAD | S_IWRITE);
if (logbuffer_shm == -1) {
ELOG("Could not shm_open SHM segment for the i3 log: %s\n", strerror(errno));
fprintf(stderr, "Could not shm_open SHM segment for the i3 log: %s\n", strerror(errno));
return;
}
if (ftruncate(logbuffer_shm, logbuffer_size) == -1) {
int ret;
if ((ret = posix_fallocate(logbuffer_shm, 0, logbuffer_size)) != 0) {
close(logbuffer_shm);
shm_unlink("/i3-log-");
ELOG("Could not ftruncate SHM segment for the i3 log: %s\n", strerror(errno));
shm_unlink(shmlogname);
fprintf(stderr, "Could not ftruncate SHM segment for the i3 log: %s\n", strerror(ret));
return;
}
logbuffer = mmap(NULL, logbuffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, logbuffer_shm, 0);
if (logbuffer == MAP_FAILED) {
close(logbuffer_shm);
shm_unlink("/i3-log-");
ELOG("Could not mmap SHM segment for the i3 log: %s\n", strerror(errno));
logbuffer = NULL;
close_logbuffer();
fprintf(stderr, "Could not mmap SHM segment for the i3 log: %s\n", strerror(errno));
return;
}
@ -138,14 +152,23 @@ void init_logging(void) {
pthread_condattr_t cond_attr;
pthread_condattr_init(&cond_attr);
if (pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED) != 0)
ELOG("pthread_condattr_setpshared() failed, i3-dump-log -f will not work!\n");
fprintf(stderr, "pthread_condattr_setpshared() failed, i3-dump-log -f will not work!\n");
pthread_cond_init(&(header->condvar), &cond_attr);
logwalk = logbuffer + sizeof(i3_shmlog_header);
loglastwrap = logbuffer + logbuffer_size;
store_log_markers();
}
atexit(purge_zerobyte_logfile);
}
/*
* Closes the logbuffer.
*
*/
void close_logbuffer(void) {
close(logbuffer_shm);
shm_unlink(shmlogname);
logbuffer = NULL;
shmlogname = "";
}
/*
@ -158,6 +181,14 @@ void set_verbosity(bool _verbose) {
verbose = _verbose;
}
/*
* Get debug logging.
*
*/
bool get_debug_logging(void) {
return debug_logging;
}
/*
* Set debug logging.
*

View File

@ -19,6 +19,7 @@
#include <sys/mman.h>
#include <sys/stat.h>
#include "all.h"
#include "shmlog.h"
#include "sd-daemon.h"
@ -67,6 +68,9 @@ 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. */
const int default_shmlog_size = 25 * 1024 * 1024;
/* The list of key bindings */
struct bindings_head *bindings;
@ -290,8 +294,8 @@ int main(int argc, char *argv[]) {
* (file) logging. */
init_logging();
/* On non-release builds, disable SHM logging by default. */
shmlog_size = (is_debug_build() ? 25 * 1024 * 1024 : 0);
/* On release builds, disable SHM logging by default. */
shmlog_size = (is_debug_build() ? default_shmlog_size : 0);
start_argv = argv;
@ -725,6 +729,7 @@ int main(int argc, char *argv[]) {
/* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */
x_set_i3_atoms();
ewmh_update_workarea();
struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io));
struct ev_io *xkb = scalloc(sizeof(struct ev_io));

View File

@ -4,7 +4,7 @@
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
*
* manage.c: Initially managing new windows (or existing ones on restart).
*
@ -122,34 +122,30 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
geomc = xcb_get_geometry(conn, d);
#define FREE_GEOMETRY() do { \
if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) != NULL) \
free(geom); \
} while (0)
/* Check if the window is mapped (it could be not mapped when intializing and
calling manage_window() for every window) */
if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
DLOG("Could not get attributes\n");
FREE_GEOMETRY();
xcb_discard_reply(conn, geomc.sequence);
return;
}
if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) {
FREE_GEOMETRY();
xcb_discard_reply(conn, geomc.sequence);
goto out;
}
/* Dont manage clients with the override_redirect flag */
if (attr->override_redirect) {
FREE_GEOMETRY();
xcb_discard_reply(conn, geomc.sequence);
goto out;
}
/* Check if the window is already managed */
if (con_by_window_id(window) != NULL) {
DLOG("already managed (by con %p)\n", con_by_window_id(window));
FREE_GEOMETRY();
xcb_discard_reply(conn, geomc.sequence);
goto out;
}

View File

@ -92,6 +92,31 @@ Output *get_output_containing(int x, int y) {
return NULL;
}
/*
* In contained_by_output, we check if any active output contains part of the container.
* We do this by checking if the output rect is intersected by the Rect.
* This is the 2-dimensional counterpart of get_output_containing.
* Since we don't actually need the outputs intersected by the given Rect (There could
* be many), we just return true or false for convenience.
*
*/
bool contained_by_output(Rect rect){
Output *output;
int lx = rect.x, uy = rect.y;
int rx = rect.x + rect.width, by = rect.y + rect.height;
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
continue;
DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
rect.x, rect.y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
if (rx >= (int)output->rect.x && lx <= (int)(output->rect.x + output->rect.width) &&
by >= (int)output->rect.y && uy <= (int)(output->rect.y + output->rect.height))
return true;
}
return false;
}
/*
* Like get_output_next with close_far == CLOSEST_OUTPUT, but wraps.
*

View File

@ -70,7 +70,7 @@ static void render_l_output(Con *con) {
Con *fullscreen = con_get_fullscreen_con(ws, CF_OUTPUT);
if (fullscreen) {
fullscreen->rect = con->rect;
x_raise_con(fullscreen);
x_raise_con(fullscreen, true);
render_con(fullscreen, true);
return;
}
@ -110,7 +110,7 @@ static void render_l_output(Con *con) {
DLOG("child at (%d, %d) with (%d x %d)\n",
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
x_raise_con(child);
x_raise_con(child, false);
render_con(child, false);
}
}
@ -172,13 +172,14 @@ void render_con(Con *con, bool render_fullscreen) {
* Ignoring aspect ratio during fullscreen was necessary to fix MPlayer
* subtitle rendering, see http://bugs.i3wm.org/594 */
if (!render_fullscreen &&
con->proportional_height != 0 &&
con->proportional_width != 0) {
con->aspect_ratio > 0.0) {
DLOG("aspect_ratio = %f, current width/height are %d/%d\n",
con->aspect_ratio, inset->width, inset->height);
double new_height = inset->height + 1;
int new_width = inset->width;
while (new_height > inset->height) {
new_height = ((double)con->proportional_height / con->proportional_width) * new_width;
new_height = (1.0 / con->aspect_ratio) * new_width;
if (new_height > inset->height)
new_width--;
@ -207,7 +208,7 @@ void render_con(Con *con, bool render_fullscreen) {
}
if (fullscreen) {
fullscreen->rect = rect;
x_raise_con(fullscreen);
x_raise_con(fullscreen, false);
render_con(fullscreen, true);
return;
}
@ -298,7 +299,7 @@ void render_con(Con *con, bool render_fullscreen) {
}
DLOG("floating child at (%d,%d) with %d x %d\n",
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
x_raise_con(child);
x_raise_con(child, false);
render_con(child, false);
}
}
@ -407,7 +408,7 @@ void render_con(Con *con, bool render_fullscreen) {
DLOG("child at (%d, %d) with (%d x %d)\n",
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
x_raise_con(child);
x_raise_con(child, false);
render_con(child, false);
i++;
}
@ -415,7 +416,7 @@ void render_con(Con *con, bool render_fullscreen) {
/* in a stacking or tabbed container, we ensure the focused client is raised */
if (con->layout == L_STACKED || con->layout == L_TABBED) {
TAILQ_FOREACH_REVERSE(child, &(con->focus_head), focus_head, focused)
x_raise_con(child);
x_raise_con(child, false);
if ((child = TAILQ_FIRST(&(con->focus_head)))) {
/* By rendering the stacked container again, we handle the case
* that we have a non-leaf-container inside the stack. In that
@ -429,7 +430,7 @@ void render_con(Con *con, bool render_fullscreen) {
* top of every stack window. That way, when a new window is opened in
* the stack, the old window will not obscure part of the decoration
* (its unmapped afterwards). */
x_raise_con(con);
x_raise_con(con, false);
}
}
}

View File

@ -39,6 +39,12 @@ void scratchpad_move(Con *con) {
return;
}
/* If the current con is in fullscreen mode, we need to disable that,
* as a scratchpad window should never be in fullscreen mode */
if (focused && focused->type != CT_WORKSPACE && focused->fullscreen_mode != CF_NONE) {
con_toggle_fullscreen(focused, CF_OUTPUT);
}
/* 1: Ensure the window or any parent is floating. From now on, we deal
* with the CT_FLOATING_CON. We use automatic == false because the user
* made the choice that this window should be a scratchpad (and floating).
@ -78,14 +84,25 @@ void scratchpad_show(Con *con) {
Con *__i3_scratch = workspace_get("__i3_scratch", NULL);
Con *floating;
/* If this was 'scratchpad show' without criteria, we check if the
* currently focused window is a scratchpad window and should be hidden
* again. */
if (!con &&
(floating = con_inside_floating(focused)) &&
floating->scratchpad_state != SCRATCHPAD_NONE) {
DLOG("Focused window is a scratchpad window, hiding it.\n");
scratchpad_move(focused);
return;
}
/* If the current con or any of its parents are in fullscreen mode, we
* first need to disable it before showing the scratchpad con. */
Con *fs = focused;
while (fs && fs->fullscreen_mode == CF_NONE)
fs = fs->parent;
if (fs->type != CT_WORKSPACE) {
con_toggle_fullscreen(focused, CF_OUTPUT);
if (fs && fs->type != CT_WORKSPACE) {
con_toggle_fullscreen(fs, CF_OUTPUT);
}
/* If this was 'scratchpad show' without criteria, we check if there is a
@ -93,7 +110,7 @@ void scratchpad_show(Con *con) {
Con *walk_con;
Con *focused_ws = con_get_workspace(focused);
TAILQ_FOREACH(walk_con, &(focused_ws->floating_head), floating_windows) {
if ((floating = con_inside_floating(walk_con)) &&
if (!con && (floating = con_inside_floating(walk_con)) &&
floating->scratchpad_state != SCRATCHPAD_NONE &&
floating != con_inside_floating(focused)) {
DLOG("Found an unfocused scratchpad window on this workspace\n");
@ -112,7 +129,7 @@ void scratchpad_show(Con *con) {
focused_ws = con_get_workspace(focused);
TAILQ_FOREACH(walk_con, &all_cons, all_cons) {
Con *walk_ws = con_get_workspace(walk_con);
if (walk_ws &&
if (!con && walk_ws &&
!con_is_internal(walk_ws) && focused_ws != walk_ws &&
(floating = con_inside_floating(walk_con)) &&
floating->scratchpad_state != SCRATCHPAD_NONE) {
@ -123,14 +140,10 @@ void scratchpad_show(Con *con) {
}
}
/* If this was 'scratchpad show' without criteria, we check if the
* currently focused window is a scratchpad window and should be hidden
* again. */
if (!con &&
(floating = con_inside_floating(focused)) &&
floating->scratchpad_state != SCRATCHPAD_NONE) {
DLOG("Focused window is a scratchpad window, hiding it.\n");
scratchpad_move(focused);
/* If this was 'scratchpad show' with criteria, we check if the window
* is actually in the scratchpad */
if (con && con->parent->scratchpad_state == SCRATCHPAD_NONE) {
DLOG("Window is not in the scratchpad, doing nothing.\n");
return;
}
@ -162,6 +175,10 @@ void scratchpad_show(Con *con) {
LOG("Use 'move scratchpad' to move a window to the scratchpad.\n");
return;
}
} else {
/* We used a criterion, so we need to do what follows (moving,
* resizing) on the floating parent. */
con = con_inside_floating(con);
}
/* 1: Move the window from __i3_scratch to the current workspace. */

View File

@ -444,7 +444,7 @@ static void _workspace_show(Con *workspace) {
} else
con_focus(next);
ipc_send_workspace_focus_event(workspace, old);
ipc_send_workspace_focus_event(workspace, current);
DLOG("old = %p / %s\n", old, (old ? old->name : "(null)"));
/* Close old workspace if necessary. This must be done *after* doing

32
src/x.c
View File

@ -36,6 +36,7 @@ typedef struct con_state {
bool mapped;
bool unmap_now;
bool child_mapped;
bool above_all;
/** The con for which this state is. */
Con *con;
@ -350,7 +351,7 @@ void x_draw_decoration(Con *con) {
p->con_deco_rect = con->deco_rect;
p->background = config.client.background;
p->con_is_leaf = con_is_leaf(con);
p->parent_orientation = con_orientation(parent);
p->parent_layout = con->parent->layout;
if (con->deco_render_params != NULL &&
(con->window == NULL || !con->window->name_x_changed) &&
@ -445,10 +446,10 @@ void x_draw_decoration(Con *con) {
TAILQ_PREV(con, nodes_head, nodes) == NULL &&
con->parent->type != CT_FLOATING_CON) {
xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->indicator });
if (p->parent_orientation == HORIZ)
if (p->parent_layout == L_SPLITH)
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 } });
else
else if (p->parent_layout == L_SPLITV)
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 } });
}
@ -899,6 +900,10 @@ void x_push_changes(Con *con) {
xcb_configure_window(conn, prev->id, mask, values);
}
if (state->above_all) {
DLOG("above all: 0x%08x\n", state->id);
xcb_configure_window(conn, state->id, XCB_CONFIG_WINDOW_STACK_MODE, (uint32_t[]){ XCB_STACK_MODE_ABOVE });
}
state->initial = false;
}
@ -1024,12 +1029,18 @@ void x_push_changes(Con *con) {
* Raises the specified container in the internal stack of X windows. The
* next call to x_push_changes() will make the change visible in X11.
*
* If above_all is true, the X11 window will be raised to the top
* of the stack. This should only be used for precisely one fullscreen
* window per output.
*
*/
void x_raise_con(Con *con) {
void x_raise_con(Con *con, bool above_all) {
con_state *state;
state = state_for_frame(con->frame);
//DLOG("raising in new stack: %p / %s / %s / xid %08x\n", con, con->name, con->window ? con->window->name_json : "", state->id);
state->above_all = above_all;
CIRCLEQ_REMOVE(&state_head, state, state);
CIRCLEQ_INSERT_HEAD(&state_head, state, state);
}
@ -1052,6 +1063,16 @@ void x_set_name(Con *con, const char *name) {
state->name = sstrdup(name);
}
/*
* Set up the I3_SHMLOG_PATH atom.
*
*/
void update_shmlog_atom() {
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
A_I3_SHMLOG_PATH, A_UTF8_STRING, 8,
strlen(shmlogname), shmlogname);
}
/*
* Sets up i3 specific atoms (I3_SOCKET_PATH and I3_CONFIG_PATH)
*
@ -1064,8 +1085,7 @@ void x_set_i3_atoms(void) {
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_PID, XCB_ATOM_CARDINAL, 32, 1, &pid);
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8,
strlen(current_configpath), current_configpath);
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_SHMLOG_PATH, A_UTF8_STRING, 8,
strlen(shmlogname), shmlogname);
update_shmlog_atom();
}
/*

View File

@ -315,6 +315,11 @@ Usually, though, calls are simpler:
my $top_window = open_window;
To identify the resulting window object in i3 commands, use the id property:
my $top_window = open_window;
cmd '[id="' . $top_window->id . '"] kill';
=cut
sub open_window {
my %args = @_ == 1 ? %{$_[0]} : @_;

View File

@ -278,6 +278,32 @@ for ($type = 1; $type <= 2; $type++) {
is($w->{urgent}, 0, 'Urgent flag no longer set after killing the window ' .
'from another workspace');
##############################################################################
# Check if urgent flag can be unset if we move the window out of the container
##############################################################################
my $tmp = fresh_workspace;
cmd 'layout tabbed';
my $w1 = open_window;
my $w2 = open_window;
sync_with_i3;
cmd '[id="' . $w2->id . '"] focus';
sync_with_i3;
cmd 'split v';
cmd 'layout stacked';
my $w3 = open_window;
sync_with_i3;
cmd '[id="' . $w2->id . '"] focus';
sync_with_i3;
set_urgency($w3, 1, $type);
sync_with_i3;
cmd 'focus parent';
sync_with_i3;
cmd 'move right';
cmd '[id="' . $w3->id . '"] focus';
sync_with_i3;
my $ws = get_ws($tmp);
ok(!$ws->{urgent}, 'urgent flag not set on workspace');
exit_gracefully($pid);
}

View File

@ -144,7 +144,7 @@ is(parser_calls("\nworkspace test"),
################################################################################
is(parser_calls('unknown_literal'),
"ERROR: Expected one of these tokens: <end>, '[', 'move', 'exec', 'exit', 'restart', 'reload', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'split', 'floating', 'mark', 'resize', 'rename', 'nop', 'scratchpad', 'mode'\n" .
"ERROR: Expected one of these tokens: <end>, '[', 'move', 'exec', 'exit', 'restart', 'reload', 'shmlog', 'debuglog', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'split', 'floating', 'mark', 'unmark', 'resize', 'rename', 'nop', 'scratchpad', 'mode', 'bar'\n" .
"ERROR: Your command: unknown_literal\n" .
"ERROR: ^^^^^^^^^^^^^^^",
'error for unknown literal ok');

View File

@ -448,6 +448,21 @@ is(parser_calls($config),
$expected,
'errors dont harm subsequent statements');
################################################################################
# Regression: semicolons end comments, but shouldnt
################################################################################
$config = <<'EOT';
# "foo" client.focused #4c7899 #285577 #ffffff #2e9ef4
EOT
$expected = <<'EOT';
EOT
is(parser_calls($config),
$expected,
'semicolon does not end a comment line');
################################################################################
# Error message with 2+2 lines of context
@ -612,7 +627,7 @@ EOT
$expected = <<'EOT';
cfg_bar_output(LVDS-1)
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'modifier', 'position', 'output', 'tray_output', 'font', 'workspace_buttons', 'verbose', 'colors', '}'
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'position', 'output', 'tray_output', 'font', 'workspace_buttons', 'verbose', 'colors', '}'
ERROR: CONFIG: (in file <stdin>)
ERROR: CONFIG: Line 1: bar {
ERROR: CONFIG: Line 2: output LVDS-1

View File

@ -17,42 +17,159 @@
# Verifies that using criteria to address scratchpad windows works.
use i3test;
################################################################################
my $i3 = i3(get_socket_path());
#####################################################################
# Verify that using scratchpad show with criteria works as expected:
# When matching a scratchpad window which is visible, it should hide it.
# When matching a scratchpad window which is on __i3_scratch, it should show it.
# When matching a non-scratchpad window, it should be a no-op.
################################################################################
# - When matching a scratchpad window which is visible,
# it should hide it.
# - When matching a scratchpad window which is on __i3_scratch,
# it should show it.
# - When matching a non-scratchpad window, it should be a no-op.
# - When matching a scratchpad window,
# non-matching windows shouldn't appear
######################################################################
my $tmp = fresh_workspace;
my $third_window = open_window(name => 'scratch-match');
cmd 'move scratchpad';
# Verify that using 'scratchpad show' without any matching windows is a no-op.
#####################################################################
# Verify that using 'scratchpad show' without any matching windows is
# a no-op.
#####################################################################
my $old_focus = get_focused($tmp);
cmd '[title="nomatch"] scratchpad show';
is(get_focused($tmp), $old_focus, 'non-matching criteria have no effect');
#####################################################################
# Verify that we can use criteria to show a scratchpad window.
#####################################################################
cmd '[title="scratch-match"] scratchpad show';
my $scratch_focus = get_focused($tmp);
isnt($scratch_focus, $old_focus, 'matching criteria works');
# Check that the window was centered and resized too.
my $tree = $i3->get_tree->recv;
my $ws = get_ws($tmp);
my $scratchrect = $ws->{floating_nodes}->[0]->{rect};
my $output = $tree->{nodes}->[1];
my $outputrect = $output->{rect};
is($scratchrect->{width}, $outputrect->{width} * 0.5, 'scratch width is 50%');
is($scratchrect->{height}, $outputrect->{height} * 0.75, 'scratch height is 75%');
is($scratchrect->{x},
($outputrect->{width} / 2) - ($scratchrect->{width} / 2),
'scratch window centered horizontally');
is($scratchrect->{y},
($outputrect->{height} / 2 ) - ($scratchrect->{height} / 2),
'scratch window centered vertically');
cmd '[title="scratch-match"] scratchpad show';
isnt(get_focused($tmp), $scratch_focus, 'matching criteria works');
is(get_focused($tmp), $old_focus, 'focus restored');
#####################################################################
# Verify that we cannot use criteria to show a non-scratchpad window.
#####################################################################
my $tmp2 = fresh_workspace;
my $non_scratch_window = open_window(name => 'non-scratch');
cmd "workspace $tmp";
is(get_focused($tmp), $old_focus, 'focus still ok');
cmd '[title="non-match"] scratchpad show';
cmd '[title="non-scratch"] scratchpad show';
is(get_focused($tmp), $old_focus, 'focus unchanged');
#####################################################################
# Verify that non-matching windows doesn't appear
#####################################################################
# Subroutine to clear scratchpad
sub clear_scratchpad {
while (scalar @{get_ws('__i3_scratch')->{floating_nodes}}) {
cmd 'scratchpad show';
cmd 'kill';
}
}
#Start from an empty fresh workspace
my $empty_ws = fresh_workspace;
cmd "workspace $empty_ws";
my $no_focused = get_focused($empty_ws);
cmd '[title="nothingmatchthistitle"] scratchpad show';
#Check nothing match
is(get_focused($empty_ws), $no_focused, "no window to focus on");
clear_scratchpad;
open_window(name => "my-scratch-window");
my $w1_focus = get_focused($empty_ws);
cmd 'move scratchpad';
cmd '[title="my-scratch-window"] scratchpad show';
#Check we created and shown a scratchpad window
is(get_focused($empty_ws), $w1_focus, "focus on scratchpad window");
#Switching workspace
my $empty_ws2 = fresh_workspace;
cmd "workspace $empty_ws2";
open_window(name => "my-second-scratch-window");
my $w2_focus = get_focused($empty_ws2);
cmd 'move scratchpad';
cmd '[title="my-second-scratch-window"] scratchpad show';
#Check we got the correct window
is(get_focused($empty_ws2), $w2_focus, "focus is on second window");
#####################################################################
# Verify that 'scratchpad show' correctly hide multiple scratchpad
# windows
#####################################################################
clear_scratchpad;
sub check_floating {
my($rws, $n) = @_;
my $ws = get_ws($rws);
is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
is(scalar @{$ws->{floating_nodes}}, $n, "$n floating windows on ws");
}
my $empty_ws3 = fresh_workspace;
cmd "workspace $empty_ws3";
check_floating($empty_ws3, 0);
#Creating two scratchpad windows
open_window(name => "toggle-1");
cmd 'move scratchpad';
open_window(name => "toggle-2");
cmd 'move scratchpad';
check_floating($empty_ws3, 0);
#Showing both
cmd '[title="toggle-"] scratchpad show';
check_floating($empty_ws3, 2);
#Hiding both
cmd '[title="toggle-"] scratchpad show';
check_floating($empty_ws3, 0);
#Showing both again
cmd '[title="toggle-"] scratchpad show';
check_floating($empty_ws3, 2);
#Hiding one
cmd 'scratchpad show';
check_floating($empty_ws3, 1);
#Hiding the last
cmd 'scratchpad show';
check_floating($empty_ws3, 0);
done_testing;

View File

@ -0,0 +1,94 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • http://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • http://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • http://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Assure that no window is in fullscreen mode after showing a scratchpad window
# Bug still in: 4.5.1-54-g0f6b5fe
use i3test;
my $tmp = fresh_workspace;
sub fullscreen_windows {
my $ws = $tmp;
$ws = shift if @_;
my $nodes = scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($ws)->[0]->{nodes}};
my $cons = scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($ws)};
return $nodes + $cons;
}
##########################################################################################
# map two windows in one container, fullscreen one of them and then move it to scratchpad
##########################################################################################
my $first_win = open_window;
my $second_win = open_window;
# fullscreen the focused window
cmd 'fullscreen';
# see if the window really is in fullscreen mode
is(fullscreen_windows(), 1, 'amount of fullscreen windows after enabling fullscreen');
# move window to scratchpad
cmd 'move scratchpad';
###############################################################################
# show the scratchpad window again; it should not be in fullscreen mode anymore
###############################################################################
# show window from scratchpad
cmd 'scratchpad show';
# switch window back to tiling mode
cmd 'floating toggle';
# see if no window is in fullscreen mode
is(fullscreen_windows(), 0, 'amount of fullscreen windows after showing previously fullscreened scratchpad window');
########################################################################################
# move a window to scratchpad, focus parent container, make it fullscreen, focus a child
########################################################################################
# make layout tabbed
cmd 'layout tabbed';
# move one window to scratchpad
cmd 'move scratchpad';
# focus parent
cmd 'focus parent';
# fullscreen the container
cmd 'fullscreen';
# focus child
cmd 'focus child';
# see if the window really is in fullscreen mode
is(fullscreen_windows(), 1, 'amount of fullscreen windows after enabling fullscreen on parent');
##########################################################################
# show a scratchpad window; no window should be in fullscreen mode anymore
##########################################################################
# show the scratchpad window
cmd 'scratchpad show';
# see if no window is in fullscreen mode
is(fullscreen_windows(), 0, 'amount of fullscreen windows after showing a scratchpad window while a parent container was in fullscreen mode');
done_testing;

87
testcases/t/207-shmlog.t Normal file
View File

@ -0,0 +1,87 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • http://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • http://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • http://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
use i3test i3_autostart => 0;
use IPC::Run qw(run);
use File::Temp;
################################################################################
# 1: test that shared memory logging does not work yet
################################################################################
my $config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
EOT
# NB: launch_with_config sets --shmlog-size=0 because the logfile gets
# redirected via stdout redirection anyways.
my $pid = launch_with_config($config);
my $stdout;
my $stderr;
run [ '../i3-dump-log/i3-dump-log' ],
'>', \$stdout,
'2>', \$stderr;
like($stderr, qr#^i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled\.#,
'shm logging not enabled');
################################################################################
# 2: enable shared memory logging and verify new content shows up
################################################################################
cmd 'shmlog on';
my $random_nop = mktemp('nop.XXXXXX');
cmd "nop $random_nop";
run [ '../i3-dump-log/i3-dump-log' ],
'>', \$stdout,
'2>', \$stderr;
like($stdout, qr#$random_nop#, 'random nop found in shm log');
like($stderr, qr#^$#, 'stderr empty');
################################################################################
# 3: change size of the shared memory log buffer and verify old content is gone
################################################################################
cmd 'shmlog ' . (23 * 1024 * 1024);
run [ '../i3-dump-log/i3-dump-log' ],
'>', \$stdout,
'2>', \$stderr;
unlike($stdout, qr#$random_nop#, 'random nop not found in shm log');
like($stderr, qr#^$#, 'stderr empty');
################################################################################
# 4: disable logging and verify it no longer works
################################################################################
cmd 'shmlog off';
run [ '../i3-dump-log/i3-dump-log' ],
'>', \$stdout,
'2>', \$stderr;
like($stderr, qr#^i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled\.#,
'shm logging not enabled');
exit_gracefully($pid);
done_testing;

View File

@ -0,0 +1,71 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • http://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • http://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • http://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Verifies the _NET_WORKAREA hint is deleted in case it is already set on the
# root window.
# Ticket: #1038
# Bug still in: 4.5.1-103-g1f8a860
use i3test i3_autostart => 0;
use X11::XCB qw(PROP_MODE_REPLACE);
my $atom_cookie = $x->intern_atom(
0, # create!
length('_NET_WORKAREA'),
'_NET_WORKAREA',
);
my $_net_workarea_id = $x->intern_atom_reply($atom_cookie->{sequence})->{atom};
$x->change_property(
PROP_MODE_REPLACE,
$x->get_root_window(),
$_net_workarea_id,
$x->atom(name => 'CARDINAL')->id,
32,
4,
pack('L4', 0, 0, 1024, 768));
$x->flush;
sub is_net_workarea_set {
my $cookie = $x->get_property(
0,
$x->get_root_window(),
$x->atom(name => '_NET_WORKAREA')->id,
$x->atom(name => 'CARDINAL')->id,
0,
4096,
);
my $reply = $x->get_property_reply($cookie->{sequence});
return 0 if $reply->{value_len} == 0;
return 0 if $reply->{format} == 0;
return 1
}
ok(is_net_workarea_set(), '_NET_WORKAREA is set before starting i3');
my $config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
fake-outputs 1024x768+0+0
EOT
my $pid = launch_with_config($config);
ok(!is_net_workarea_set(), '_NET_WORKAREA not set after starting i3');
exit_gracefully($pid);
done_testing;

View File

@ -0,0 +1,79 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • http://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • http://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • http://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# checks if mark and unmark work correctly
use i3test;
sub get_marks {
return i3(get_socket_path())->get_marks->recv;
}
##############################################################
# 1: check that there are no marks set yet
##############################################################
my $tmp = fresh_workspace;
cmd 'split h';
is_deeply(get_marks(), [], 'no marks set yet');
##############################################################
# 2: mark a con, check that it's marked, unmark it, check that
##############################################################
my $one = open_window;
cmd 'mark foo';
is_deeply(get_marks(), ["foo"], 'mark foo set');
cmd 'unmark foo';
is_deeply(get_marks(), [], 'mark foo removed');
##############################################################
# 3: mark three cons, check that they are marked
# unmark one con, check that it's unmarked
# unmark all cons, check that they are unmarked
##############################################################
my $left = open_window;
my $middle = open_window;
my $right = open_window;
cmd 'mark right';
cmd 'focus left';
cmd 'mark middle';
cmd 'focus left';
cmd 'mark left';
#
# get_marks replys an array of marks, whose order is undefined,
# so we use sort to be able to compare the output
#
is_deeply(sort(get_marks()), ["left","middle","right"], 'all three marks set');
cmd 'unmark right';
is_deeply(sort(get_marks()), ["left","middle"], 'mark right removed');
cmd 'unmark';
is_deeply(get_marks(), [], 'all marks removed');
done_testing;

View File

@ -0,0 +1,72 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • http://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • http://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • http://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Ticket: #990
# Bug still in: 4.5.1-23-g82b5978
use i3test i3_autostart => 0;
my $config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
fake-outputs 1024x768+0+0,1024x768+1024+0
EOT
my $pid = launch_with_config($config);
my $i3 = i3(get_socket_path());
$i3->connect()->recv;
################################
# Workspaces requests and events
################################
my $focused = get_ws(focused_ws());
# Events
# We are switching to an empty workpspace on the output to the right from an empty workspace on the output on the left, so we expect
# to receive "init", "focus", and "empty".
my $focus = AnyEvent->condvar;
$i3->subscribe({
workspace => sub {
my ($event) = @_;
if ($event->{change} eq 'focus') {
# Check that we have the old and new workspace
$focus->send(
$event->{current}->{name} == '2' &&
$event->{old}->{name} == $focused->{name}
);
}
}
})->recv;
cmd 'focus output right';
my $t;
$t = AnyEvent->timer(
after => 0.5,
cb => sub {
$focus->send(0);
}
);
ok($focus->recv, 'Workspace "focus" event received');
exit_gracefully($pid);
done_testing;