Calling X11 code from C#

Written by on Modified on Read time: 2 min

I was (wrongly) under the impression that Unity’s new Input System didn’t support warping the mouse position on linux due to a game developer stating so.

And instead of doing the sensible thing and confirming it first before creating a mod to fix it… I created the mod to fix the non-issue.

Warping X11 mouse, in C

It was quite painful to try to find documentation to Xlib, and to also have to learn C code at the same time. I also found out that printf can just stop the program unexpectedly. Over some experimentation though, I managed to make a small test program that roughly did what I wanted to do from the C# mod:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <X11/Xlib.h>
#include <stdio.h>

int main() {
  Display *dpy = XOpenDisplay(NULL);
  Window window;
  int revert_to_return;
  XGetInputFocus(dpy, &window, &revert_to_return);
  XWarpPointer(dpy, None, window, None, None, None, None, 100, 100);
  XFlush(dpy);
}

Calling C from C#

So now that I had my C demo, all I had to do was to translate that into C#.

With a bit of searching online and guessing how the DllImport translated to linux, I did manage it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
[DllImport("libX11.so.6")]
private static unsafe extern int XWarpPointer(void* display, int src_w, int dest_w, int src_x, int src_y, uint src_width, uint src_height, int dest_x, int dest_y);
[DllImport("libX11.so.6")]
private static unsafe extern int* XOpenDisplay(void* display_name);
[DllImport("libX11.so.6")]
private static unsafe extern int* XFlush(void* display);
[DllImport("libX11.so.6")]
private static unsafe extern int XGetInputFocus(void* display, int* focus_return, int* revert_to_return);

private static unsafe void* DisplayPointer;

// Initialization
DisplayPointer = XOpenDisplay(null);
// Whenever the cursor position needs to be moved
int focus_return, revert_to_return;
XGetInputFocus(DisplayPointer, &focus_return, &revert_to_return);
Vector2 pos = Vector2.zero;
XWarpPointer(DisplayPointer, 0, focus_return, 0, 0, 0, 0, (int)pos.x, (int)pos.y);
XFlush(DisplayPointer);
Debug($"Set mouse pointer to ({(int)pos.x}, {(int)pos.y})");

In the end though I ended up scrapping all of that, since the new InputSystem did support it rather well:

1
2
Cursor.lockState = CursorLockMode.None;
Mouse.current.WarpCursorPosition(pos);

The reason I didn’t find out until much later, is that I didn’t do enough debug logging, and thought that the method just wasn’t working, when in fact it just wasn’t being called by the game’s code.