]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'master' into next
authorMichael Stapelberg <michael@stapelberg.de>
Tue, 23 Apr 2013 05:18:39 +0000 (07:18 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Tue, 23 Apr 2013 05:18:39 +0000 (07:18 +0200)
36 files changed:
debian/changelog
debian/rules
docs/hacking-howto
docs/i3bar-protocol
docs/multi-monitor
docs/testsuite
docs/userguide
docs/wsbar
i3-config-wizard/main.c
i3-dmenu-desktop
i3bar/include/child.h
i3bar/include/common.h
i3bar/src/child.c
i3bar/src/main.c
i3bar/src/parse_json_header.c
i3bar/src/xcb.c
include/con.h
include/data.h
include/randr.h
include/x.h
parser-specs/config.spec
src/commands_parser.c
src/con.c
src/config_parser.c
src/floating.c
src/ipc.c
src/load_layout.c
src/log.c
src/randr.c
src/render.c
src/scratchpad.c
src/workspace.c
src/x.c
testcases/t/201-config-parser.t
testcases/t/202-scratchpad-criteria.t
testcases/t/514-ipc-workspace-multi-monitor.t [new file with mode: 0644]

index ebdfbcef3ec7369c850fa23966f225ab4b92a40a..80a20e3c2d092e10066df87bb3a5db21d72d528e 100644 (file)
@@ -1,8 +1,20 @@
-i3-wm (4.4.1-0) unstable; urgency=low
+i3-wm (4.5.2-1) experimental; urgency=low
 
   * NOT YET RELEASED
 
- -- Michael Stapelberg <stapelberg@debian.org>  Wed, 12 Dec 2012 00:23:32 +0100
+ -- Michael Stapelberg <stapelberg@debian.org>  Mon, 18 Mar 2013 23:01:30 +0100
+
+i3-wm (4.5.1-1) experimental; urgency=low
+
+  * New upstream release
+
+ -- Michael Stapelberg <stapelberg@debian.org>  Mon, 18 Mar 2013 22:50:12 +0100
+
+i3-wm (4.5-1) experimental; urgency=low
+
+  * New upstream release
+
+ -- Michael Stapelberg <stapelberg@debian.org>  Tue, 12 Mar 2013 13:51:04 +0100
 
 i3-wm (4.4-1) experimental; urgency=low
 
index b119c47e84347a2c9a82230c4704cf151791e16d..3ae79dddf30ae59db5de256e4db3422b10e19f2d 100755 (executable)
@@ -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
index 633c277165f40b3a4d22315573367f0b2641f31b..73f8e88ab4ff22e4a31511bc4bea56a843db2336 100644 (file)
@@ -57,7 +57,7 @@ all, most users sooner or later tend to lay out their windows in a way which
 corresponds to tiling or stacking mode in i3. Therefore, why not let i3 do this
 for you? Certainly, 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
index 9225d97eab134f90b9567095f2cb2eea622ef3e0..bd8ea5365a4a8883d023f9e4c6e2b88d8251626c 100644 (file)
@@ -51,7 +51,7 @@ consists of a single JSON hash:
 
 *All features example*:
 ------------------------------
-{ "version": 1, "stop_signal": 10, "cont_signal": 12 }
+{ "version": 1, "stop_signal": 10, "cont_signal": 12, "click_events": true }
 ------------------------------
 
 (Note that before i3 v4.3 the precise format had to be +{"version":1}+,
@@ -110,6 +110,9 @@ cont_signal::
        Specify to i3bar the signal (as an integer)to send to continue your
        processing.
        The default value (if none is specified) is SIGCONT.
+click_events::
+       If specified and true i3bar will write a infinite array (same as above)
+       to your stdin.
 
 === Blocks in detail
 
@@ -210,3 +213,28 @@ An example of a block which uses all possible entries follows:
  "separator_block_width": 9
 }
 ------------------------------------------
+
+=== Click events
+
+If enabled i3bar will send you notifications if the user clicks on a block and
+looks like this:
+
+name::
+       Name of the block, if set
+instance::
+       Instance of the block, if set
+x, y::
+       X11 root window coordinates where the click occured
+button:
+       X11 button ID (for example 1 to 3 for left/middle/right mouse button)
+
+*Example*:
+------------------------------------------
+{
+ "name": "ethernet",
+ "instance": "eth0",
+ "button": 1,
+ "x": 1320,
+ "y": 1400
+}
+------------------------------------------
index a1fd6dc039273b454c2ddc9ddcd322c4726d754c..cafe569103b6c3f88a08615b697c4f176bb9c137 100644 (file)
@@ -1,15 +1,16 @@
 The multi-monitor situation
 ===========================
-Michael Stapelberg <michael+i3@stapelberg.de>
-September 2011
+Michael Stapelberg <michael@i3wm.org>
+April 2013
 
-…or: oh no, I have an nVidia graphics card!
+Please upgrade your nVidia driver to version 302.17 or newer and i3 will just
+work. This document is kept around for historic reasons only.
 
 == The quick fix
 
 If you are using the nVidia binary graphics driver (also known as 'blob')
-you need to use the +--force-xinerama+ flag (in your .xsession) when starting
-i3, like so:
+before version 302.17, you need to use the +--force-xinerama+ flag (in your
+.xsession) when starting i3, like so:
 
 .Example:
 ----------------------------------------------
index 9b7485bb82f39dd0f16d5f8cc953bd3d4e0942dc..6c3a36d9c78e9f253a8b0371eee4be55f0f29c31 100644 (file)
@@ -143,6 +143,16 @@ Result: PASS
 $ less latest/i3-log-for-04-floating.t
 ----------------------------------------
 
+If your attempt to run the tests with a bare call to ./complete-run.pl fails, try this:
+
+---------------------------------------------------
+$ ./complete-run.pl --parallel=1 --keep-xdummy-output
+---------------------------------------------------
+
+One common cause of failures is not having the X dummy server module
+installed.  Under Debian and Ubuntu this is the package
++xserver-xorg-video-dummy+.
+
 ==== IPC interface
 
 The testsuite makes extensive use of the IPC (Inter-Process Communication)
index a0f521c27b2613806742b35e494e15d351bea26f..bc105b346ad53900ae3b1b3a32cc411cf7ff0347 100644 (file)
@@ -1,7 +1,7 @@
 i3 User’s Guide
 ===============
 Michael Stapelberg <michael@i3wm.org>
-February 2013
+March 2013
 
 This document contains all the information you need to configure and use the i3
 window manager. If it does not, please check http://faq.i3wm.org/ first, then
@@ -26,8 +26,8 @@ are your homerow.
 == Using i3
 
 Throughout this guide, the keyword +$mod+ will be used to refer to the
-configured modifier. This is the Alt key (Mod1) by default, with windows (Mod4)
-being a popular alternative.
+configured modifier. This is the Alt key (Mod1) by default, with the Windows
+key (Mod4) being a popular alternative.
 
 === Opening terminals and moving around
 
@@ -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.
 
@@ -1223,7 +1224,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 +1320,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 +1330,7 @@ it floating (or tiling again) use +floating enable+ respectively +floating disab
 
 *Syntax*:
 --------------
-layout <tabbed|stacking>
+layout <default|tabbed|stacking|splitv|splith>
 layout toggle [split|all]
 --------------
 
index 9e379dd91f370e7d966e509020a35e049abd959a..6405880a580811c2520b191f7462212e51ad0804 100644 (file)
@@ -1,23 +1,18 @@
 External workspace bars
 =======================
-Michael Stapelberg <michael+i3@stapelberg.de>
-May 2010
+Michael Stapelberg <michael@i3wm.org>
+April 2013
 
-This document describes why the internal workspace bar is minimal and how an
-external workspace bar can be used. It explains the concepts using +i3-wsbar+
-as the reference implementation.
+i3 comes with i3bar by default, a simple bar that is sufficient for most users.
+In case you are unhappy with it, this document explains how to use a different,
+external workspace bar. Note that we do not provide support for external
+programs.
 
 == Internal and external bars
 
 The internal workspace bar of i3 is meant to be a reasonable default so that
 you can use i3 without having too much hassle when setting it up. It is quite
-simple and intended to stay this way. So, there is no way to display your own
-information in this bar (unlike dwm, wmii, awesome, …).
-
-We chose not to implement such a mechanism because that would be duplicating
-already existing functionality of tools such as dzen2, xmobar and similar.
-Instead, you should disable the internal bar and use an external workspace bar
-(which communicates with i3 through its IPC interface).
+simple and intended to stay this way.
 
 == dock mode
 
@@ -25,10 +20,10 @@ You typically want to see the same workspace bar on every workspace on a
 specific screen. Also, you 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.
index 54c8e0245478d11b97677d8d445e4518d7ee8510..ffc3df93011869e88561e523445fa9e267aeb68e 100644 (file)
@@ -13,7 +13,7 @@
 #endif
 
 /* For systems without getline, fall back to fgetln */
-#if defined(__APPLE__) || (defined(__FreeBSD__) && __FreeBSD_version < 800000)
+#if defined(__APPLE__)
 #define USE_FGETLN
 #elif defined(__FreeBSD__)
 /* Defining this macro before including stdio.h is necessary in order to have
index 65e99ec09b13c663df6820dbcbf293c588bb463d..cf5b41e0f8f97d943aebc44e3ec6824d6d49de47 100755 (executable)
@@ -346,7 +346,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} = '';
     }
 }
 
index d1c46890bc28e6534fdebf7987da54572c1e5bc0..dc244befe83080808246db148fb68ee8b0930ce8 100644 (file)
@@ -33,6 +33,12 @@ typedef struct {
      * The signal requested by the client to inform it of theun hidden state of i3bar
      */
     int cont_signal;
+
+    /**
+     * Enable click events
+     */
+    bool click_events;
+    bool click_events_init;
 } i3bar_child;
 
 /*
@@ -68,4 +74,10 @@ void stop_child(void);
  */
 void cont_child(void);
 
+/*
+ * Generates a click event, if enabled.
+ *
+ */
+void send_block_clicked(int button, const char *name, const char *instance, int x, int y);
+
 #endif
index 1365082f54591c0720712ce346ce7c37373ff36d..cb55e0d679d6f751ef5ed74d4a9bbc1c375ef829 100644 (file)
@@ -54,6 +54,10 @@ struct status_block {
     uint32_t x_offset;
     uint32_t x_append;
 
+    /* Optional */
+    char *name;
+    char *instance;
+
     TAILQ_ENTRY(status_block) blocks;
 };
 
index e5f4ea213cd10a5f16d9a2eb0a21343f45fdae63..4e5e49c928edf37f1e3ff450c0cb5b5c0f87ddaa 100644 (file)
@@ -21,6 +21,7 @@
 #include <yajl/yajl_common.h>
 #include <yajl/yajl_parse.h>
 #include <yajl/yajl_version.h>
+#include <yajl/yajl_gen.h>
 
 #include "common.h"
 
@@ -35,6 +36,9 @@ ev_child *child_sig;
 yajl_callbacks callbacks;
 yajl_handle parser;
 
+/* JSON generator for stdout */
+yajl_gen gen;
+
 typedef struct parser_ctx {
     /* True if one of the parsed blocks was urgent */
     bool has_urgent;
@@ -53,6 +57,8 @@ parser_ctx parser_context;
 struct statusline_head statusline_head = TAILQ_HEAD_INITIALIZER(statusline_head);
 char *statusline_buffer = NULL;
 
+int child_stdin;
+
 /*
  * Stop and free() the stdin- and sigchild-watchers
  *
@@ -85,6 +91,8 @@ static int stdin_start_array(void *context) {
         first = TAILQ_FIRST(&statusline_head);
         I3STRING_FREE(first->full_text);
         FREE(first->color);
+        FREE(first->name);
+        FREE(first->instance);
         TAILQ_REMOVE(&statusline_head, first, blocks);
         free(first);
     }
@@ -152,6 +160,18 @@ static int stdin_string(void *context, const unsigned char *val, unsigned int le
         ctx->block.min_width = (uint32_t)predict_text_width(text);
         i3string_free(text);
     }
+    if (strcasecmp(ctx->last_map_key, "name") == 0) {
+        char *copy = (char*)malloc(len+1);
+        strncpy(copy, (const char *)val, len);
+        copy[len] = 0;
+        ctx->block.name = copy;
+    }
+    if (strcasecmp(ctx->last_map_key, "instance") == 0) {
+        char *copy = (char*)malloc(len+1);
+        strncpy(copy, (const char *)val, len);
+        copy[len] = 0;
+        ctx->block.instance = copy;
+    }
     return 1;
 }
 
@@ -336,6 +356,21 @@ void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
     cleanup();
 }
 
+void child_write_output(void) {
+    if (child.click_events) {
+        const unsigned char *output;
+#if YAJL_MAJOR < 2
+        unsigned int size;
+#else
+        size_t size;
+#endif
+        yajl_gen_get_buf(gen, &output, &size);
+        write(child_stdin, output, size);
+        write(child_stdin, "\n", 1);
+        yajl_gen_clear(gen);
+    }
+}
+
 /*
  * Start a child-process with the specified command and reroute stdin.
  * We actually start a $SHELL to execute the command so we don't have to care
@@ -357,14 +392,22 @@ void start_child(char *command) {
     yajl_parser_config parse_conf = { 0, 0 };
 
     parser = yajl_alloc(&callbacks, &parse_conf, NULL, (void*)&parser_context);
+
+    gen = yajl_gen_alloc(NULL, NULL);
 #else
     parser = yajl_alloc(&callbacks, NULL, &parser_context);
+
+    gen = yajl_gen_alloc(NULL);
 #endif
 
     if (command != NULL) {
-        int fd[2];
-        if (pipe(fd) == -1)
-            err(EXIT_FAILURE, "pipe(fd)");
+        int pipe_in[2]; /* pipe we read from */
+        int pipe_out[2]; /* pipe we write to */
+
+        if (pipe(pipe_in) == -1)
+            err(EXIT_FAILURE, "pipe(pipe_in)");
+        if (pipe(pipe_out) == -1)
+            err(EXIT_FAILURE, "pipe(pipe_out)");
 
         child.pid = fork();
         switch (child.pid) {
@@ -372,10 +415,13 @@ void start_child(char *command) {
                 ELOG("Couldn't fork(): %s\n", strerror(errno));
                 exit(EXIT_FAILURE);
             case 0:
-                /* Child-process. Reroute stdout and start shell */
-                close(fd[0]);
+                /* Child-process. Reroute streams and start shell */
+
+                close(pipe_in[0]);
+                close(pipe_out[1]);
 
-                dup2(fd[1], STDOUT_FILENO);
+                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.
  *
index ea6056470c5f05381fc4c1e9efffddb1e3abbb7b..c62f7b3c96d74c8a1d0a802caf4ac9aeb455fcd5 100644 (file)
@@ -53,12 +53,12 @@ char *expand_path(char *path) {
 }
 
 void print_usage(char *elf_name) {
-    printf("Usage: %s [-b bar_id] [-s sock_path] [-h] [-v]\n", elf_name);
+    printf("Usage: %s -b bar_id [-s sock_path] [-h] [-v]\n", elf_name);
     printf("\n");
-    printf("--bar_id <bar_id>\tBar ID for which to get the configuration\n");
-    printf("-s <sock_path>\tConnect to i3 via <sock_path>\n");
-    printf("-h\t\tDisplay this help-message and exit\n");
-    printf("-v\t\tDisplay version number and exit\n");
+    printf("-b, --bar_id  <bar_id>\tBar ID for which to get the configuration\n");
+    printf("-s, --socket  <sock_path>\tConnect to i3 via <sock_path>\n");
+    printf("-h, --help    Display this help-message and exit\n");
+    printf("-v, --version Display version number and exit\n");
     printf("\n");
     printf(" PLEASE NOTE that i3bar will be automatically started by i3\n"
            " as soon as there is a 'bar' configuration block in your\n"
@@ -97,13 +97,13 @@ int main(int argc, char **argv) {
 
     static struct option long_opt[] = {
         { "socket",               required_argument, 0, 's' },
-        { "bar_id",               required_argument, 0, 0 },
+        { "bar_id",               required_argument, 0, 'b' },
         { "help",                 no_argument,       0, 'h' },
         { "version",              no_argument,       0, 'v' },
         { NULL,                   0,                 0, 0}
     };
 
-    while ((opt = getopt_long(argc, argv, "s:hv", long_opt, &option_index)) != -1) {
+    while ((opt = getopt_long(argc, argv, "b:s:hv", long_opt, &option_index)) != -1) {
         switch (opt) {
             case 's':
                 socket_path = expand_path(optarg);
@@ -112,11 +112,8 @@ int main(int argc, char **argv) {
                 printf("i3bar version " I3_VERSION " © 2010-2011 Axel Wagner and contributors\n");
                 exit(EXIT_SUCCESS);
                 break;
-            case 0:
-                if (!strcmp(long_opt[option_index].name, "bar_id")) {
-                    FREE(config.bar_id);
-                    config.bar_id = sstrdup(optarg);
-                }
+            case 'b':
+                config.bar_id = sstrdup(optarg);
                 break;
             default:
                 print_usage(argv[0]);
index 80ec5af8f78d4de61bda169e442b5552ac57631e..c09e0f499aaf107bedc0f531dd391483b0dd2622 100644 (file)
@@ -31,6 +31,7 @@ static enum {
     KEY_VERSION,
     KEY_STOP_SIGNAL,
     KEY_CONT_SIGNAL,
+    KEY_CLICK_EVENTS,
     NO_KEY
 } current_key;
 
@@ -54,6 +55,21 @@ static int header_integer(void *ctx, long val) {
         default:
             break;
     }
+
+    return 1;
+}
+
+static int header_boolean(void *ctx, int val) {
+    i3bar_child *child = ctx;
+
+    switch (current_key) {
+        case KEY_CLICK_EVENTS:
+            child->click_events = val;
+            break;
+        default:
+            break;
+    }
+
     return 1;
 }
 
@@ -71,13 +87,15 @@ static int header_map_key(void *ctx, const unsigned char *stringval, unsigned in
         current_key = KEY_STOP_SIGNAL;
     } else if (CHECK_KEY("cont_signal")) {
         current_key = KEY_CONT_SIGNAL;
+    } else if (CHECK_KEY("click_events")) {
+        current_key = KEY_CLICK_EVENTS;
     }
     return 1;
 }
 
 static yajl_callbacks version_callbacks = {
     NULL, /* null */
-    NULL, /* boolean */
+    &header_boolean, /* boolean */
     &header_integer,
     NULL, /* double */
     NULL, /* number */
index 3cdf6ed7bebc5b49d530a4c6ef8672203569b4a8..b09f63d210de56bc70b9eba0b1b642cbb1126730 100644 (file)
@@ -80,6 +80,9 @@ ev_io      *xkb_io;
 /* The name of current binding mode */
 static mode binding;
 
+/* Indicates whether a new binding mode was recently activated */
+bool activated_mode = false;
+
 /* The parsed colors */
 struct xcb_colors_t {
     uint32_t bar_fg;
@@ -162,7 +165,7 @@ void refresh_statusline(void) {
         realloc_sl_buffer();
 
     /* Clear the statusline pixmap. */
-    xcb_rectangle_t rect = { 0, 0, root_screen->width_in_pixels, font.height };
+    xcb_rectangle_t rect = { 0, 0, root_screen->width_in_pixels, font.height + 2 };
     xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect);
 
     /* Draw the text of each block. */
@@ -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;
     }
@@ -1573,7 +1612,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,6 +1711,8 @@ 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;
@@ -1719,5 +1760,6 @@ void redraw_bars(void) {
 void set_current_mode(struct mode *current) {
     I3STRING_FREE(binding.name);
     binding = *current;
+    activated_mode = binding.name != NULL;
     return;
 }
index 62eb12d0d7584255c5a93aff2000fa07b4debffb..7c60211515f9ffe878fc0a6a3d6568ac7abb4675 100644 (file)
 
 /**
  * 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);
index 1632efc76b8e7c6c21082fd09934930c05f544e7..13acc063284669a54013fc7716d23fc883c0b8d8 100644 (file)
@@ -584,6 +584,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
index 8222b99ac886520b8b1a5c7763a3e6ea0895d1f8..dadcfd64912bf55836d3486014582e4ef87da9e1 100644 (file)
@@ -87,6 +87,16 @@ Output *get_output_by_name(const char *name);
  */
 Output *get_output_containing(int x, int y);
 
+/*
+ * In contained_by_output, we check if any active output contains part of the container.
+ * We do this by checking if the output rect is intersected by the Rect.
+ * This is the 2-dimensional counterpart of get_output_containing.
+ * Since we don't actually need the outputs intersected by the given Rect (There could
+ * be many), we just return true or false for convenience.
+ *
+ */
+bool contained_by_output(Rect rect);
+
 /**
  * Gets the output which is the next one in the given direction.
  *
index c3d4ffc7cc29f5bac0194aa9ca401f469ee70e8e..b712c78692646ab961d52c420cbd21619f60f115 100644 (file)
@@ -93,8 +93,12 @@ void x_push_changes(Con *con);
  * Raises the specified container in the internal stack of X windows. The
  * next call to x_push_changes() will make the change visible in X11.
  *
+ * If above_all is true, the X11 window will be raised to the top
+ * of the stack. This should only be used for precisely one fullscreen
+ * window per output.
+ *
  */
-void x_raise_con(Con *con);
+void x_raise_con(Con *con, bool above_all);
 
 /**
  * Sets the WM_NAME property (so, no UTF8, but used only for debugging anyways)
index 6960510d4d386a731b92a3a997befeaf74f9c44c..c6328a0eae0e1b70f063d2c46ce52962f8d56af9 100644 (file)
@@ -49,7 +49,7 @@ state INITIAL:
 
 # We ignore comments and 'set' lines (variables).
 state IGNORE_LINE:
-  end, string
+  line
       -> INITIAL
 
 # floating_minimum_size <width> x <height>
@@ -311,7 +311,7 @@ state MODE:
 
 # We ignore comments and 'set' lines (variables).
 state MODE_IGNORE_LINE:
-  end, string
+  line
       -> MODE
 
 state MODE_BINDING:
@@ -362,7 +362,7 @@ state BAR:
 
 # We ignore comments and 'set' lines (variables).
 state BAR_IGNORE_LINE:
-  end, string
+  line
       -> BAR
 
 state BAR_BAR_COMMAND:
@@ -428,7 +428,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:
index 93ee38896eb96fb9f9cd63cb913875562583e904..4f04501c21408d02930e43c4285d9c20f03cc78e 100644 (file)
@@ -13,7 +13,7 @@
  * We use a hand-written parser instead of lex/yacc because our commands are
  * easy for humans, not for computers. Thus, 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
index 1050513ae593a76e8981e8d93055be0a07f550bf..79872f9251d64fa7cd4aabd07b8a1be2a4d55bb5 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -44,11 +44,10 @@ 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);
@@ -56,6 +55,10 @@ Con *con_new(Con *parent, i3Window *window) {
     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 +69,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 +81,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
index 1cdc7acba8cd745fd1ba4803dbb41233996d5061..f197c39d8a62274b900104e72765833185e04d43 100644 (file)
@@ -446,6 +446,16 @@ struct ConfigResult *parse_config(const char *input, struct context *context) {
                 }
             }
 
+            if (strcmp(token->name, "line") == 0) {
+               while (*walk != '\0' && *walk != '\n' && *walk != '\r')
+                  walk++;
+               next_state(token);
+               token_handled = true;
+               linecnt++;
+               walk++;
+               break;
+            }
+
             if (strcmp(token->name, "end") == 0) {
                 //printf("checking for end: *%s*\n", walk);
                 if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
index 49a4122ec2253ea67229e8b11c5efa92060395a8..b7bf3ff65c9537a25765ecb5047aa615595fa2a9 100644 (file)
@@ -659,11 +659,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;
     }
index cf2535733e46123fe8aa7a0540d6c18b052a4441..8161b1d27603e940e82a6bf213f9d15d2cc37159 100644 (file)
--- 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);
 }
 
index ca4c87ef827b1537735a1c46c407e488ef7362a9..1b08f8c17eae72821d8b69499a92f8039b8d7e88 100644 (file)
@@ -51,12 +51,12 @@ static int json_start_map(void *ctx) {
             if (last_key && strcasecmp(last_key, "floating_nodes") == 0) {
                 DLOG("New floating_node\n");
                 Con *ws = con_get_workspace(json_node);
-                json_node = con_new(NULL, NULL);
+                json_node = con_new_skeleton(NULL, NULL);
                 json_node->parent = ws;
                 DLOG("Parent is workspace = %p\n", ws);
             } else {
                 Con *parent = json_node;
-                json_node = con_new(NULL, NULL);
+                json_node = con_new_skeleton(NULL, NULL);
                 json_node->parent = parent;
             }
         }
@@ -69,6 +69,8 @@ static int json_end_map(void *ctx) {
     if (!parsing_swallows && !parsing_rect && !parsing_window_rect && !parsing_geometry) {
         LOG("attaching\n");
         con_attach(json_node, json_node->parent, true);
+        LOG("Creating window\n");
+        x_con_init(json_node, json_node->depth);
         json_node = json_node->parent;
     }
     if (parsing_rect)
@@ -277,6 +279,9 @@ static int json_int(void *ctx, long val) {
     if (strcasecmp(last_key, "current_border_width") == 0)
         json_node->current_border_width = val;
 
+    if (strcasecmp(last_key, "depth") == 0)
+        json_node->depth = val;
+
     if (!parsing_swallows && strcasecmp(last_key, "id") == 0)
         json_node->old_id = val;
 
index 42e714ef0bcee0f65445a557c15f7019d5453707..0269fb21ff9b7078a6f94ecd787c7e4a2dd4a1f6 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -81,11 +81,11 @@ 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");
             }
         }
     }
@@ -107,25 +107,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) {
             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(errno));
             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));
+            shm_unlink(shmlogname);
+            fprintf(stderr, "Could not mmap SHM segment for the i3 log: %s\n", strerror(errno));
             logbuffer = NULL;
             return;
         }
@@ -138,7 +142,7 @@ 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);
index 10b085cbb9825006a58ad44bbefd3e7bc1c910c6..1aef9c9c620c59af800c4a1e972f6dd6cbb0aecb 100644 (file)
@@ -92,6 +92,31 @@ Output *get_output_containing(int x, int y) {
     return NULL;
 }
 
+/*
+ * In contained_by_output, we check if any active output contains part of the container.
+ * We do this by checking if the output rect is intersected by the Rect.
+ * This is the 2-dimensional counterpart of get_output_containing.
+ * Since we don't actually need the outputs intersected by the given Rect (There could
+ * be many), we just return true or false for convenience.
+ *
+ */
+bool contained_by_output(Rect rect){
+    Output *output;
+    int lx = rect.x, uy = rect.y;
+    int rx = rect.x + rect.width, by = rect.y + rect.height;
+    TAILQ_FOREACH(output, &outputs, outputs) {
+        if (!output->active)
+            continue;
+        DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
+                        rect.x, rect.y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
+        if (rx >= (int)output->rect.x && lx <= (int)(output->rect.x + output->rect.width) &&
+            by >= (int)output->rect.y && uy <= (int)(output->rect.y + output->rect.height))
+            return true;
+    }
+    return false;
+
+}
+
 /*
  * Like get_output_next with close_far == CLOSEST_OUTPUT, but wraps.
  *
index c0a0fedfe4848b70c6b3c229fbe760115cd2b67b..0142ae9718f3fbd55771a222d9b08bab5d01ce42 100644 (file)
@@ -70,7 +70,7 @@ static void render_l_output(Con *con) {
     Con *fullscreen = con_get_fullscreen_con(ws, CF_OUTPUT);
     if (fullscreen) {
         fullscreen->rect = con->rect;
-        x_raise_con(fullscreen);
+        x_raise_con(fullscreen, true);
         render_con(fullscreen, true);
         return;
     }
@@ -110,7 +110,7 @@ static void render_l_output(Con *con) {
 
         DLOG("child at (%d, %d) with (%d x %d)\n",
                 child->rect.x, child->rect.y, child->rect.width, child->rect.height);
-        x_raise_con(child);
+        x_raise_con(child, false);
         render_con(child, false);
     }
 }
@@ -207,7 +207,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 +298,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 +407,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 +415,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 +429,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);
     }
     }
 }
index a4d2950519619bd563f804c36854c666f6cfcbad..9997f17df2037e9a981e2ae1a6375854391a53c9 100644 (file)
@@ -88,12 +88,23 @@ void scratchpad_show(Con *con) {
         con_toggle_fullscreen(focused, CF_OUTPUT);
     }
 
+    /* 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 this was 'scratchpad show' without criteria, we check if there is a
      * unfocused scratchpad on the current workspace and focus it */
     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 +123,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 +134,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;
     }
 
index 5a0913bfcc77852c1084fe2f2908097000714b8c..17f5ac33d27b3d4fe2ce2d7bf8404e1f920273c9 100644 (file)
@@ -442,7 +442,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 fcb63c35bdef19765540800ecb610011ced1941a..ae6e8a600cc7f1f9044ce7d5848137baa6d63bf9 100644 (file)
--- 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;
@@ -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);
 }
index 6a49ff7844eddebfda36bec143ac450e8a83bec4..151103d7503d1f4770c46436445a66edc46de924 100644 (file)
@@ -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
index 2603f65af7e6328b7153e50914329d4e7e95563f..5ba8bfdddcdbd5d74067df85ab87a16fa4e9518d 100644 (file)
 # Verifies that using criteria to address scratchpad windows works.
 use i3test;
 
-################################################################################
+#####################################################################
 # 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);
@@ -47,12 +56,102 @@ 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/514-ipc-workspace-multi-monitor.t b/testcases/t/514-ipc-workspace-multi-monitor.t
new file mode 100644 (file)
index 0000000..360bd42
--- /dev/null
@@ -0,0 +1,72 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+# 
+# Ticket: #990
+# Bug still in: 4.5.1-23-g82b5978
+
+use i3test i3_autostart => 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+fake-outputs 1024x768+0+0,1024x768+1024+0
+EOT
+
+my $pid = launch_with_config($config);
+
+my $i3 = i3(get_socket_path());
+
+$i3->connect()->recv;
+
+################################
+# Workspaces requests and events
+################################
+
+my $focused = get_ws(focused_ws());
+
+# Events
+
+# We are switching to an empty workpspace on the output to the right from an empty workspace on the output on the left, so we expect
+# to receive "init", "focus", and "empty".
+my $focus = AnyEvent->condvar;
+$i3->subscribe({
+    workspace => sub {
+        my ($event) = @_;
+        if ($event->{change} eq 'focus') {
+            # Check that we have the old and new workspace
+            $focus->send(
+                $event->{current}->{name} == '2' &&
+                $event->{old}->{name} == $focused->{name}
+            );
+        }
+    }
+})->recv;
+
+cmd 'focus output right';
+
+my $t;
+$t = AnyEvent->timer(
+    after => 0.5,
+    cb => sub {
+        $focus->send(0);
+    }
+);
+
+ok($focus->recv, 'Workspace "focus" event received');
+
+exit_gracefully($pid);
+
+done_testing;