awesomeWM / awesome

awesome window manager
https://awesomewm.org/
GNU General Public License v2.0
6.36k stars 598 forks source link

mouse buttons undesirably propagate all events to clients #1447

Open fleminra opened 7 years ago

fleminra commented 7 years ago

Output of awesome --version: awesome v4.0 (Harder, Better, Faster, Stronger) • Compiled against Lua 5.1.5 (running with Lua 5.1) • D-Bus support: ✔ • execinfo support: ✔ • RandR 1.5 support: ✘ • LGI version: 0.9.0

How to reproduce the issue:

clientbuttons = awful.util.table.join(
    awful.button({ }, 1, function (c)
                           client.focus = c;
                           -- c:raise()
                         end),
    awful.button({ modkey }, 1, awful.mouse.client.move),
    awful.button({ modkey }, 3, awful.mouse.client.resize),

    awful.button({ modkey }, 4, function(c) c:raise() end, function(c) end),
    awful.button({ modkey }, 5, function(c) c:lower() end, function(c) end))

Goals:

Emmanuel suggested on Stack Overflow that adding a release callback would cause the event to not be propagated to the client, but this does not seem to be the case.

Actual result:

Mouse wheel events do raise and lower the window, but also propagate the event to the client. E.g. if it's Chrome or Firefox, the window scrolls.

Expected result:

The window should be raised or lowered, and the event swallowed.

I'm not proposing that Awesome should swallow all button events, because most people would expect that buttons 1, 2, 3 should propagate. Maybe swallow/propagation could be indicated by the return value of the callback (e.g. a boolean value).

I observed that keyboard events do not propagate, but I couldn't pinpoint the difference in Awesome's source code, between the handling of keyboard events and button events.

blueyed commented 7 years ago

What about:

    awful.button({ modkey }, 4, nil, function(c) c:raise() end),
    awful.button({ modkey }, 5, nil, function(c) c:lower() end))

i.e. trgigrring it on release events.

Maybe swallow/propagation could be indicated by the return value of the callback (e.g. a boolean value).

Yes, that's what browsers are / JS is doing, too.

It is a bit weird though that keyboard behavior derives from mouse events here?! But that might be just too much distinguished input handlers?!

fleminra commented 7 years ago
   awful.button({ modkey }, 4, nil, function(c) c:raise() end),
   awful.button({ modkey }, 5, nil, function(c) c:lower() end))

Tried this and couldn't get the release callback to fire at all. This should do that, right:

logfile = io.open("/tmp/awesome.log", "w");
logfile:write("log start\n");

clientbuttons = awful.util.table.join(
  awful.button({ modkey }, 4,
               nil,
               function(c)
                 logfile:write("button4 release1\n");
                 logfile:flush();
                 c:raise();
                 logfile:write("button4 release2\n");
                 logfile:flush();
               end),
  awful.button({ modkey }, 5,
               function(c)
                 logfile:write("button5 press1\n");
                 logfile:flush();
                 c:lower();
                 logfile:write("button5 press1\n");
                 logfile:flush();
               end,
               function(c)
                 logfile:write("button5 release1\n");
                 logfile:flush();
                 c:lower()
                 logfile:write("button5 release2\n");
                 logfile:flush();
               end))

?

I can't get any "release" messages in my log file either in the case of providing a press callback or not.

psychon commented 7 years ago

I didn't test this and of course this is not what anyone wants, but I think that with the following patch no button events propagate at all:

diff --git a/event.c b/event.c
index 40e33d740..e17b0df2c 100644
--- a/event.c
+++ b/event.c
@@ -262,7 +262,7 @@ event_handle_button(xcb_button_press_event_t *ev)
             event_button_callback(ev, &c->buttons, L, -1, 1, NULL);
         }
         xcb_allow_events(globalconf.connection,
-                         XCB_ALLOW_REPLAY_POINTER,
+                         XCB_ALLOW_ASYNC_POINTER,
                          XCB_CURRENT_TIME);
     }
     else if(ev->child == XCB_NONE)

(Just to see how easy this is)

The real problem for implementing this is:

Maybe swallow/propagation could be indicated by the return value of the callback (e.g. a boolean value).

It is not immediately visible in the API, but this uses a "press" and a "release" signal internally to which a callback is connected. Many callbacks can be connected to the same signal and so we get many boolean values. How to merge them into a single one?

It is a bit weird though that keyboard behavior derives from mouse events here?! But that might be just too much distinguished input handlers?!

Key events are explicitly registered with the X11 server ("I grab presses of key 'a' when Mod4 is active") while button events are handled more implicitly ("Just inform me about all button events, I'll then tell you how to continue (via xcb_allow_events)"). This is not really required by the X11 protocol, but just how to it in awesome. It was always like that, so possibly even inherited from dwm.

Edit: And just to say it again: The C code does not check if a callback is registered or not. It behaves the same way in any case. The only reason why it somehow makes a difference to react to key release instead of press is that the callback is removing/unmapping/hiding the window which gets the input event.

fleminra commented 7 years ago

Many callbacks can be connected to the same signal and so we get many boolean values. How to merge them into a single one?

Interesting question.

IIUC, Java AWT's strategy in this situation is that if any callback indicates that the event is consumed (using event.consume()), then the event isn't passed to the default underlying component.

In JavaScript it looks like using the event-handler-return-value idiom was never actually standard, but in practice it's equivalent to calling the two standard functions event.preventDefault() and event.stopPropagation().

So there may be some wisdom here behind using explicit methods on the event object, instead of using the return value.

I'm pretty green on X11 programming, but how difficult would it be, technically, to add state in the event object to manage all this, and then have the C layer interrogate this state before propagating the event out to the application? If it doesn't seem like there's a broad use case for this, I'm pretty receptive to running a fork of awesome to scratch this particular itch of mine.

sigprof commented 5 years ago

As a workaround, it is possible to grab the mouse in the press handler until all mouse buttons are released, so that all mouse events will be consumed by Awesome:

local function grab_mouse_until_released()
    mousegrabber.run(function(_mouse)
        for _, v in pairs(_mouse.buttons) do
            if v then return true end
        end
        return false
    end, "mouse")
end

Use this function in handlers which do not grab the mouse themselves (like awful.mouse.client.move):

    awful.button({ modkey }, 4, function(c) c:raise(); grab_mouse_until_released() end),
    awful.button({ modkey }, 5, function(c) c:lower(); grab_mouse_until_released() end)

Testing in xev shows that the client still gets some events (EnterNotify and KeymapNotify) in this case, but there are no button-related events:

EnterNotify event, serial 35, synthetic NO, window 0x2a00001,
    root 0x553, subw 0x0, time 19182689, (84,50), root:(1168,734),
    mode NotifyUngrab, detail NotifyAncestor, same_screen YES,
    focus YES, state 64

KeymapNotify event, serial 35, synthetic NO, window 0x0,
    keys:  83  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
           32  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   

Update: While grabbing the mouse works for normal buttons (1–3) and blocks scroll wheel events (buttons 4 and 5) in Firefox, for some reason Qt5-based programs (tried Telegram Desktop and qterminal) still receive scroll wheel events even when the mouse is grabbed (maybe through some newer XInput interfaces?), so this is still not a complete solution.

actionless commented 5 years ago

till receive scroll wheel events even when the mouse is grabbed (maybe through some newer XInput interfaces?)

take a look on xinput list output, some devices are making a separate virtual device for scrolling

sigprof commented 5 years ago

take a look on xinput list output, some devices are making a separate virtual device for scrolling

There is no extra virtual device. The problem is that grabbing the mouse does not block the initial XInput scroll event, and Qt5 uses XInput for mouse wheel scroll by default.

Setting QT_XCB_NO_XI2=1 disables XInput support in Qt5, and in this case grabbing the mouse in Awesome blocks the mouse wheel scroll events.

For testing, I added the Mod4+Button2 binding which raises the window and keeps the mouse grabbed until the button is released:

    awful.button({ modkey }, 2, function (c)
        c:emit_signal("request::activate", "mouse_click", {raise = true})
        grab_mouse_until_released()
    end),
    awful.button({ modkey }, 4, function(c) c:raise(); grab_mouse_until_released() end),
    awful.button({ modkey }, 5, function(c) c:lower(); grab_mouse_until_released() end)

With these bindings and without QT_XCB_NO_XI2=1, while Mod4+Button2 is pressed, the Qt5-based app does not receive mouse wheel scroll events, therefore grabbing the mouse also stops event delivery via XInput. The problem is only with the initial event — using xinput test, I see the button press 2 event when pressing Mod4+Button2, then I see no further events until Button2 is released.

wooparadog commented 2 years ago

Well.. This is an old issue. I want to use meta + mouse wheel to control client opacity. After reading [https://github.com/awesomeWM/awesome/blob/master/lib/awful/mouse/resize.lua](), I came up with this solution.

    awful.button({ modkey }, 4, function (c)
      c:emit_signal("request::activate", "mouse_click", {raise = true})
      capi.mousegrabber.run(function (_mouse)
        c.opacity = c.opacity + 0.1
        return false
      end, 'mouse')
    end, nil),
    awful.button({ modkey }, 5, function (c)
      c:emit_signal("request::activate", "mouse_click", {raise = true})
      capi.mousegrabber.run(function (_mouse)
        c.opacity = c.opacity - 0.1
        return false
      end, 'mouse')
    end, nil)
rocchidavide commented 11 months ago

@wooparadog, thank you for your solution! I modified it so that when I press modkey with 8 or 9 mouse buttons, I can move a tag to the other screen (i have multiple monitors). It works except with Google Chrome: he continues to use the mouse keys, navigating prev next. Does it work for you?