Merge branch 'next'
This commit is contained in:
commit
f682841ae1
|
@ -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
|
||||
|
|
|
@ -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 don’t 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
|
|
@ -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(";
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, it’s 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 wasn’t 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 don’t 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 don’t forget our
|
||||
|
|
|
@ -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
|
||||
}
|
||||
------------------------------------------
|
||||
|
|
26
docs/ipc
26
docs/ipc
|
@ -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]]
|
||||
|
|
|
@ -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:
|
||||
----------------------------------------------
|
||||
|
|
|
@ -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)
|
||||
|
|
165
docs/userguide
165
docs/userguide
|
@ -1,7 +1,7 @@
|
|||
i3 User’s 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
|
||||
|
||||
|
@ -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 window’s 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 can’t 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
|
||||
|
|
46
docs/wsbar
46
docs/wsbar
|
@ -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 don’t 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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 doesn’t 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
|
||||
|
||||
|
|
|
@ -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 it’s 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);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=i3
|
||||
NoDisplay=true
|
||||
Comment=improved dynamic tiling window manager
|
||||
Exec=i3
|
||||
X-GNOME-WMName=i3
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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\" ]");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
case 'b':
|
||||
config.bar_id = sstrdup(optarg);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
print_usage(argv[0]);
|
||||
|
|
|
@ -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 */
|
||||
|
|
202
i3bar/src/xcb.c
202
i3bar/src/xcb.c
|
@ -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, don’t 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 (xcb_request_failed(pm_cookie, "Could not create pixmap")) {
|
||||
|
||||
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(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,20 +1762,23 @@ 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 */
|
||||
/* 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 (walks_away) {
|
||||
} 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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, don’t 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.
|
||||
|
|
|
@ -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);
|
||||
|
|
126
include/data.h
126
include/data.h
|
@ -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
|
||||
|
|
|
@ -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 i3’s 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
12
include/x.h
12
include/x.h
|
@ -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)
|
||||
*
|
||||
|
|
|
@ -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 it’s in /usr/bin? */
|
||||
return "/usr/bin/i3-nagbar";
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
191
src/commands.c
191
src/commands.c
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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, it’s quite hard to specify a
|
||||
* context-free grammar for the commands. A PEG grammar would be easier, but
|
||||
* there’s downsides to every PEG parser generator I have come accross so far.
|
||||
* there’s 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
|
||||
|
|
27
src/con.c
27
src/con.c
|
@ -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
|
||||
* don’t close it automatically. */
|
||||
|
|
43
src/config.c
43
src/config.c
|
@ -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,
|
||||
|
|
|
@ -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(¤t_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)
|
||||
|
|
|
@ -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') {
|
||||
|
|
61
src/ewmh.c
61
src/ewmh.c
|
@ -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 i3’s 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) {
|
|||
/* I’m 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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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, it’s 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,21 +838,17 @@ 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;
|
||||
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;
|
||||
}
|
||||
} else goto render_and_return;
|
||||
|
||||
render_and_return:
|
||||
if (changed)
|
||||
|
|
29
src/ipc.c
29
src/ipc.c
|
@ -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)
|
||||
switch (config->mode) {
|
||||
case M_HIDE:
|
||||
ystr("hide");
|
||||
else ystr("dock");
|
||||
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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
61
src/log.c
61
src/log.c
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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));
|
||||
|
|
14
src/manage.c
14
src/manage.c
|
@ -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;
|
||||
}
|
||||
|
||||
/* Don’t 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;
|
||||
}
|
||||
|
||||
|
|
25
src/randr.c
25
src/randr.c
|
@ -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.
|
||||
*
|
||||
|
|
21
src/render.c
21
src/render.c
|
@ -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
|
||||
* (it’s unmapped afterwards). */
|
||||
x_raise_con(con);
|
||||
x_raise_con(con, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
32
src/x.c
|
@ -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();
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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]} : @_;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -448,6 +448,21 @@ is(parser_calls($config),
|
|||
$expected,
|
||||
'errors dont harm subsequent statements');
|
||||
|
||||
################################################################################
|
||||
# Regression: semicolons end comments, but shouldn’t
|
||||
################################################################################
|
||||
|
||||
$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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
Loading…
Reference in New Issue