5

I was using WinAPI SetWindowsHookEx and OS X objective-c [NSEvent addLocalMonitorForEventsMatchingMask:handler:] which both set up a callback and then i run the event loop endlessly and the callback triggers whenver it needs.

I insert this hook into just my process/application (but it would be cool if I could do system wide as well). As users do mouse combinations I track them, and if a combination matches a certain pattern, it blocks the last mouse event and does a certain function.

I was wondering what would be the x11 equivalent?

I found this topic: X11 Mouse Movement Event

But that seems to monitor absolutely all events, and he's just filtering out the mouse ones. This one is also a locking non-callback method, which is ok because i'm running this code from a dedicated thread. But ideally I would prefer a callback method because my main thread has to send messages to this thread like about the active window changing, and if its stuck in a loop it will never let up to get that active window change message.

Community
  • 1
  • 1
Noitidart
  • 35,443
  • 37
  • 154
  • 323
  • 1
    [This tutorial](https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html) may be useful in the future. [Writing callback functions in XLib](http://www.linuxforums.org/forum/desktop-x-windows/91793-writing-callback-functions-xlib-post459233.html?s=a403c153e0bfc1b6be9e747990450bd3#post459233) is probably useful for you in this case. Basically, you handle events the same way you do in Windows. However, you manually filter events and ultimately you call a specific callback function as you might also do in your window procedure in Windows. –  Aug 28 '15 at 03:46
  • 2
    `xAttributes.event_mask = ...` you are supposed to build your own mask, not blindly copy the example. – n. m. could be an AI Aug 29 '15 at 07:23
  • Thanks Chrono and @n.m. I will look into those and report back, I'm going to test it out within today or so. Thanks! :) – Noitidart Aug 29 '15 at 19:16
  • @ChronoKitsune that topic seems to be "You don't. With Xlib, you have to poll for events." is that true? So no callbacks possibility? :( http://www.linuxforums.org/forum/desktop-x-windows/91793-writing-callback-functions-xlib-post459231.html?s=f969aad42b3a2100f881e08afa6d31a0#post459231 – Noitidart Aug 29 '15 at 21:12
  • 1
    @Noitidart There's also [XCB's mouse movement events](http://xcb.freedesktop.org/tutorial/events/#mousemovementevents) instead of Xlib, but you're right. Instead of adding a hook, you're using the regular message/event loop to filter events and handle the input, similar to handling the `WM_MOUSEMOVE` message in a window procedure in Windows. You just move the logic to the message loop by setting the event mask as necessary. If you're thinking this seems more difficult than it should be, X is fundamentally different from Windows: X sends and receives messages to/from a server; Windows doesn't. –  Aug 29 '15 at 23:29
  • Thanks @ChronoKitsune i think you're right I think to reach a cross platform thing Ill put the logic into the callback and a mutex where within the callback it checks for updates for executing. The XCB thing is awesome thanks! Ill investigate that too :) – Noitidart Aug 30 '15 at 01:16
  • Hey @ChronoKitsune and n.m. I have a question. So I decided I'll have this loop/poll running in a thread, but how would I cancel that poll? I can't make it watch a pipe like with file watching api's. Any ideas? – Noitidart Aug 30 '15 at 19:23
  • 2
    Use a pthreads condition variable. – n. m. could be an AI Aug 30 '15 at 20:03
  • Thanks @n.m. I dont know about that but Ill investigate it. If you have some sample could I would really love to see please. :D – Noitidart Aug 30 '15 at 21:03
  • Hey @n.m. I wasn't able to find something good with pthreads condition variable, I'm not a C expert I mostly just port from C to js-ctypes. I tried looking for an example with keywords of `XGrabPointer` and `pthread` in it on github but had no luck, may you please help me with such an example. Thanks so much – Noitidart Aug 30 '15 at 23:19
  • @ChronoKitsune I was checking out the XCB events thing, and it seems only if the window is focused it will get the mouse events right? Like if the window is not focused and the user does a scroll wheel, i wont receive that correct? – Noitidart Nov 01 '15 at 10:15
  • 1
    @Noitidart [Try it yourself :-)](http://pastebin.com/UJaAMFDd) The events are received even if the window doesn't have input focus, including clicks/taps and movement, though it only receives events for the exposed part of the window's client area, not the obscured parts that are hidden behind another window (my window manager allows me to hold Shift to click a window without giving it focus). However, pointer dragging persists even when leaving the client area of the window. For this reason, you could receive extra `XCB_LEAVE_NOTIFY` events, and pointer dragging must be handled specially. –  Nov 01 '15 at 22:57
  • Thanks so much @Chrono i asked first because i do all this work in ctypes (i dont have an environment to do c) Ill definitely try this out! I dont understand that exposed part but as I write it up Im sure it will make sense, thanks so much!! Were you able to block that event from dispatching to others? Just curious because this is exactly how I did it in WinAPI, i created a hidden window which received global events, and I would eat up/block, or let the event continue. – Noitidart Nov 02 '15 at 05:13
  • 1
    @Noitidart It's just a matter of adding the focus change events to the mask and listening for them. When you receive a "focus out" event, you would stop handling certain things like mouse movement and such. This could be done either by altering the window attributes to change the event mask or by merely using a variable that determines whether to check the remaining events you're listening for or not. "Focus in" events would then trigger listening for the events that were disabled such as button presses. –  Nov 02 '15 at 06:51
  • 1
    I should note that my example isn't complete. One possible issue is that the event response type could be 0, signalling an error. Other issues are likely present, but I created that merely to demonstrate things. In any case, I hope it helps. If you don't have a C environment, you might download a Linux distribution and install it in something like VirtualBox (on Windows) or Parallels (on OS X). –  Nov 02 '15 at 07:00
  • Thanks @Chrono! I will update you as soon as i test it :) – Noitidart Nov 02 '15 at 07:05
  • Hi there @ChronoKitsune I tried your method thnks so much I finally ctype'd up everything :) Here it is - https://github.com/Noitidart/MouseControl/blob/master/modules/workers/MMSyncWorker.js#L920-L987 - but one major issue is that, mouse button press/release event that happen outside the created window are not being logged :( - are you sure your window was getting pointer press/release events that happened outside of it? (in my linked writeup it is only listening to first event its not looping this is on purpose just for testing) – Noitidart Nov 23 '15 at 10:34
  • Here's a youtube screencast of me showing that its not working when i click outside the window: https://www.youtube.com/watch?v=_YvAiXv7IM8 – Noitidart Nov 23 '15 at 11:28
  • 1
    @Noitidart Congrats. Regarding the button press/release events, I was referring to a window that you have created. For example, I can press inside the window, drag my cursor outside of the window, and my window manager will send a release event to the X server from that window. I'm not sure if this behavior applies to all X implementations, but it does on mine. –  Nov 24 '15 at 00:39
  • 1
    My specific window manager allows me to register a button press in my window without making it the active window: create the window, switch to another program to make your window inactive, hold Shift, and click on your inactive window. You may not be able to emulate this behavior with JS... I don't really know for certain. –  Nov 24 '15 at 00:41
  • Ah darn, thanks very much. I think I can't escape GTK, I'll use the gtk_window_add_filter :( – Noitidart Nov 24 '15 at 03:36

1 Answers1

6

You will probably have to patch the kernel for this if you want this to work globally. Some background.

I'm not sure about OS X but Windows is a very insecure OS. For example, every process can install a hook via SetWindowsHookEx and monitor the mouse and keyboard - it's basically a key logger. A few years ago, no anti virus tool would report this. I don't know how it is today.

But at the core, Windows is a cooperative OS. That means the GUI runs the computer. The application which has control (= the active one) gets all the events. If the application locks up, Windows locks up (mouse and keyboard are dead). If you click in another window and the active application says "No", then the new window doesn't become active. I remember that something was done to make things better but that's one of the reasons why Windows was so unstable in the past - one bug in some app and the whole system becomes buggy.

On Unix, the kernel doesn't care about the UI (which comes with it's own bag of problems). Instead, there is a program (a normal process) called the X server. From the kernel point of view, this program isn't any different from the others. The kernel handles mouse and keyboard. If X locks up, the keyboard still works (so you can switch to a text console, for example).

That means X reads devices like /dev/input/mice (which merges all mouse events from all the mice that are currently connected to your computer). Your keyboard is somewhere under /dev/input/by-id/. These devices are maintained be the kernel and used by X. X is just a customer here. Kernel as control.

If a program uses the X library, that means it creates a socket connection to the X server. The server processes the mouse and keyboard events sent by the kernel devices. These are turned into XEvent structures and send to the client. Rendering happens in the server, the client sends drawing commands to the server.

Which makes it hard to take control of mouse and keyboard from an X client - it's pretty far away from the source. If you create artificial events, they get flagged as "synthetic" and most programs ignore those - they are a security threat.

The devices listed above can only be read by root, so it's also not easy to listen to everything that the user does.

All in all, if you want to do this for all programs on the X display, you will need a programming running as root and probably a kernel module which allows you to inject events and expose them as a new event device under /dev/input/. And you need to configure X to listen to your new device. And even then, I don't think you can cancel events from other devices, just add your own.

If you need this just for a single application, then things are much easier. First, you need to add an event listener to all the windows which you create. In the handler, you can then analyze the mouse movements. Use the necessary Button*Mask and *MotionMask bits to get the events that you need.

If you don't care about security, you can then make your application accept synthetic events and just inject your new events. The problem is that X has no concept of "cancel event". You may be able to use the event propagation rules to achieve what you want; see "Propagation of Device Events" on this page: http://menehune.opt.wfu.edu/Kokua/Irix_6.5.21_doc_cd/usr/share/Insight/library/SGI_bookshelves/SGI_Developer/books/XLib_PG/sgi_html/ch08.html

You should probably also read chapter 2 which gives an overview of X.

One more thing: X is not thread safe. You must not call X functions from outside of the main thread. If you do, you'll get errors or your program will crash.

Related:

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • wow thank you so much I'm reading right now. Btw do you use Firefox I'm trying to use c and x11 instead of javascript to make this addon: https://addons.mozilla.org/en-US/firefox/addon/mousecontrol/ because javascript could not block mouseevents from top level, so things like flash and other plugins would steal focus. But if you try out the mousecontrol version there It would give you perfect idea what I am trying to do. :) Im not too good with communication of ideas :( – Noitidart Oct 23 '15 at 22:15
  • I'm reading this a lot thanks so much. I am runing this code via ctypes from Firefox. So ideally just need to hook/monitor and block the mouse events in current Firefox process (user may have multiple Firefoxs open). Still reading but just thought to update this as I was reading :) Thank you for an incredibly indepth reply I learned about windows too :) – Noitidart Oct 23 '15 at 22:29
  • Ah ok I think I'm getting it. So I'm going to froget system side and focus on just application. Then I am going to make a window, then use XGrabPointer to divert (i cant block i can only divert) the mouse events to this window, which will effectively block it in the window the user is in. And I add event listener by doing `XSelectInput` on each of the windows open in my application? :) As I did here: https://github.com/Noitidart/MouseControl/blob/master/modules/workers/MMSyncWorker.js#L733 ? – Noitidart Oct 23 '15 at 22:35
  • Hey @Aron how does gdk_window_add_filter with param of null work? It uses x no? Is there a place I can look up what the gdk functions are doing? https://developer.gnome.org/gdk3/stable/gdk3-Windows.html#gdk-window-add-filter – Noitidart Oct 24 '15 at 10:07
  • 1
    I don't know much about GTK+. But if you can run C code from within Firefox, a better solution is probably a local event loop: https://marc.info/?l=gtk-app-devel&m=97373050608317&w=2 You should also look into modal windows: https://developer.gnome.org/gtk3/stable/GtkWindow.html#gtk-window-set-modal – Aaron Digulla Oct 26 '15 at 10:29
  • Thanks! Checking now :) – Noitidart Oct 26 '15 at 10:46