]> git.sur5r.net Git - i3/i3/commitdiff
Merge pull request #2496 from Airblader/feature-917
authorMichael Stapelberg <stapelberg@users.noreply.github.com>
Mon, 15 May 2017 19:35:10 +0000 (21:35 +0200)
committerGitHub <noreply@github.com>
Mon, 15 May 2017 19:35:10 +0000 (21:35 +0200)
Implement 'swap' command.

17 files changed:
docs/ipc
docs/userguide
i3-dmenu-desktop
i3-nagbar/main.c
i3bar/src/ipc.c
include/bindings.h
include/config_directives.h
include/data.h
man/i3-nagbar.man
parser-specs/config.spec
src/bindings.c
src/click.c
src/con.c
src/config_directives.c
src/tree.c
testcases/t/167-workspace_layout.t
testcases/t/201-config-parser.t

index 026e434d9c1b8c5d057d4fe03c5d541f906ab4d9..2e223801fd12f3ec19686f861e4b508b1fe9c51e 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -264,7 +264,7 @@ rect (map)::
    "y": 0,
    "width": 1280,
    "height": 1024
-  },
+  }
  }
 ]
 -------------------
@@ -392,7 +392,7 @@ JSON dump:
         "y": 0,
         "width": 1280,
         "height": 0
-       },
+       }
       },
 
       {
index 9e0a6f009e6448fee5e34836826de1e06f511833..4eb8c7f59c36b39370de49f6aa8b963bde030d81 100644 (file)
@@ -297,6 +297,15 @@ keyboard layout. To start the wizard, use the command +i3-config-wizard+.
 Please note that you must not have +~/.i3/config+, otherwise the wizard will
 exit.
 
+Since i3 4.0, a new configuration format is used. i3 will try to automatically
+detect the format version of a config file based on a few different keywords,
+but if you want to make sure that your config is read with the new format,
+include the following line in your config file:
+
+---------------------
+# i3 config file (v4)
+---------------------
+
 === Comments
 
 It is possible and recommended to use comments in your configuration file to
@@ -412,9 +421,9 @@ button in the scope of the clicked container (see <<command_criteria>>). You
 can configure mouse bindings in a similar way to key bindings.
 
 *Syntax*:
--------------------------------------------------------------------------------
-bindsym [--release] [--border] [--whole-window] [<Modifiers>+]button<n> command
--------------------------------------------------------------------------------
+----------------------------------------------------------------------------------------------------
+bindsym [--release] [--border] [--whole-window] [--exclude-titlebar] [<Modifiers>+]button<n> command
+----------------------------------------------------------------------------------------------------
 
 By default, the binding will only run when you click on the titlebar of the
 window. If the +--release+ flag is given, it will run when the mouse button
@@ -424,6 +433,9 @@ If the +--whole-window+ flag is given, the binding will also run when any part
 of the window is clicked, with the exception of the border. To have a bind run
 when the border is clicked, specify the +--border+ flag.
 
+If the +--exclude-titlebar+ flag is given, the titlebar will not be considered
+for the keybinding.
+
 *Examples*:
 --------------------------------
 # The middle button over a titlebar kills the window
index 3b81cb20ce008097b9f3dbd412f1fd08a6ea2c87..1d29a69f21bb71d771a58988e0557f5b15c7ce10 100755 (executable)
@@ -282,7 +282,31 @@ for my $app (keys %apps) {
     }
 
     if ((scalar grep { $_ eq 'command' } @entry_types) > 0) {
-        my ($command) = split(' ', $apps{$app}->{Exec});
+        my $command = $apps{$app}->{Exec};
+
+        # Handle escape sequences (should be done for all string values, but does
+        # matter here).
+        my %escapes = (
+            '\\s' => ' ',
+            '\\n' => '\n',
+            '\\t' => '\t',
+            '\\r' => '\r',
+            '\\\\' => '\\',
+        );
+        $command =~ s/(\\[sntr\\])/$escapes{$1}/go;
+
+        # Extract executable
+        if ($command =~ m/^\s*([^\s\"]+)(?:\s|$)/) {
+            # No quotes
+            $command = $1;
+        } elsif ($command =~ m/^\s*\"([^\"\\]*(?:\\.[^\"\\]*)*)\"(?:\s|$)/) {
+            # Quoted, remove quotes and fix escaped characters
+            $command = $1;
+            $command =~ s/\\([\"\`\$\\])/$1/g;
+        } else {
+            # Invalid quotes, fallback to whitespace
+            ($command) = split(' ', $command);
+        }
 
         # Don’t add “geany” if “Geany” is already present.
         my @keys = map { lc } keys %choices;
index 7d38f73140d741c635aa0de25bd0e5809d94df75..6ec4294f0078adff6b348ee373cac913b4319a44 100644 (file)
@@ -50,6 +50,7 @@ static char *argv0 = NULL;
 typedef struct {
     i3String *label;
     char *action;
+    bool exec_in_terminal;
     int16_t x;
     uint16_t width;
 } button_t;
@@ -123,35 +124,7 @@ static void start_application(const char *command) {
     wait(0);
 }
 
-static button_t *get_button_at(int16_t x, int16_t y) {
-    for (int c = 0; c < buttoncnt; c++)
-        if (x >= (buttons[c].x) && x <= (buttons[c].x + buttons[c].width))
-            return &buttons[c];
-
-    return NULL;
-}
-
-static void handle_button_press(xcb_connection_t *conn, xcb_button_press_event_t *event) {
-    printf("button pressed on x = %d, y = %d\n",
-           event->event_x, event->event_y);
-    /* TODO: set a flag for the button, re-render */
-}
-
-/*
- * Called when the user releases the mouse button. Checks whether the
- * coordinates are over a button and executes the appropriate action.
- *
- */
-static void handle_button_release(xcb_connection_t *conn, xcb_button_release_event_t *event) {
-    printf("button released on x = %d, y = %d\n",
-           event->event_x, event->event_y);
-    /* If the user hits the close button, we exit(0) */
-    if (event->event_x >= btn_close.x && event->event_x < btn_close.x + btn_close.width)
-        exit(0);
-    button_t *button = get_button_at(event->event_x, event->event_y);
-    if (!button)
-        return;
-
+static void execute_in_terminal(const char *command) {
     /* We need to create a custom script containing our actual command
      * since not every terminal emulator which is contained in
      * i3-sensible-terminal supports -e with multiple arguments (and not
@@ -172,7 +145,7 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
         warn("Could not fdopen() temporary script to store the nagbar command");
         return;
     }
-    fprintf(script, "#!/bin/sh\nrm %s\n%s", script_path, button->action);
+    fprintf(script, "#!/bin/sh\nrm %s\n%s", script_path, command);
     /* Also closes fd */
     fclose(script);
 
@@ -194,6 +167,43 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
     free(terminal_cmd);
     free(script_path);
     free(exe_path);
+}
+
+static button_t *get_button_at(int16_t x, int16_t y) {
+    for (int c = 0; c < buttoncnt; c++)
+        if (x >= (buttons[c].x) && x <= (buttons[c].x + buttons[c].width))
+            return &buttons[c];
+
+    return NULL;
+}
+
+static void handle_button_press(xcb_connection_t *conn, xcb_button_press_event_t *event) {
+    printf("button pressed on x = %d, y = %d\n",
+           event->event_x, event->event_y);
+    /* TODO: set a flag for the button, re-render */
+}
+
+/*
+ * Called when the user releases the mouse button. Checks whether the
+ * coordinates are over a button and executes the appropriate action.
+ *
+ */
+static void handle_button_release(xcb_connection_t *conn, xcb_button_release_event_t *event) {
+    printf("button released on x = %d, y = %d\n",
+           event->event_x, event->event_y);
+    /* If the user hits the close button, we exit(0) */
+    if (event->event_x >= btn_close.x && event->event_x < btn_close.x + btn_close.width)
+        exit(0);
+    button_t *button = get_button_at(event->event_x, event->event_y);
+    if (!button) {
+        return;
+    }
+
+    if (button->exec_in_terminal) {
+        execute_in_terminal(button->action);
+    } else {
+        start_application(button->action);
+    }
 
     /* TODO: unset flag, re-render */
 }
@@ -358,12 +368,13 @@ int main(int argc, char *argv[]) {
         {"version", no_argument, 0, 'v'},
         {"font", required_argument, 0, 'f'},
         {"button", required_argument, 0, 'b'},
+        {"button-sh", required_argument, 0, 'B'},
         {"help", no_argument, 0, 'h'},
         {"message", required_argument, 0, 'm'},
         {"type", required_argument, 0, 't'},
         {0, 0, 0, 0}};
 
-    char *options_string = "b:f:m:t:vh";
+    char *options_string = "B:b:f:m:t:vh";
 
     prompt = i3string_from_utf8("Please do not run this program.");
 
@@ -385,15 +396,26 @@ int main(int argc, char *argv[]) {
                 break;
             case 'h':
                 printf("i3-nagbar " I3_VERSION "\n");
-                printf("i3-nagbar [-m <message>] [-b <button> <action>] [-t warning|error] [-f <font>] [-v]\n");
+                printf("i3-nagbar [-m <message>] [-b <button> <terminal-action>] "
+                       "[-B <button> <shell-action> [-t warning|error] [-f <font>] [-v]\n");
                 return 0;
+            /* falls through */
+            case 'B':
             case 'b':
                 buttons = srealloc(buttons, sizeof(button_t) * (buttoncnt + 1));
                 buttons[buttoncnt].label = i3string_from_utf8(optarg);
                 buttons[buttoncnt].action = argv[optind];
-                printf("button with label *%s* and action *%s*\n",
-                       i3string_as_utf8(buttons[buttoncnt].label),
-                       buttons[buttoncnt].action);
+                if (o == 'b') {
+                    buttons[buttoncnt].exec_in_terminal = true;
+                    printf("button with label *%s* and terminal action *%s*\n",
+                           i3string_as_utf8(buttons[buttoncnt].label),
+                           buttons[buttoncnt].action);
+                } else {
+                    buttons[buttoncnt].exec_in_terminal = false;
+                    printf("button with label *%s* and shell action *%s*\n",
+                           i3string_as_utf8(buttons[buttoncnt].label),
+                           buttons[buttoncnt].action);
+                }
                 buttoncnt++;
                 printf("now %d buttons\n", buttoncnt);
                 if (optind < argc)
index 459684ef32dc70afd3f065ae01a41d0b12f12a92..042e230a522ce3eeb43f535993f3fbe16ba978ed 100644 (file)
@@ -79,6 +79,10 @@ void got_output_reply(char *reply) {
         kick_tray_clients(o_walk);
     }
 
+    if (!config.disable_ws) {
+        i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
+    }
+
     draw_bars(false);
 }
 
index 0fcc4df4c94f390d43648503c65fd27c95b1e303..df3c32a5a88c8edea7048a39fb7b8d40aefb1883 100644 (file)
@@ -27,7 +27,8 @@ extern const char *DEFAULT_BINDING_MODE;
  */
 Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code,
                            const char *release, const char *border, const char *whole_window,
-                           const char *command, const char *mode, bool pango_markup);
+                           const char *exclude_titlebar, const char *command, const char *mode,
+                           bool pango_markup);
 
 /**
  * Grab the bound keys (tell X to send us keypress events for those keycodes)
index 0bf521686855fed4b3dd4540017e96979e600e33..f35666f3612000c21c4837c592a5b22261bf6d33 100644 (file)
@@ -67,10 +67,10 @@ CFGFUN(color_single, const char *colorclass, const char *color);
 CFGFUN(floating_modifier, const char *modifiers);
 CFGFUN(new_window, const char *windowtype, const char *border, const long width);
 CFGFUN(workspace, const char *workspace, const char *output);
-CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command);
+CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command);
 
 CFGFUN(enter_mode, const char *pango_markup, const char *mode);
-CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command);
+CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command);
 
 CFGFUN(bar_font, const char *font);
 CFGFUN(bar_separator_symbol, const char *separator);
index 3d67e315733893a85be3ef1124f6d145eb76cea8..69a79ade09faabf22bca2ddbbebef2243a65c14c 100644 (file)
@@ -298,6 +298,10 @@ struct Binding {
      * title bar (default). */
     bool whole_window;
 
+    /** If this is true for a mouse binding, the binding should only be
+     * executed if the button press was not on the titlebar. */
+    bool exclude_titlebar;
+
     /** Keycode to bind */
     uint32_t keycode;
 
index 77fdd80b504ed5dd24c0f345ea1eefb2abfd086b..7dd3a238130e66ea09719d37aaa2365cfc4980f1 100644 (file)
@@ -9,7 +9,7 @@ i3-nagbar - displays an error bar on top of your screen
 
 == SYNOPSIS
 
-i3-nagbar [-m <message>] [-b <button> <action>] [-t warning|error] [-f <font>] [-v]
+i3-nagbar [-m <message>] [-b <button> <terminal-action>] [-B <button> <shell-action>] [-t warning|error] [-f <font>] [-v]
 
 == OPTIONS
 
@@ -29,9 +29,13 @@ Display 'message' as text on the left of the i3-nagbar.
 *-f, --font* 'font'::
 Select font that is being used.
 
-*-b, --button* 'button' 'action'::
-Create a button with text 'button'. The 'action' are the shell commands that
-will be executed by this button. Multiple buttons can be defined.
+*-b, --button* 'button' 'terminal-action'::
+Adds a button labelled 'button' to the bar. When pressed, the command given
+in 'terminal-action' is executed inside a terminal emulator, via i3-sensible-terminal(1).
+Multiple buttons can be defined.
+
+*-B, --button-sh* 'button' 'shell-action'::
+Same as *--button*, except that the command given in 'shell-action' is executed directly by the shell.
 
 == DESCRIPTION
 
@@ -49,7 +53,7 @@ i3-nagbar -m 'You have an error in your i3 config file!' \
 
 == SEE ALSO
 
-i3(1)
+i3(1), i3-sensible-terminal(1)
 
 == AUTHOR
 
index 19e2d21a09166290b1251422a3ec01bcc30680f9..53828221236fb621d56d8fb1d76c6f028c293c15 100644 (file)
@@ -321,6 +321,8 @@ state BINDING:
       ->
   whole_window = '--whole-window'
       ->
+  exclude_titlebar = '--exclude-titlebar'
+      ->
   modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod'
       ->
   '+'
@@ -335,8 +337,10 @@ state BINDCOMMAND:
       ->
   whole_window = '--whole-window'
       ->
+  exclude_titlebar = '--exclude-titlebar'
+      ->
   command = string
-      -> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $command)
+      -> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command)
 
 ################################################################################
 # Mode configuration
@@ -376,6 +380,8 @@ state MODE_BINDING:
       ->
   whole_window = '--whole-window'
       ->
+  exclude_titlebar = '--exclude-titlebar'
+      ->
   modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod'
       ->
   '+'
@@ -390,8 +396,10 @@ state MODE_BINDCOMMAND:
       ->
   whole_window = '--whole-window'
       ->
+  exclude_titlebar = '--exclude-titlebar'
+      ->
   command = string
-      -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $command); MODE
+      -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command); MODE
 
 ################################################################################
 # Bar configuration (i3bar)
index f91f795900135dceb4b6399de4e876978c630c5a..bf04c4320be42de4ea63b479129a46489acc6657 100644 (file)
@@ -56,12 +56,14 @@ static struct Mode *mode_from_name(const char *name, bool pango_markup) {
  */
 Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code,
                            const char *release, const char *border, const char *whole_window,
-                           const char *command, const char *modename, bool pango_markup) {
+                           const char *exclude_titlebar, const char *command, const char *modename,
+                           bool pango_markup) {
     Binding *new_binding = scalloc(1, sizeof(Binding));
     DLOG("Binding %p bindtype %s, modifiers %s, input code %s, release %s\n", new_binding, bindtype, modifiers, input_code, release);
     new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS);
     new_binding->border = (border != NULL);
     new_binding->whole_window = (whole_window != NULL);
+    new_binding->exclude_titlebar = (exclude_titlebar != NULL);
     if (strcmp(bindtype, "bindsym") == 0) {
         new_binding->input_type = (strncasecmp(input_code, "button", (sizeof("button") - 1)) == 0
                                        ? B_MOUSE
index e989b88d987aa15877e565e9e743e991b0367cca..e5cdc8b2f39e82dbe880fb184337f3f8363380f2 100644 (file)
@@ -186,7 +186,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
     if (dest == CLICK_DECORATION || dest == CLICK_INSIDE || dest == CLICK_BORDER) {
         Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event);
 
-        if (bind != NULL && (dest == CLICK_DECORATION ||
+        if (bind != NULL && ((dest == CLICK_DECORATION && !bind->exclude_titlebar) ||
                              (dest == CLICK_INSIDE && bind->whole_window) ||
                              (dest == CLICK_BORDER && bind->border))) {
             CommandResult *result = run_binding(bind, con);
index cec3a29b8377ac226eeaec38d846efe711815932..136ce5d2a8f7a007d29a4e46f8287bdb80128dc8 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -1667,12 +1667,14 @@ void con_set_layout(Con *con, layout_t layout) {
      * whole workspace into stacked/tabbed mode. To do this and still allow
      * intuitive operations (like level-up and then opening a new window), we
      * need to create a new split container. */
-    if (con->type == CT_WORKSPACE &&
-        (layout == L_STACKED || layout == L_TABBED)) {
+    if (con->type == CT_WORKSPACE) {
         if (con_num_children(con) == 0) {
-            DLOG("Setting workspace_layout to %d\n", layout);
-            con->workspace_layout = layout;
-        } else {
+            layout_t ws_layout = (layout == L_STACKED || layout == L_TABBED) ? layout : L_DEFAULT;
+            DLOG("Setting workspace_layout to %d\n", ws_layout);
+            con->workspace_layout = ws_layout;
+            DLOG("Setting layout to %d\n", layout);
+            con->layout = layout;
+        } else if (layout == L_STACKED || layout == L_TABBED) {
             DLOG("Creating new split container\n");
             /* 1: create a new split container */
             Con *new = con_new(NULL, NULL);
index 879d225e64e60796e61b104ab6a2257c28fc0462..7ca6e102785a674daa3741e5f884fd68ebc958ab 100644 (file)
@@ -106,8 +106,8 @@ CFGFUN(font, const char *font) {
     font_pattern = sstrdup(font);
 }
 
-CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) {
-    configure_binding(bindtype, modifiers, key, release, border, whole_window, command, DEFAULT_BINDING_MODE, false);
+CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) {
+    configure_binding(bindtype, modifiers, key, release, border, whole_window, exclude_titlebar, command, DEFAULT_BINDING_MODE, false);
 }
 
 /*******************************************************************************
@@ -117,8 +117,8 @@ CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, co
 static char *current_mode;
 static bool current_mode_pango_markup;
 
-CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) {
-    configure_binding(bindtype, modifiers, key, release, border, whole_window, command, current_mode, current_mode_pango_markup);
+CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) {
+    configure_binding(bindtype, modifiers, key, release, border, whole_window, exclude_titlebar, command, current_mode, current_mode_pango_markup);
 }
 
 CFGFUN(enter_mode, const char *pango_markup, const char *modename) {
index d2fe4e07972327b1da827acddad5a87c43de346a..2d4647f8dfba08b7d6c97abe3fc1588119d905a0 100644 (file)
@@ -383,7 +383,11 @@ void tree_split(Con *con, orientation_t orientation) {
 
     if (con->type == CT_WORKSPACE) {
         if (con_num_children(con) < 2) {
-            DLOG("Just changing orientation of workspace\n");
+            if (con_num_children(con) == 0) {
+                DLOG("Changing workspace_layout to L_DEFAULT\n");
+                con->workspace_layout = L_DEFAULT;
+            }
+            DLOG("Changing orientation of workspace\n");
             con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
             return;
         } else {
index 033a31f2665e025ea86188b275c8c832707bcd5a..1ed0999031fb9db2fa20980ad17f243ff84ba17c 100644 (file)
@@ -145,6 +145,237 @@ is($x->input_focus, $second->id, 'second window focused');
 ok(@content == 1, 'one con at workspace level');
 is($content[0]->{layout}, 'stacked', 'layout stacked');
 
+#####################################################################
+# 8: when the workspace is empty check that its layout can be changed
+# from stacked to horizontal split using the 'layout splith' command.
+#####################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'layout stacked';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+
+cmd '[id="' . $first->id . '"] kill';
+cmd '[id="' . $second->id . '"] kill';
+sync_with_i3;
+
+ok(@{get_ws_content($tmp)} == 0, 'workspace is empty');
+
+cmd 'layout splith';
+$first = open_window;
+$second = open_window;
+@content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'stacked', 'layout not stacked');
+isnt($content[1]->{layout}, 'stacked', 'layout not stacked');
+
+#####################################################################
+# 9: when the workspace is empty check that its layout can be changed
+# from stacked to vertical split using the 'layout splitv' command.
+#####################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'layout stacked';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+
+cmd '[id="' . $first->id . '"] kill';
+cmd '[id="' . $second->id . '"] kill';
+sync_with_i3;
+
+ok(@{get_ws_content($tmp)} == 0, 'workspace is empty');
+
+cmd 'layout splitv';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'stacked', 'layout not stacked');
+isnt($content[1]->{layout}, 'stacked', 'layout not stacked');
+
+#####################################################################
+# 10: when the workspace is empty check that its layout can be changed
+# from tabbed to horizontal split using the 'layout splith' command.
+#####################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'layout tabbed';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{layout}, 'tabbed', 'layout tabbed');
+
+cmd '[id="' . $first->id . '"] kill';
+cmd '[id="' . $second->id . '"] kill';
+sync_with_i3;
+
+ok(@{get_ws_content($tmp)} == 0, 'workspace is empty');
+
+cmd 'layout splith';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed');
+isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed');
+
+#####################################################################
+# 11: when the workspace is empty check that its layout can be changed
+# from tabbed to vertical split using the 'layout splitv' command.
+#####################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'layout tabbed';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{layout}, 'tabbed', 'layout tabbed');
+
+cmd '[id="' . $first->id . '"] kill';
+cmd '[id="' . $second->id . '"] kill';
+sync_with_i3;
+
+ok(@{get_ws_content($tmp)} == 0, 'workspace is empty');
+
+cmd 'layout splitv';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed');
+isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed');
+
+#####################################################################
+# 12: when the workspace is empty check that its layout can be changed
+# from stacked to horizontal split using the 'split horizontal' command.
+#####################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'layout stacked';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+
+cmd '[id="' . $first->id . '"] kill';
+cmd '[id="' . $second->id . '"] kill';
+sync_with_i3;
+
+ok(@{get_ws_content($tmp)} == 0, 'workspace is empty');
+
+cmd 'split horizontal';
+$first = open_window;
+$second = open_window;
+@content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'stacked', 'layout not stacked');
+isnt($content[1]->{layout}, 'stacked', 'layout not stacked');
+
+#####################################################################
+# 13: when the workspace is empty check that its layout can be changed
+# from stacked to vertical split using the 'split vertical' command.
+#####################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'layout stacked';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+
+cmd '[id="' . $first->id . '"] kill';
+cmd '[id="' . $second->id . '"] kill';
+sync_with_i3;
+
+ok(@{get_ws_content($tmp)} == 0, 'workspace is empty');
+
+cmd 'split vertical';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'stacked', 'layout not stacked');
+isnt($content[1]->{layout}, 'stacked', 'layout not stacked');
+
+#####################################################################
+# 14: when the workspace is empty check that its layout can be changed
+# from tabbed to horizontal split using the 'split horizontal' command.
+#####################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'layout tabbed';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{layout}, 'tabbed', 'layout tabbed');
+
+cmd '[id="' . $first->id . '"] kill';
+cmd '[id="' . $second->id . '"] kill';
+sync_with_i3;
+
+ok(@{get_ws_content($tmp)} == 0, 'workspace is empty');
+
+cmd 'split horizontal';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed');
+isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed');
+
+#####################################################################
+# 15: when the workspace is empty check that its layout can be changed
+# from tabbed to vertical split using the 'split vertical' command.
+#####################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'layout tabbed';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{layout}, 'tabbed', 'layout tabbed');
+
+cmd '[id="' . $first->id . '"] kill';
+cmd '[id="' . $second->id . '"] kill';
+sync_with_i3;
+
+ok(@{get_ws_content($tmp)} == 0, 'workspace is empty');
+
+cmd 'split vertical';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed');
+isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed');
+
+
 exit_gracefully($pid);
 
 done_testing;
index 1de86c653c2fde44e78e4608167fdda3dd18cd17..159de0466b42d47b815d5bd2a79cfa1f0e3ef8a4 100644 (file)
@@ -49,18 +49,22 @@ mode "meh" {
     bindsym --release --whole-window button3 nop
     bindsym --border button3 nop
     bindsym --release --border button3 nop
+    bindsym --exclude-titlebar button3 nop
+    bindsym --whole-window --border --exclude-titlebar button3 nop
 }
 EOT
 
 my $expected = <<'EOT';
 cfg_enter_mode((null), meh)
-cfg_mode_binding(bindsym, Mod1,Shift, x, (null), (null), (null), resize grow)
-cfg_mode_binding(bindcode, Mod1, 44, (null), (null), (null), resize shrink)
-cfg_mode_binding(bindsym, Mod1, x, --release, (null), (null), exec foo)
-cfg_mode_binding(bindsym, (null), button3, (null), (null), --whole-window, nop)
-cfg_mode_binding(bindsym, (null), button3, --release, (null), --whole-window, nop)
-cfg_mode_binding(bindsym, (null), button3, (null), --border, (null), nop)
-cfg_mode_binding(bindsym, (null), button3, --release, --border, (null), nop)
+cfg_mode_binding(bindsym, Mod1,Shift, x, (null), (null), (null), (null), resize grow)
+cfg_mode_binding(bindcode, Mod1, 44, (null), (null), (null), (null), resize shrink)
+cfg_mode_binding(bindsym, Mod1, x, --release, (null), (null), (null), exec foo)
+cfg_mode_binding(bindsym, (null), button3, (null), (null), --whole-window, (null), nop)
+cfg_mode_binding(bindsym, (null), button3, --release, (null), --whole-window, (null), nop)
+cfg_mode_binding(bindsym, (null), button3, (null), --border, (null), (null), nop)
+cfg_mode_binding(bindsym, (null), button3, --release, --border, (null), (null), nop)
+cfg_mode_binding(bindsym, (null), button3, (null), (null), (null), --exclude-titlebar, nop)
+cfg_mode_binding(bindsym, (null), button3, (null), --border, --whole-window, --exclude-titlebar, nop)
 EOT
 
 is(parser_calls($config),
@@ -674,7 +678,7 @@ EOT
 
 $expected = <<'EOT';
 cfg_enter_mode((null), yo)
-cfg_mode_binding(bindsym, (null), x, (null), (null), (null), resize shrink left)
+cfg_mode_binding(bindsym, (null), x, (null), (null), (null), (null), resize shrink left)
 ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', '}'
 ERROR: CONFIG: (in file <stdin>)
 ERROR: CONFIG: Line   1: mode "yo" {