]> git.sur5r.net Git - i3/i3/commitdiff
Add '--release' flag for bindsym in the bar block 3070/head
authorOrestis Floros <orestisf1993@gmail.com>
Fri, 8 Dec 2017 00:23:15 +0000 (02:23 +0200)
committerOrestis Floros <orestisf1993@gmail.com>
Fri, 8 Dec 2017 10:16:22 +0000 (12:16 +0200)
i3bar's handle_button is modified to also handle XCB_BUTTON_RELEASE
events. During these button release events, only custom commands are
checked to avoid sending multiple workspace ipc messages.

The way this patch is implemented will allow to assign a custom command
for both the press and release of the same button:
bar {
  ...
  bindsym buttonX exec command1
  bindsym --release buttonX exec command2
}

Fixes #3068.

docs/userguide
i3bar/include/configuration.h
i3bar/src/config.c
i3bar/src/xcb.c
include/config_directives.h
include/configuration.h
parser-specs/config.spec
src/config_directives.c
src/ipc.c
testcases/t/525-i3bar-mouse-bindings.t

index 7a1621da86e86af18c423da3f1f42ec02ac3ee8f..0258e2ab78e82ef8942130938897cf6b7cd3ca48 100644 (file)
@@ -1377,7 +1377,7 @@ and will be removed in a future release. We strongly recommend using the more ge
 
 *Syntax*:
 ----------------------------
-bindsym button<n> <command>
+bindsym [--release] button<n> <command>
 ----------------------------
 
 *Example*:
@@ -1385,6 +1385,8 @@ bindsym button<n> <command>
 bar {
     # disable clicking on workspace buttons
     bindsym button1 nop
+    # Take a screenshot by right clicking on the bar
+    bindsym --release button3 exec --no-startup-id import /tmp/latest-screenshot.png
     # execute custom script when scrolling downwards
     bindsym button5 exec ~/.i3/scripts/custom_wheel_down
 }
index e77e891b9f3bdf03afd548839b9d6508162a3b5a..61cac7f6bdcab344de7cbaaa9dfc5f1a8c4fc1f3 100644 (file)
@@ -27,6 +27,7 @@ typedef enum { M_DOCK = 0,
 typedef struct binding_t {
     int input_code;
     char *command;
+    bool release;
 
     TAILQ_ENTRY(binding_t)
     bindings;
index 79e106c07447f9d56a994958b34229dc8dab216b..a58b9bf80c06b69425872bfe74eb69602a2d8215 100644 (file)
@@ -264,6 +264,21 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
  *
  */
 static int config_boolean_cb(void *params_, int val) {
+    if (parsing_bindings) {
+        if (strcmp(cur_key, "release") == 0) {
+            binding_t *binding = TAILQ_LAST(&(config.bindings), bindings_head);
+            if (binding == NULL) {
+                ELOG("There is no binding to put the current command onto. This is a bug in i3.\n");
+                return 0;
+            }
+
+            binding->release = val;
+            return 1;
+        }
+
+        ELOG("Unknown key \"%s\" while parsing bar bindings.\n", cur_key);
+    }
+
     if (!strcmp(cur_key, "binding_mode_indicator")) {
         DLOG("binding_mode_indicator = %d\n", val);
         config.disable_binding_mode_indicator = !val;
index 1a9240fb996dae3e14ca162650ec4eef7dbac656..77822544e72e5df3de2a0cc532850f965c7b6cfb 100644 (file)
@@ -439,6 +439,18 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
     xcb_flush(xcb_connection);
 }
 
+static bool execute_custom_command(xcb_keycode_t input_code, bool event_is_release) {
+    binding_t *binding;
+    TAILQ_FOREACH(binding, &(config.bindings), bindings) {
+        if ((binding->input_code != input_code) || (binding->release != event_is_release))
+            continue;
+
+        i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command);
+        return true;
+    }
+    return false;
+}
+
 /*
  * Handle a button press event (i.e. a mouse click on one of our bars).
  * We determine, whether the click occurred on a workspace button or if the scroll-
@@ -460,10 +472,16 @@ void handle_button(xcb_button_press_event_t *event) {
         return;
     }
 
-    int32_t x = event->event_x >= 0 ? event->event_x : 0;
-
     DLOG("Got button %d\n", event->detail);
 
+    /* During button release events, only check for custom commands. */
+    const bool event_is_release = (event->response_type & ~0x80) == XCB_BUTTON_RELEASE;
+    if (event_is_release) {
+        execute_custom_command(event->detail, event_is_release);
+        return;
+    }
+
+    int32_t x = event->event_x >= 0 ? event->event_x : 0;
     int workspace_width = 0;
     i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk;
 
@@ -516,12 +534,7 @@ void handle_button(xcb_button_press_event_t *event) {
 
     /* If a custom command was specified for this mouse button, it overrides
      * the default behavior. */
-    binding_t *binding;
-    TAILQ_FOREACH(binding, &(config.bindings), bindings) {
-        if (binding->input_code != event->detail)
-            continue;
-
-        i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command);
+    if (execute_custom_command(event->detail, event_is_release)) {
         return;
     }
 
@@ -1164,6 +1177,7 @@ void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
                 }
 
                 break;
+            case XCB_BUTTON_RELEASE:
             case XCB_BUTTON_PRESS:
                 /* Button press events are mouse buttons clicked on one of our bars */
                 handle_button((xcb_button_press_event_t *)event);
@@ -1678,7 +1692,8 @@ void reconfig_windows(bool redraw_bars) {
              * */
             values[3] = XCB_EVENT_MASK_EXPOSURE |
                         XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
-                        XCB_EVENT_MASK_BUTTON_PRESS;
+                        XCB_EVENT_MASK_BUTTON_PRESS |
+                        XCB_EVENT_MASK_BUTTON_RELEASE;
             if (config.hide_on_modifier == M_DOCK) {
                 /* If the bar is normally visible, catch visibility change events to suspend
                  * the status process when the bar is obscured by full-screened windows.  */
index 1191a7c6e34a476da70e286e16f0f39f4b4b1cac..60c7a4b14a9136a8e0b80bf3daf87474c84f7bf2 100644 (file)
@@ -84,7 +84,7 @@ CFGFUN(bar_verbose, const char *verbose);
 CFGFUN(bar_modifier, const char *modifier);
 CFGFUN(bar_wheel_up_cmd, const char *command);
 CFGFUN(bar_wheel_down_cmd, const char *command);
-CFGFUN(bar_bindsym, const char *button, const char *command);
+CFGFUN(bar_bindsym, const char *button, const char *release, const char *command);
 CFGFUN(bar_position, const char *position);
 CFGFUN(bar_i3bar_command, const char *i3bar_command);
 CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text);
index 8f1ce3320353664296fa49a410f717e2049e9016..ac8001590a9ff37f7f5086d25a9bed4a2d39a140 100644 (file)
@@ -384,6 +384,9 @@ struct Barbinding {
     /** The command which is to be executed for this button. */
     char *command;
 
+    /** If true, the command will be executed after the button is released. */
+    bool release;
+
     TAILQ_ENTRY(Barbinding)
     bindings;
 };
index c31567a64b87d29dc340cd821621f15c2636fb45..3d3ffb283af70298b74c5173dfab8ca2d4c58347 100644 (file)
@@ -501,12 +501,16 @@ state BAR_WHEEL_DOWN_CMD:
       -> call cfg_bar_wheel_down_cmd($command); BAR
 
 state BAR_BINDSYM:
+  release = '--release'
+      ->
   button = word
       -> BAR_BINDSYM_COMMAND
 
 state BAR_BINDSYM_COMMAND:
+  release = '--release'
+      ->
   command = string
-      -> call cfg_bar_bindsym($button, $command); BAR
+      -> call cfg_bar_bindsym($button, $release, $command); BAR
 
 state BAR_POSITION:
   position = 'top', 'bottom'
index 1cf875ef00aeaf5d30c52f9944f84217a99fc19b..a3b1f776cb76cfa44f59dba04ef9af7c5d038a85 100644 (file)
@@ -502,7 +502,7 @@ CFGFUN(bar_modifier, const char *modifier) {
         current_bar->modifier = M_NONE;
 }
 
-static void bar_configure_binding(const char *button, const char *command) {
+static void bar_configure_binding(const char *button, const char *release, const char *command) {
     if (strncasecmp(button, "button", strlen("button")) != 0) {
         ELOG("Bindings for a bar can only be mouse bindings, not \"%s\", ignoring.\n", button);
         return;
@@ -513,16 +513,18 @@ static void bar_configure_binding(const char *button, const char *command) {
         ELOG("Button \"%s\" does not seem to be in format 'buttonX'.\n", button);
         return;
     }
+    const bool release_bool = release != NULL;
 
     struct Barbinding *current;
     TAILQ_FOREACH(current, &(current_bar->bar_bindings), bindings) {
-        if (current->input_code == input_code) {
+        if (current->input_code == input_code && current->release == release_bool) {
             ELOG("command for button %s was already specified, ignoring.\n", button);
             return;
         }
     }
 
     struct Barbinding *new_binding = scalloc(1, sizeof(struct Barbinding));
+    new_binding->release = release_bool;
     new_binding->input_code = input_code;
     new_binding->command = sstrdup(command);
     TAILQ_INSERT_TAIL(&(current_bar->bar_bindings), new_binding, bindings);
@@ -530,16 +532,16 @@ static void bar_configure_binding(const char *button, const char *command) {
 
 CFGFUN(bar_wheel_up_cmd, const char *command) {
     ELOG("'wheel_up_cmd' is deprecated. Please us 'bindsym button4 %s' instead.\n", command);
-    bar_configure_binding("button4", command);
+    bar_configure_binding("button4", NULL, command);
 }
 
 CFGFUN(bar_wheel_down_cmd, const char *command) {
     ELOG("'wheel_down_cmd' is deprecated. Please us 'bindsym button5 %s' instead.\n", command);
-    bar_configure_binding("button5", command);
+    bar_configure_binding("button5", NULL, command);
 }
 
-CFGFUN(bar_bindsym, const char *button, const char *command) {
-    bar_configure_binding(button, command);
+CFGFUN(bar_bindsym, const char *button, const char *release, const char *command) {
+    bar_configure_binding(button, release, command);
 }
 
 CFGFUN(bar_position, const char *position) {
index 99b9e0ec65814fd7c4fb52f5ef724f5c2344c3c3..a1a72b1ac8ba620136fd9f8d3cced898465d69f0 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -572,6 +572,8 @@ static void dump_bar_bindings(yajl_gen gen, Barconfig *config) {
         y(integer, current->input_code);
         ystr("command");
         ystr(current->command);
+        ystr("release");
+        y(bool, current->release == B_UPON_KEYRELEASE);
 
         y(map_close);
     }
index 57786deaf2f8bc6e727498f61268ce450ee4e88d..8755278508e0391563ee8f0ae96ab8db7ff12898 100644 (file)
@@ -30,6 +30,9 @@ bar {
     bindsym button3 focus left
     bindsym button4 focus right
     bindsym button5 focus left
+    bindsym --release button6 focus right
+    bindsym button7 focus left
+    bindsym button7 --release focus right
 }
 EOT
 use i3test::XTEST;
@@ -142,4 +145,43 @@ subtest 'button 5 moves focus left', \&focus_subtest,
     [ $left->{id} ],
     'button 5 moves focus left';
 
+# Test --release flag with bar bindsym.
+# See issue: #3068.
+
+my $old_focus = get_focused($ws);
+subtest 'button 6 does not move focus while pressed', \&focus_subtest,
+    sub {
+        xtest_button_press(6, 3, 3);
+        xtest_sync_with($i3bar_window);
+    },
+    [],
+    'button 6 does not move focus while pressed';
+is(get_focused($ws), $old_focus, 'focus unchanged');
+
+subtest 'button 6 release moves focus right', \&focus_subtest,
+    sub {
+        xtest_button_release(6, 3, 3);
+        xtest_sync_with($i3bar_window);
+    },
+    [ $right->{id} ],
+    'button 6 release moves focus right';
+
+# Test same bindsym button with and without --release.
+
+subtest 'button 7 press moves focus left', \&focus_subtest,
+    sub {
+        xtest_button_press(7, 3, 3);
+        xtest_sync_with($i3bar_window);
+    },
+    [ $left->{id} ],
+    'button 7 press moves focus left';
+
+subtest 'button 7 release moves focus right', \&focus_subtest,
+    sub {
+        xtest_button_release(7, 3, 3);
+        xtest_sync_with($i3bar_window);
+    },
+    [ $right->{id} ],
+    'button 7 release moves focus right';
+
 done_testing;