12

I'm trying to change the behavior of the right click menu in ubuntu 10.10 because I use Windows 7 and Ubuntu.

When you right click you need to hold the right click and select something.

In Windows the is different right click (release) select something with left click

Is there a way to change this in ubuntu?

I'm running the ubuntu in vmware 7

Is that behavior normal?

dv3500ea
  • 37,754
Berty
  • 223

4 Answers4

21

It's really annoying behaviour for mouse when it opens context menu on right-click-down event, I tried to fix it in driver level but I couldn't. But I found very reliable workaround to fix it.

1) Install Easystroke Gesture Recognition from repository

sudo apt-get install easystroke

2) Open Easystroke then go to preferences tab, in Behavior section click Gesture Button

and right click on the rectangular area in Select a Mouse or Pen Button window.

3) In Timeout Profile dropdown list pick up Timeout Off

That's all! You don't need to add an action

kenn
  • 5,232
  • Thanks a lot, I poured some water into my external Thinkpad USB keyboard and now the right and middle click trigger at the same time so I couldn't scroll any more with the middle click. With this solution, it is bearable again (still some issues but this will do until I get replacement parts). – nh2 Jul 18 '18 at 15:11
  • This does work, but damn you can now draw on screen with right click... overkill. But right click works as in windows. – Cipi Mar 19 '19 at 17:14
  • 2
    @Cipi you can disable drawing on screen. easystroke show >preferences>Method to show gestures>None – kenn Mar 19 '19 at 17:45
  • @kenn yes, but they're still being handled. You get the annoying PING when you rightclick-drag, but.. who cares... it works. :D – Cipi Mar 20 '19 at 16:28
  • 1
    The default Ubuntu behavior is kind of mystifying. – Eric Walker Jul 05 '22 at 13:33
  • 3
    Lifesaver for VSCode and not limited to Ubuntu. I had this annoying context menu closes immediately after right click bug in VSCode where you have to hold right mouse button to be able to select anything in the menu. This easystroke fixes it completely. I am running Manjaro with i3wm. – daniel451 Oct 02 '22 at 16:01
  • easystroke was removed from newer Ubuntu repositories, so it can't be installed this easily anymore. Also, this delays the mouse down event in general, not just the context menu. Minecraft, for example, will place blocks after you release the mouse rather than as soon as you click. And you won't be able to hold right click long enough to aim and fire a bow. – Grant Gryczan Jan 31 '25 at 21:26
6

At least in newer Ubuntu versions, this is a design decision. There seems to be no way to change the default behaviour.

Behavior: click and hold the right button and then release on the chosen menu option.

Sources:

user1251007
  • 1,131
  • 2
  • 12
  • 30
1

This is not the normal behaviour in Ubuntu. The normal behaviour is the same as you described for Windows.

As far as I know, there is no way to change this right click behaviour.

It might be a problem with running Ubuntu in vmware, although I can't think how this would make a difference. You could try dual booting or using a different virtualisation solution such as virtualbox.

Was it like this when you first installed Ubuntu? Also, are you running Ubuntu or Kubuntu? (You tagged your question with KDE, which is only installed by default in Kubuntu).

dv3500ea
  • 37,754
  • thx for your answer, i'll try the dual boot. the tag was a mistake. thx for your help :) – Berty Nov 02 '10 at 08:09
1

I wrote a C++ program 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 program is available here:
https://github.com/pangratt12345/X11-Cursor-Grabber-Cpp
I translated This C++ program into Python script here:
https://github.com/pangratt12345/X11-Cursor-Grabber-Python
https://askubuntu.com/a/1551385/1756067

This program is based on easystroke project
https://github.com/higersky/easystroke
This program encapsulates the minimal part of easystroke project in order to achieve functionality of grabbed mouse cursor after right mouse button click. Because easystroke project is quite large and uncommented, around 7500 lines of C++ code. The most important files of easystroke project for this fix are these: grabber.cc/grabber.h, handler.cc/handler.h, main.cc/main.h

This C++ program 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 program also uses Xinput2.0 and XTest extensions of X library. So to be able to run this you need to install these this way:
sudo apt install libx11-dev libxi-dev libxtst-dev

Easystroke project has many cool features, but most are not necessary for this problem. Can be a bit overkill as mentioned in the comments. Easystroke project has several problems:
  • vanish of mouse cursor after right mouse click
  • displacement of mouse cursor after right mouse cursor release
  • context menu may not show when mouse cursor has been dragged when holding right mouse button
  • some applications need free movement of mouse cursor when right mouse button is held, like Blender

This program addresses above problems, respectively:
  • 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
  • program has a whitelisted processes list, which disables/enables mouse cursor grabs when whitelisted process is detected/undetected respectively

Explanation on how this program works:
  • Program 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 C++ code:

#include <X11/Xlib.h> // using X11 Windows System library
#include <X11/extensions/XInput2.h> // used for grabbing input devices
#include <X11/extensions/XTest.h> // used for delivering simulated clicks and releases
#include <csignal>
#include <cstdio>
#include <atomic>
#include <fstream>
#include <dirent.h>
#include <set>
#include <map>

Display* x_display; // currently used X display Window root_window; // top-level window in X windows hierarchy std::atomic<bool> running(true); // atomic to prevent data races when multiple threads are used bool pressed = false, grab_enabled = false, verbose_flag = true; // if true verbose_flag then print all logs to stdout int original_x = 0, original_y = 0; // original location of right mouse button click int xinput_opcode = 131; // code used by XInput2.0 function calls, usually 131 constexpr int Right_Mouse_Button = Button3; // X Window constant for right mouse button = 3 static const std::set<std::string> Whitelisted_Processes = { // 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", };

bool is_process_running(const std::set<std::string>& processes) { DIR* dir = opendir("/proc"); // /proc directory contains all processes information in folders for each process if (!dir) return false; struct dirent* dir_entry; while ((dir_entry = readdir(dir)) != nullptr) { // loop over all entries in /proc folder if (!isdigit(dir_entry->d_name[0])) continue; // if folder name has letters in it then it is not a process info, then skip it std::string pid = dir_entry->d_name; // processes' directories in /proc folder have pids as their names std::string cmd_path = "/proc/" + pid + "/comm"; // full path to process comm file, which has only 1 line = process name std::ifstream cmd_file(cmd_path); if (!cmd_file.is_open()) continue; std::string process_name; std::getline(cmd_file, process_name); if (!process_name.empty() && processes.contains(process_name)) { // std::set::contains method requires at least C++20 closedir(dir); return true; } } closedir(dir); return false; }

void enable_cursor_grabbing(Display* dpy, Window root) { int number_of_devices = 0; XIDeviceInfo* devices = XIQueryDevice(dpy, XIAllDevices, &number_of_devices); if (!devices) throw std::runtime_error("Failed to query XInput2 devices.\n"); for (int i = 0; i < number_of_devices; ++i) { XIDeviceInfo& device = devices[i]; // 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 != XISlavePointer) continue; XIEventMask mask; unsigned char data[3] = {0}; mask.deviceid = device.deviceid; mask.mask_len = sizeof(data); mask.mask = data; XISetMask(mask.mask, XI_ButtonPress); // this line is unnecessary, because XIGrabButton() function causes implicit listening to XI_ButtonPress events (passive grab) XISetMask(mask.mask, XI_ButtonRelease); // this line is important to set listening for XI_ButtonRelease events, during passive grab after right mouse button click // XIGrabButton doesn't grab mouse cursor instantly, it sets listening to button events and then grabs it in position when XI_ButtonPress event is received (passive grab) XIGrabModifiers mods[1] = {{.modifiers = static_cast<int>(XIAnyModifier), .status = 0}}; XIGrabButton(dpy, device.deviceid, Right_Mouse_Button, root, None, GrabModeAsync, GrabModeAsync, False, &mask, 1, mods); } XIFreeDeviceInfo(devices); grab_enabled = true; }

void disable_cursor_grabbing(Display* dpy, Window root) { int number_of_devices = 0; XIDeviceInfo* devices = XIQueryDevice(dpy, XIAllDevices, &number_of_devices); if (!devices) throw std::runtime_error("Failed to query XInput2 devices.\n"); for (int i = 0; i < number_of_devices; ++i) { XIDeviceInfo& device = devices[i]; // 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 != XISlavePointer) continue; XIGrabModifiers mods[1] = {{.modifiers = static_cast<int>(XIAnyModifier), .status = 0}}; XIUngrabButton(dpy, device.deviceid, Right_Mouse_Button, root, 1, mods); } XIFreeDeviceInfo(devices); grab_enabled = false; }

void X_extension_check(Display* dpy) { int event_base, error_base, xtest_major, xtest_minor; if (XTestQueryExtension(dpy, &event_base, &error_base, &xtest_major, &xtest_minor)) { if (verbose_flag) printf("XTest extension is available.\n"); } else throw std::runtime_error("XTest extension not available\n"); int event, error; if (XQueryExtension(dpy, "XInputExtension", &xinput_opcode, &event, &error)) { if (verbose_flag) printf("XInput extension is available.\n"); } else throw std::runtime_error("XInput extension not available.\n"); int xi_major = 2, xi_minor = 0; if (XIQueryVersion(dpy, &xi_major, &xi_minor) != Success || xi_major < 2) throw std::runtime_error("XInput2.0 not available (need at least 2.0).\n"); }

void X_event_loop(Display* dpy, Window root) { while (running) { if(grab_enabled && is_process_running(Whitelisted_Processes)) { if(verbose_flag) printf("Whitelisted process detected, disabling right mouse button grabbing.\n"); disable_cursor_grabbing(dpy, root); } else if(!grab_enabled && !is_process_running(Whitelisted_Processes)) { if(verbose_flag) printf("Whitelisted process undetected, enabling right mouse button grabbing.\n"); enable_cursor_grabbing(dpy, root); }

XEvent x_event;
XNextEvent(dpy, &amp;x_event); // blocking call that suspends current thread until next X event is received
if(verbose_flag) printf(&quot;XNextEvent, X event code ID: %d\n&quot;, x_event.type); // X event code IDs are defined in X.h file
// generic events are generated only for key presses and key releases from XInput2.0 module, only when passive grab is enabled
if (x_event.type == GenericEvent &amp;&amp; XGetEventData(dpy, &amp;x_event.xcookie)) {
  if (x_event.xcookie.extension == xinput_opcode) {
    int xi_event_type = x_event.xcookie.evtype;
    XIDeviceEvent *xi_event = reinterpret_cast&lt;XIDeviceEvent *&gt;(x_event.xcookie.data); // NOLINT(modernize-use-auto) // auto warning suppression
    if(verbose_flag) printf(&quot;XI Extension event code ID: %d\n&quot;, x_event.xcookie.evtype); // 4 = XI_ButtonPress, 5 = XI_ButtonRelease
    if (xi_event &amp;&amp; xi_event-&gt;detail == Right_Mouse_Button) {
      if (xi_event_type == XI_ButtonPress &amp;&amp; !pressed) {
        // saving right mouse click location, no need to do anything else here, because mouse cursor will be automatically grabbed by XIGrabButton listener on XI_ButtonPress event (passive grab)
        original_x = (int) xi_event-&gt;root_x; original_y = (int) xi_event-&gt;root_y;
        if(verbose_flag) printf(&quot;[XI2] right mouse button pressed: holding pointer lock at position: (x: %d, y: %d)\n&quot;, original_x, original_y);
        pressed = true;
      }
      else if (xi_event_type == XI_ButtonRelease &amp;&amp; pressed) {
        if(verbose_flag) printf(&quot;[XI2] right mouse button released: showing context menu and releasing pointer lock.\n&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
        // fix for a glitch that sometimes the cursor can be invisibly moved away even when mouse cursor is visually locked in the same position
        XWarpPointer(dpy, None, root, 0, 0, 0, 0, original_x, original_y); // move the pointer back to the original position to assure that it stays in fixed position
        XFlush(dpy); // Ensure the changes are immediately flushed to X display
        XTestFakeButtonEvent(dpy, Right_Mouse_Button, True, CurrentTime); // simulate right mouse button click in the short interval with no passive grab
        XTestFakeButtonEvent(dpy, Right_Mouse_Button, False, CurrentTime); // simulate right mouse button release in the short interval with no passive grab
        XFlush(dpy); // 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;
      }
    }
  }
  XFreeEventData(dpy, &amp;x_event.xcookie);
}

} }

void handle_signal(int signal_id) { std::map<int, std::string> signals = { {SIGINT, "SIGINT"}, {SIGTERM, "SIGTERM"} }; if(verbose_flag) printf("Received %s signal, exiting gracefully.\n", signals[signal_id].c_str()); running = false; std::exit(0); }

int main() { signal(SIGINT, handle_signal); // handler to exit application gracefully on signal signal(SIGTERM, handle_signal); // handler to exit application gracefully on signal

if (!(x_display = XOpenDisplay(nullptr))) throw std::runtime_error("Failed to open X display.\n"); X_extension_check(x_display); root_window = DefaultRootWindow(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 XSelectInput(x_display, root_window, SubstructureNotifyMask); if(verbose_flag) printf("Listening for right mouse button press and release X events, Ctrl+C to exit.\n"); X_event_loop(x_display, root_window);

if(grab_enabled) disable_cursor_grabbing(x_display, root_window); XCloseDisplay(x_display); return 0; }

Below is CMakeLists.txt for above C++ code:

cmake_minimum_required(VERSION 3.10)
project(X11-Cursor-Grabber LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20) # at least C++20 is needed because std::set::contains method is used set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF)

find_package(X11 REQUIRED) add_executable(X11-Cursor-Grabber x-cursor-grabber.cpp) target_link_libraries(X11-Cursor-Grabber PRIVATE ${X11_LIBRARIES} ${X11_Xtst_LIB} ${X11_Xi_LIB}) target_include_directories(X11-Cursor-Grabber PRIVATE ${X11_INCLUDE_DIR})