15

In Ubuntu 12.04, my mouse clicks when I release the button.

What happens (bad): I right click (mousedown), the context menu appears, when I release the mouse button (mouseup), the item under the cursor is clicked.

What should happen (good): I right click (mousedown), the context menu appears, nothing happens when I release the button. To click an item in the context menu, I click it normally with the right mouse button.

I have experienced this behavior in Chrome, the file browser, and in gnome terminal. The mouse is a Razer DeathAdder (but I'm just running whatever drivers Ubuntu picked automatically), and if it matters, I'm using the AMD/ATI graphics drivers.

  • 2
    i have the same behavior here on chromium – josinalvo Aug 21 '12 at 04:24
  • 4
    This is so unbelievably annoying. Any luck getting it disabled? – Max Nov 09 '12 at 19:17
  • 1
    I have the same problem, and it hacks me off too. I am considering as a workaround trying to get my right-click menus to have thick borders so that even if I'm moving my mouse slightly when I right-click, I'll just hit the border and not an actual menu option. The version of Fedora I use at home has these borders, and as a result I never accidentally hit items in right-click menus in the way I do in Ubuntu, even though Fedora has the same select-option-on-release behavior. I don't know much about Linux desktop management stuff, but if I get the workaround to work, I'll post an answer. – Mark Amery Mar 02 '13 at 10:57

4 Answers4

5

Here is my fix:

First, you need to compile and install sxhkd, from here:

https://github.com/baskerville/sxhkd

which is a really cool program.

Then, in your ~/.config/sxhkd/sxhkdrc add this recipe:

~button3
  for id in `xinput list | grep 'slave  pointer' | grep -v XTEST | sed -e 's/.*id=\([0-9]\+\).*/\1/'`; do xinput set-prop $id "Device Enabled" 0; done; \  
  xte 'mouseup 3' 'mousermove 0 -1'; \ 
  sleep 0.3; \
  for id in `xinput list | grep 'slave  pointer' | grep -v XTEST | sed -e 's/.*id=\([0-9]\+\).*/\1/'`; do xinput set-prop $id "Device Enabled" 1; done

How this works, sxhkd captures the right click event with its xcb listener, and replays it back, this is what the tilde is for. After this, we turn off the touchpad, move the mouse cursor one pixel up so that the first entry is not highlighted, sleep for 300ms to ignore any finger dragging after the click that throws off the position, and reenable the pointer devices.

This works perfectly on the chromebook I am setting up.

UPDATE: I changed the sxhkd recipe to work with all pointer input devices, not just touchpads.

2

If you do not move your mouse in the meanwhile, there should not be any "menu item under cursor". The context menu opens such that its left upper corner is at the cursor, and there is a margin below to the next menu item.

However, if on mouse-button-release there is a menu item under the cursor, the desired behavior is to launch that menu item. This his how you (or at least many people) normally proceed: mouse-button-down, move the cursor to the desired menu item, mouse-button-release to activate that item.

If there is no menu-item under cursor after mouse-button-down, then the behavior is as you described: mouse-button-release does not destroy the context menu.

So maybe your mouse is very sensitive, and a button-down event is accompanied by a cursor move?

January
  • 37,288
  • 3
    You are right that if there is nothing under the menu, nothing happens, which I knew, but this behavior is not desired to me. I just played around with my OS X laptop, and it seems that it distinguishes between a right-click and drag to the menu item versus simply right-clicking and releasing immediately. In Ubuntu, it is very sensitive. I have already lost work once accidentally this way when I was editing something in my browser. – davidtbernal Aug 21 '12 at 06:12
  • 2
    The problem is that if you're moving your mouse even very very slightly down and right at the moment that you right click, you are guaranteed to hit the top button of the menu. How often this happens to a given user probably depends upon their mouse-using habits. If you're in the habit of pointing at something and then clicking it in one fluid motion without ensuring your cursor is perfectly stationary, then you'll hit this issue regularly. Friendlier behavior would be to have, say, a 0.1s delay after mousedown before mouseup would count as selecting a menu option. – Mark Amery Mar 02 '13 at 10:50
  • 3
    This is not necessarily true. For very big context menus, like Thunderbird's, if one right click's to the left of the screen, then the context menu appears while the cursor is inside it. So just right-clicking in this situation triggers unwanted operations. – zazke Dec 05 '20 at 09:53
1

I wrote a Python script that is a solution to this problem. It grabs mouse cursor in place after right mouse button is clicked and shows context menu only after right mouse cursor is released.
This script is available here:
https://github.com/pangratt12345/X11-Cursor-Grabber-Python
I translated this Python script from C++ program here:
https://github.com/pangratt12345/X11-Cursor-Grabber-Cpp
https://askubuntu.com/a/1556079/1756067
I wrote that C++ program basing on a bigger easystroke project
https://github.com/higersky/easystroke

This Python script uses X library and works in X11 Windows system. It may not work in Wayland. To check if you are running X11 or Wayland you can run below command:
echo $XDG_SESSION_TYPE # prints "x11" or "wayland"
This script also uses Xinput2.0 and XTest extensions of X library. So to be able to run this you need to install these this way:
pip install python-xlib

This script has below features:
  • mouse cursor is not hidden during the grab
  • mouse cursor is warped back to original location after right mouse release
  • context menu is always shown even when mouse cursor has been dragged when holding right mouse button
  • script has a whitelisted processes list, which disables/enables mouse cursor grabs when whitelisted process is detected/undetected respectively. You can modify the whitelist in script's code

Explanation on how this script works:
  • Script opens X Display, and checks if XTest and XInput2.0 extensions are available.
  • It sets listening SubstructureNotify X events to detect changes in windows hierarchy to determine if whitelisted process window got opened and if mouse cursor grabbing should be enabled/disabled.
  • It starts X event listening loop which also listens to XI_ButtonPress and XI_ButtonReleaseevents using XInput extension when passive grab is enabled
  • Passive grab is set for right mouse button, which locks mouse cursor in place when XI_ButtonPress event is received. Original right mouse click location is saved.
Passive grab is set for all pointer devices which can be seen using this command xinput list, these devices are named as "X slave pointer"
  • Mouse cursor is unlocked on XI_ButtonRelease event. Passive grab is disabled for a short moment to be able to warp mouse cursor back to the original click location and to deliver simulated right mouse click and release actions to X Server.

Below is full Python script:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os, signal from typing import List, Tuple from Xlib import X, display # using X11 Windows System library from Xlib.ext import xinput, xtest # xinput used for grabbing input devices, xtest used for delivering simulated clicks and releases from Xlib.error import BadAccess # raised on mouse cursor grab conflicts between applications from pyglet.libs.x11.xlib import GenericEvent # for XInput2.0 events which are of generic type

x_display = display.Display() # currently used X display root_window = x_display.screen().root # top-level window in X windows hierarchy verbose_flag: bool = True # if True then print all logs to stdout running: bool = True pressed: bool = False grab_enabled: bool = False original_xy: Tuple[int, int] = (0, 0) # original location of right mouse button click xinput_opcode: int = 131 # code used by XInput2.0 function calls, usually 131 Right_Mouse_Button : int = 3 # X Window constant for right mouse button = 3 Whitelisted_Processes: List[str] = [ # processes for which right mouse button grabbing feature is turned off "blender", "autodesk", "wineserver", "wine", "wine64", "wine-preloader", "wine64-preloader", "explorer.exe", "services.exe", "winedevice.exe", "plugplay.exe", "rpcss.exe", ]

def is_process_running(processes: List[str]) -> bool: try: # /proc directory contains all processes information in folders for each process for pid_dir in os.listdir("/proc"): # loop over all entries in /proc folder if not pid_dir.isdigit(): continue # if folder name has letters in it then it is not a process info, then skip it # processes' directories in /proc folder have pids as their names with open(os.path.join("/proc", pid_dir, "comm"), "r", encoding="utf-8", errors="replace") as cmd_file: process_name = cmd_file.readline().strip() # full path to process comm file, which has only 1 line = process name if process_name and process_name in processes: return True except (FileNotFoundError, PermissionError, OSError): return False return False

def enable_cursor_grabbing(dpy: display.Display, root) -> None: global xinput_opcode reply = xinput.XIQueryDevice(dpy.display, deviceid=xinput.AllDevices, opcode=xinput_opcode) if reply is None or reply.devices is None: raise RuntimeError("Failed to query XInput2 devices.") for device in reply.devices: # only X devices marked as X slave pointer (mouse) (as in xinput list command) are important and being grabbed by this function if device.use != xinput.SlavePointer: continue try: # below function doesn't grab mouse cursor instantly, it sets listening to button events and then grabs it in position when xinput.ButtonPress event is received # reply = xinput.passive_grab_device( # alternative wrapper function call in python-xlib xinput.XIPassiveGrabDevice(display=dpy.display, opcode=xinput_opcode, time=X.CurrentTime, grab_window=root, cursor=X.NONE, detail=Right_Mouse_Button, deviceid=device.deviceid, modifiers=[xinput.AnyModifier], mask=xinput.ButtonReleaseMask, grab_type=xinput.GrabtypeButton, grab_mode=xinput.GrabModeAsync, paired_device_mode=xinput.GrabModeAsync, owner_events=False, ) # no need to provide xinput.ButtonPress in mask, because XIPassiveGrabDevice() function causes implicit listening to xinput.ButtonPress events dpy.flush() # provide only xinput.ButtonReleaseMask in mask to listen for this event during passive grab after right mouse button click global grab_enabled; grab_enabled = True except BadAccess: if verbose_flag: print("[XI2] xinput.XIPassiveGrabDevice denied (BadAccess).") except Exception as exception: if verbose_flag: print(f"[XI2] xinput.XIPassiveGrabDevice failed: {exception}")

def disable_cursor_grabbing(dpy: display.Display, root) -> None: global xinput_opcode reply = xinput.XIQueryDevice(dpy.display, deviceid=xinput.AllDevices, opcode=xinput_opcode) if reply is None or reply.devices is None: raise RuntimeError("Failed to query XInput2 devices.") for device in reply.devices: # only X devices marked as X slave pointer (mouse) (as in xinput list command) are important and being ungrabbed by this function if device.use != xinput.SlavePointer: continue try: # reply = xinput.passive_ungrab_device( # alternative wrapper function call in python-xlib xinput.XIPassiveUngrabDevice(display=dpy.display, opcode=xinput_opcode, grab_window=root, detail=Right_Mouse_Button, deviceid=device.deviceid, grab_type=xinput.GrabtypeButton, modifiers=[xinput.AnyModifier], ) dpy.flush() global grab_enabled; grab_enabled = False except Exception as exception: if verbose_flag: print(f"[XI2] xinput.XIPassiveUngrabDevice failed: {exception}")

def x_extension_check(dpy: display.Display) -> None: ext_info = dpy.query_extension('XTEST') if ext_info is not None and ext_info.present: if verbose_flag: print("XTest extension is available.") else: raise RuntimeError("XTest extension not available.") ext_info = dpy.query_extension('XInputExtension') if ext_info and ext_info.present: if verbose_flag: print("XInput extension is available.") else: raise RuntimeError("XInput extension not available.") global xinput_opcode; xinput_opcode = ext_info.major_opcode reply = xinput.XIQueryVersion(dpy.display, major_version=2, minor_version=0, opcode=xinput_opcode) if reply is None or reply.major_version < 2: raise RuntimeError("XInput2.0 not available (need at least 2.0).")

def event_loop(dpy: display.Display, root) -> None: global running, pressed, grab_enabled, original_xy while running: if grab_enabled and is_process_running(Whitelisted_Processes): if verbose_flag: print("Whitelisted process detected, disabling right mouse button grabbing.") disable_cursor_grabbing(dpy, root) elif (not grab_enabled) and (not is_process_running(Whitelisted_Processes)): if verbose_flag: print("Whitelisted process undetected, enabling right mouse button grabbing.") enable_cursor_grabbing(dpy, root)

x_event = dpy.next_event() # blocking call that suspends current thread until next X event is received
if verbose_flag: print(f&quot;Received X event: {x_event}&quot;) # X event code IDs are defined in X.h file
# generic events are generated only for key presses and key releases, only when passive grab is enabled
if x_event.type == GenericEvent and x_event.extension == xinput_opcode:
  if verbose_flag: print(f&quot;Received XI Extension event with code ID: {x_event.evtype}&quot;) # 4 = xinput.ButtonPress, 5 = xinput.ButtonRelease
  if x_event.data and x_event.data.detail == Right_Mouse_Button:
    if x_event.evtype == xinput.ButtonPress and pressed == False:
      # saving right mouse click location, no need to do anything else here, because mouse cursor will be automatically grabbed by XIPassiveGrabDevice listener on xinput.ButtonPress event
      original_xy = (int(x_event.data.root_x), int(x_event.data.root_y))
      if verbose_flag: print(f&quot;[XI2] right mouse button pressed: holding pointer lock at position: {original_xy}&quot;);
      pressed = True
    elif x_event.evtype == xinput.ButtonRelease and pressed == True:
      if verbose_flag: print(&quot;[XI2] right mouse button released: showing context menu and releasing pointer lock.&quot;)
      disable_cursor_grabbing(dpy, root) # release of grab lock and start of the short interval with no grab where simulated mouse click and release is delivered
      root.warp_pointer(*original_xy) # fix for a glitch that sometimes the cursor can be invisibly moved away even when mouse cursor is visually locked in the same position
      dpy.flush() # Ensure the changes are immediately flushed to X display
      xtest.fake_input(dpy, X.ButtonPress, Right_Mouse_Button) # simulate right mouse button click in the short interval with no passive grab
      xtest.fake_input(dpy, X.ButtonRelease, Right_Mouse_Button) # simulate right mouse button release in the short interval with no passive grab
      dpy.flush() # Ensure the changes are immediately flushed to X display
      enable_cursor_grabbing(dpy, root) # end of the short interval with no grab where simulated mouse click and release is delivered
      pressed = False

def handle_signal(signal_id, _) -> None: signals = {signal.SIGINT: "SIGINT", signal.SIGTERM: "SIGTERM"} if verbose_flag: print(f"Received {signals[signal_id]} signal, exiting gracefully.") global running; running = False os._exit(0)

def main() -> int: signal.signal(signal.SIGINT, handle_signal) # handler to exit application gracefully on signal signal.signal(signal.SIGTERM, handle_signal) # handler to exit application gracefully on signal

x_extension_check(x_display)

this line is used to trigger X events when windows hierarchy change to determine if right mouse button grabbing is necessary when whitelisted windows are being created or closed

root_window.change_attributes(event_mask=X.SubstructureNotifyMask) x_display.flush() if verbose_flag: print("Listening for right mouse button press and release X events, Ctrl+C to exit.") event_loop(x_display, root_window)

disable_cursor_grabbing(x_display, root_window) x_display.close() return 0

if name == "main": raise SystemExit(main())

1

This is the default behavior. You will experience the same thing even in Nautilus, which is the default manager. Actually, you will experience it everywhere. If you right click somewhere and you see a context menu, and, without releasing your right click, you hover above an action of the context menu and then release it, the corresponding action will be launched.

I can confirm this in all the programs I tried it into, thus, this is the default behavior under Ubuntu (and I find it handy, rather than having to manually left click to an action of the context menu)

hytromo
  • 4,901