diff --git a/.gitignore b/.gitignore index efeb4893..0160a246 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/RELEASE-NOTES-4.6 b/RELEASE-NOTES-4.6 new file mode 100644 index 00000000..4928c1b7 --- /dev/null +++ b/RELEASE-NOTES-4.6 @@ -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 diff --git a/contrib/dump-asy.pl b/contrib/dump-asy.pl index 47239f2d..3ebdb858 100755 --- a/contrib/dump-asy.pl +++ b/contrib/dump-asy.pl @@ -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("; diff --git a/contrib/gtk-tree-watch.pl b/contrib/gtk-tree-watch.pl index f15d0c18..30cc64fc 100755 --- a/contrib/gtk-tree-watch.pl +++ b/contrib/gtk-tree-watch.pl @@ -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); diff --git a/debian/changelog b/debian/changelog index ebdfbcef..80a20e3c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -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 Wed, 12 Dec 2012 00:23:32 +0100 + -- Michael Stapelberg Mon, 18 Mar 2013 23:01:30 +0100 + +i3-wm (4.5.1-1) experimental; urgency=low + + * New upstream release + + -- Michael Stapelberg Mon, 18 Mar 2013 22:50:12 +0100 + +i3-wm (4.5-1) experimental; urgency=low + + * New upstream release + + -- Michael Stapelberg Tue, 12 Mar 2013 13:51:04 +0100 i3-wm (4.4-1) experimental; urgency=low diff --git a/debian/rules b/debian/rules index b119c47e..3ae79ddd 100755 --- a/debian/rules +++ b/debian/rules @@ -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 diff --git a/docs/hacking-howto b/docs/hacking-howto index 633c2771..73f8e88a 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -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 diff --git a/docs/i3bar-protocol b/docs/i3bar-protocol index 9225d97e..bd8ea536 100644 --- a/docs/i3bar-protocol +++ b/docs/i3bar-protocol @@ -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 +} +------------------------------------------ diff --git a/docs/ipc b/docs/ipc index 8cfb21d0..913899cc 100644 --- a/docs/ipc +++ b/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]] diff --git a/docs/multi-monitor b/docs/multi-monitor index a1fd6dc0..cafe5691 100644 --- a/docs/multi-monitor +++ b/docs/multi-monitor @@ -1,15 +1,16 @@ The multi-monitor situation =========================== -Michael Stapelberg -September 2011 +Michael Stapelberg +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: ---------------------------------------------- diff --git a/docs/testsuite b/docs/testsuite index 9b7485bb..6c3a36d9 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -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) diff --git a/docs/userguide b/docs/userguide index a0f521c2..f2645998 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1,7 +1,7 @@ i3 User’s Guide =============== Michael Stapelberg -February 2013 +March 2013 This document contains all the information you need to configure and use the i3 window manager. If it does not, please check http://faq.i3wm.org/ first, then @@ -26,8 +26,8 @@ are your homerow. == Using i3 Throughout this guide, the keyword +$mod+ will be used to refer to the -configured modifier. This is the Alt key (Mod1) by default, with windows (Mod4) -being a popular alternative. +configured modifier. This is the Alt key (Mod1) by default, with the Windows +key (Mod4) being a popular alternative. === Opening terminals and moving around @@ -147,7 +147,7 @@ columns/rows with your keyboard. === Restarting i3 inplace -To restart i3 inplace (and thus get into a clean state if there is a bug, or +To restart i3 in place (and thus get into a clean state if there is a bug, or to upgrade to a newer version of i3) you can use +$mod+Shift+r+. === Exiting i3 @@ -156,11 +156,12 @@ To cleanly exit i3 without killing your X server, you can use +$mod+Shift+e+. === Floating -Floating mode is the opposite of tiling mode. The position and size of a window -are not managed by i3, but by you. Using this mode violates the tiling -paradigm but can be useful for some corner cases like "Save as" dialog -windows, or toolbar windows (GIMP or similar). Those windows usually set the -appropriate hint and are opened in floating mode by default. +Floating mode is the opposite of tiling mode. The position and size of +a window are not managed automatically by i3, but manually by +you. Using this mode violates the tiling paradigm but can be useful +for some corner cases like "Save as" dialog windows, or toolbar +windows (GIMP or similar). Those windows usually set the appropriate +hint and are opened in floating mode by default. You can toggle floating mode for a window by pressing +$mod+Shift+Space+. By dragging the 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 +mode +hidden_state modifier ---------------- @@ -1016,12 +1036,31 @@ 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 +--------------------- + +*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 +layout 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 +, the current log will be +discarded and a new one will be started. + +*Syntax*: +------------------------------ +shmlog +shmlog +------------------------------ + +*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 <> command. + +*Syntax*: +------------------------ +debuglog +------------------------ + +*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 mode dock|hide|invisible|toggle [] +--------------- + +*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 diff --git a/docs/wsbar b/docs/wsbar index 9e379dd9..6405880a 100644 --- a/docs/wsbar +++ b/docs/wsbar @@ -1,23 +1,18 @@ External workspace bars ======================= -Michael Stapelberg -May 2010 +Michael Stapelberg +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. diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index 54c8e024..ffc3df93 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -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 diff --git a/i3-dmenu-desktop b/i3-dmenu-desktop index 65e99ec0..cccc1dc5 100755 --- a/i3-dmenu-desktop +++ b/i3-dmenu-desktop @@ -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 diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 6ca80ba5..a9619f96 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -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); } diff --git a/i3.applications.desktop b/i3.applications.desktop index 31d67045..e40689dd 100644 --- a/i3.applications.desktop +++ b/i3.applications.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Type=Application Name=i3 +NoDisplay=true Comment=improved dynamic tiling window manager Exec=i3 X-GNOME-WMName=i3 diff --git a/i3bar/include/child.h b/i3bar/include/child.h index d1c46890..dc244bef 100644 --- a/i3bar/include/child.h +++ b/i3bar/include/child.h @@ -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 diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 1365082f..cb55e0d6 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -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; }; diff --git a/i3bar/include/config.h b/i3bar/include/config.h index 4f6e8858..4c01d68c 100644 --- a/i3bar/include/config.h +++ b/i3bar/include/config.h @@ -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; diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h index d8d0c090..e1654a34 100644 --- a/i3bar/include/xcb.h +++ b/i3bar/include/xcb.h @@ -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 diff --git a/i3bar/src/child.c b/i3bar/src/child.c index e5f4ea21..4e5e49c9 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -21,6 +21,7 @@ #include #include #include +#include #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. * diff --git a/i3bar/src/config.c b/i3bar/src/config.c index 6c7286c4..f5a2a342 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -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; } diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index faab8e10..3536b7dc 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -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\" ]"); } } diff --git a/i3bar/src/main.c b/i3bar/src/main.c index ea605647..c62f7b3c 100644 --- a/i3bar/src/main.c +++ b/i3bar/src/main.c @@ -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 \tBar ID for which to get the configuration\n"); - printf("-s \tConnect to i3 via \n"); - printf("-h\t\tDisplay this help-message and exit\n"); - printf("-v\t\tDisplay version number and exit\n"); + printf("-b, --bar_id \tBar ID for which to get the configuration\n"); + printf("-s, --socket \tConnect to i3 via \n"); + printf("-h, --help Display this help-message and exit\n"); + printf("-v, --version Display version number and exit\n"); printf("\n"); printf(" PLEASE NOTE that i3bar will be automatically started by i3\n" " as soon as there is a 'bar' configuration block in your\n" @@ -97,13 +97,13 @@ int main(int argc, char **argv) { static struct option long_opt[] = { { "socket", required_argument, 0, 's' }, - { "bar_id", required_argument, 0, 0 }, + { "bar_id", required_argument, 0, 'b' }, { "help", no_argument, 0, 'h' }, { "version", no_argument, 0, 'v' }, { NULL, 0, 0, 0} }; - while ((opt = getopt_long(argc, argv, "s:hv", long_opt, &option_index)) != -1) { + while ((opt = getopt_long(argc, argv, "b:s:hv", long_opt, &option_index)) != -1) { switch (opt) { case 's': socket_path = expand_path(optarg); @@ -112,11 +112,8 @@ int main(int argc, char **argv) { printf("i3bar version " I3_VERSION " © 2010-2011 Axel Wagner and contributors\n"); exit(EXIT_SUCCESS); break; - case 0: - if (!strcmp(long_opt[option_index].name, "bar_id")) { - FREE(config.bar_id); - config.bar_id = sstrdup(optarg); - } + case 'b': + config.bar_id = sstrdup(optarg); break; default: print_usage(argv[0]); diff --git a/i3bar/src/parse_json_header.c b/i3bar/src/parse_json_header.c index 80ec5af8..c09e0f49 100644 --- a/i3bar/src/parse_json_header.c +++ b/i3bar/src/parse_json_header.c @@ -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 */ diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 3cdf6ed7..15c68a08 100644 --- a/i3bar/src/xcb.c +++ b/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 (config.hide_on_modifier == M_HIDE) { + /* Switching to hide mode, register for keyevents */ + register_xkb_keyevents(); + } else { + /* Switching to dock/invisible mode, deregister from keyevents */ + deregister_xkb_keyevents(); + } } - if (xcb_request_failed(pm_cookie, "Could not create pixmap")) { + + if (xcb_request_failed(cfg_cookie, "Could not reconfigure window") || + xcb_request_failed(chg_cookie, "Could not change window") || + xcb_request_failed(pm_cookie, "Could not create pixmap") || + (redraw_bars && (xcb_request_failed(umap_cookie, "Could not unmap window") || + (config.hide_on_modifier == M_DOCK && xcb_request_failed(map_cookie, "Could not map window"))))) { exit(EXIT_FAILURE); } } @@ -1518,7 +1608,7 @@ void reconfig_windows(void) { */ void draw_bars(bool unhide) { DLOG("Drawing Bars...\n"); - int i = 1; + int i = 0; refresh_statusline(); @@ -1533,7 +1623,7 @@ void draw_bars(bool unhide) { } if (outputs_walk->bar == XCB_NONE) { /* Oh shit, an active output without an own bar. Create it now! */ - reconfig_windows(); + reconfig_windows(false); } /* First things first: clear the backbuffer */ uint32_t color = colors.bar_bg; @@ -1573,7 +1663,7 @@ void draw_bars(bool unhide) { outputs_walk->bargc, MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0, MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3, - MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font.height); + MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font.height + 2); } if (config.disable_ws) { @@ -1672,19 +1762,22 @@ void draw_bars(bool unhide) { set_font_colors(outputs_walk->bargc, fg_color, bg_color); draw_text(binding.name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 3, binding.width); + + unhide = true; } i = 0; } - if (!mod_pressed) { - if (unhide) { - /* The urgent-hint should get noticed, so we unhide the bars shortly */ - unhide_bars(); - } else if (walks_away) { - FREE(last_urgent_ws); - hide_bars(); - } + /* Assure the bar is hidden/unhidden according to the specified hidden_state and mode */ + bool should_unhide = (config.hidden_state == S_SHOW || (unhide && config.hidden_state == S_HIDE)); + bool should_hide = (config.hide_on_modifier == M_INVISIBLE); + + if (mod_pressed || (should_unhide && !should_hide)) { + unhide_bars(); + } else if (!mod_pressed && (walks_away || should_hide)) { + FREE(last_urgent_ws); + hide_bars(); } redraw_bars(); @@ -1719,5 +1812,6 @@ void redraw_bars(void) { void set_current_mode(struct mode *current) { I3STRING_FREE(binding.name); binding = *current; + activated_mode = binding.name != NULL; return; } diff --git a/include/atoms.xmacro b/include/atoms.xmacro index 205efa17..41889eb1 100644 --- a/include/atoms.xmacro +++ b/include/atoms.xmacro @@ -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) diff --git a/include/commands.h b/include/commands.h index a517d83e..bbf45ba9 100644 --- a/include/commands.h +++ b/include/commands.h @@ -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 '. * @@ -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) []' + * + */ +void cmd_bar(I3_CMD, char *bar_type, char *bar_value, char *bar_id); + +/* + * Implementation of 'shmlog |toggle|on|off' + * + */ +void cmd_shmlog(I3_CMD, char *argument); + +/* + * Implementation of 'debuglog toggle|on|off' + * + */ +void cmd_debuglog(I3_CMD, char *argument); + #endif diff --git a/include/commands_parser.h b/include/commands_parser.h index fcc14ff5..37c4d4b1 100644 --- a/include/commands_parser.h +++ b/include/commands_parser.h @@ -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); diff --git a/include/con.h b/include/con.h index 62eb12d0..ec4ae352 100644 --- a/include/con.h +++ b/include/con.h @@ -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 diff --git a/include/config.h b/include/config.h index 7056af85..c7479b3a 100644 --- a/include/config.h +++ b/include/config.h @@ -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. diff --git a/include/config_directives.h b/include/config_directives.h index 1faaa973..f9b7a47f 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -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); diff --git a/include/data.h b/include/data.h index 1632efc7..ea1d3240 100644 --- a/include/data.h +++ b/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 diff --git a/include/ewmh.h b/include/ewmh.h index 07ef6614..c36eaeb0 100644 --- a/include/ewmh.h +++ b/include/ewmh.h @@ -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 diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 2a3321b5..6a50ccc8 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -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 diff --git a/include/libi3.h b/include/libi3.h index 53f3383d..b0141f1d 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -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 diff --git a/include/log.h b/include/log.h index 6fabeca3..c8e3c8ef 100644 --- a/include/log.h +++ b/include/log.h @@ -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. * diff --git a/include/randr.h b/include/randr.h index 8222b99a..dadcfd64 100644 --- a/include/randr.h +++ b/include/randr.h @@ -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. * diff --git a/include/shmlog.h b/include/shmlog.h index fd3f53eb..94da2bdb 100644 --- a/include/shmlog.h +++ b/include/shmlog.h @@ -14,6 +14,9 @@ #include #include +/* 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. * diff --git a/include/x.h b/include/x.h index c3d4ffc7..b6bb4a52 100644 --- a/include/x.h +++ b/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) * diff --git a/libi3/get_exe_path.c b/libi3/get_exe_path.c new file mode 100644 index 00000000..fca7ba02 --- /dev/null +++ b/libi3/get_exe_path.c @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include + +#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"; +} diff --git a/man/asciidoc.conf b/man/asciidoc.conf index 5f24d845..aa0639d5 100644 --- a/man/asciidoc.conf +++ b/man/asciidoc.conf @@ -7,7 +7,7 @@ template::[header-declarations] {mantitle} {manvolnum} i3 -4.5.1 +4.6 i3 Manual diff --git a/man/i3-msg.man b/man/i3-msg.man index 2f6c2aab..911fc995 100644 --- a/man/i3-msg.man +++ b/man/i3-msg.man @@ -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 diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index a4a01a88..2c640c65 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -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 |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) [] +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) diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 6960510d..fd13797b 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -49,7 +49,7 @@ state INITIAL: # We ignore comments and 'set' lines (variables). state IGNORE_LINE: - end, string + line -> INITIAL # floating_minimum_size x @@ -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: diff --git a/src/commands.c b/src/commands.c index 538e2dbc..ca9a332a 100644 --- a/src/commands.c +++ b/src/commands.c @@ -13,6 +13,7 @@ #include #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 '. * @@ -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 []' + * + */ +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 []' + * + */ +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) []' + * + */ +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 |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); +} diff --git a/src/commands_parser.c b/src/commands_parser.c index 93ee3889..4f04501c 100644 --- a/src/commands_parser.c +++ b/src/commands_parser.c @@ -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 diff --git a/src/con.c b/src/con.c index 1050513a..5b68481a 100644 --- a/src/con.c +++ b/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. */ diff --git a/src/config.c b/src/config.c index 595aa435..337b83a8 100644 --- a/src/config.c +++ b/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, diff --git a/src/config_directives.c b/src/config_directives.c index a7fa3500..0fac7006 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -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) diff --git a/src/config_parser.c b/src/config_parser.c index a3730233..bbd59a45 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -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') { diff --git a/src/ewmh.c b/src/ewmh.c index 9021e1c5..0298de3d 100644 --- a/src/ewmh.c +++ b/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); } diff --git a/src/floating.c b/src/floating.c index ea937472..643f204b 100644 --- a/src/floating.c +++ b/src/floating.c @@ -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; } diff --git a/src/handlers.c b/src/handlers.c index f4782ca9..133d85d0 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -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,22 +838,18 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat goto render_and_return; /* Check if we need to set proportional_* variables using the correct ratio */ + double aspect_ratio = 0.0; if ((width / height) < min_aspect) { - if (con->proportional_width != width || - con->proportional_height != (width / min_aspect)) { - con->proportional_width = width; - con->proportional_height = width / min_aspect; - changed = true; - } + aspect_ratio = min_aspect; } else if ((width / height) > max_aspect) { - if (con->proportional_width != width || - con->proportional_height != (width / max_aspect)) { - con->proportional_width = width; - con->proportional_height = width / max_aspect; - changed = true; - } + aspect_ratio = max_aspect; } else goto render_and_return; + if (fabs(con->aspect_ratio - aspect_ratio) > DBL_EPSILON) { + con->aspect_ratio = aspect_ratio; + changed = true; + } + render_and_return: if (changed) tree_render(); diff --git a/src/ipc.c b/src/ipc.c index cf253573..4c41465b 100644 --- a/src/ipc.c +++ b/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) - ystr("hide"); - else ystr("dock"); + switch (config->mode) { + case M_HIDE: + ystr("hide"); + break; + case M_INVISIBLE: + ystr("invisible"); + break; + case M_DOCK: + default: + ystr("dock"); + break; + } + + ystr("hidden_state"); + switch (config->hidden_state) { + case S_SHOW: + ystr("show"); + break; + case S_HIDE: + default: + ystr("hide"); + break; + } ystr("modifier"); switch (config->modifier) { diff --git a/src/load_layout.c b/src/load_layout.c index ca4c87ef..1b08f8c1 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -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; diff --git a/src/log.c b/src/log.c index 42e714ef..34e34532 100644 --- a/src/log.c +++ b/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. * diff --git a/src/main.c b/src/main.c index db3aca49..aac73883 100644 --- a/src/main.c +++ b/src/main.c @@ -19,6 +19,7 @@ #include #include #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)); diff --git a/src/manage.c b/src/manage.c index ae3cef0f..f5bd76ea 100644 --- a/src/manage.c +++ b/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; } diff --git a/src/randr.c b/src/randr.c index 10b085cb..1aef9c9c 100644 --- a/src/randr.c +++ b/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. * diff --git a/src/render.c b/src/render.c index c0a0fedf..1a7b8fd9 100644 --- a/src/render.c +++ b/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); } } } diff --git a/src/scratchpad.c b/src/scratchpad.c index a4d29505..ce3d9b9b 100644 --- a/src/scratchpad.c +++ b/src/scratchpad.c @@ -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. */ diff --git a/src/workspace.c b/src/workspace.c index 2c26b0ea..af9325f7 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -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 diff --git a/src/x.c b/src/x.c index fcb63c35..27d92aca 100644 --- a/src/x.c +++ b/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(); } /* diff --git a/testcases/lib/i3test.pm b/testcases/lib/i3test.pm index d6d71b23..9f3a6ea2 100644 --- a/testcases/lib/i3test.pm +++ b/testcases/lib/i3test.pm @@ -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]} : @_; diff --git a/testcases/t/113-urgent.t b/testcases/t/113-urgent.t index 02f98af5..2eb853de 100644 --- a/testcases/t/113-urgent.t +++ b/testcases/t/113-urgent.t @@ -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); } diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t index 52070d56..bbb89d93 100644 --- a/testcases/t/187-commands-parser.t +++ b/testcases/t/187-commands-parser.t @@ -144,7 +144,7 @@ is(parser_calls("\nworkspace test"), ################################################################################ is(parser_calls('unknown_literal'), - "ERROR: Expected one of these tokens: , '[', '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: , '[', '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'); diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index 6a49ff78..06588b11 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -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: , '#', '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: , '#', '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 ) ERROR: CONFIG: Line 1: bar { ERROR: CONFIG: Line 2: output LVDS-1 diff --git a/testcases/t/202-scratchpad-criteria.t b/testcases/t/202-scratchpad-criteria.t index 2603f65a..162b989a 100644 --- a/testcases/t/202-scratchpad-criteria.t +++ b/testcases/t/202-scratchpad-criteria.t @@ -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; diff --git a/testcases/t/206-fullscreen-scratchpad.t b/testcases/t/206-fullscreen-scratchpad.t new file mode 100644 index 00000000..53fca52a --- /dev/null +++ b/testcases/t/206-fullscreen-scratchpad.t @@ -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; diff --git a/testcases/t/207-shmlog.t b/testcases/t/207-shmlog.t new file mode 100644 index 00000000..b63a7499 --- /dev/null +++ b/testcases/t/207-shmlog.t @@ -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 = <', \$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; diff --git a/testcases/t/209-ewmh-net-workarea.t b/testcases/t/209-ewmh-net-workarea.t new file mode 100644 index 00000000..34309828 --- /dev/null +++ b/testcases/t/209-ewmh-net-workarea.t @@ -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 = <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; diff --git a/testcases/t/514-ipc-workspace-multi-monitor.t b/testcases/t/514-ipc-workspace-multi-monitor.t new file mode 100644 index 00000000..360bd426 --- /dev/null +++ b/testcases/t/514-ipc-workspace-multi-monitor.t @@ -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 = <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;