1 //
2 // Copyright (c) 2010-2018 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using System;
9 using System.Runtime.InteropServices;
10 using System.Threading;
11 using System.Diagnostics;
12 
13 namespace Antmicro.Renode.Backends.Display.XInput
14 {
15     public static class XLibHelper
16     {
XLibHelper()17         static XLibHelper()
18         {
19             errorHandler = ((display, error) =>
20                 {
21                     var x11e = X11Error;
22                     if(x11e != null)
23                     {
24                         x11e();
25                     }
26                     return 0;
27                 });
28 
29             var fpointer = Marshal.GetFunctionPointerForDelegate(errorHandler);
30             XSetErrorHandler(fpointer);
31         }
32 
GrabCursorByWindow(int windowId)33         public static void GrabCursorByWindow(int windowId)
34         {
35             lock(locker)
36             {
37                 using(var l = new XLibLocker(DisplayHandle))
38                 {
39                     int res = 1;
40                     do
41                     {
42                         // Not always grabbing mouse works at the first time, so the loop is necessary
43                         res = XGrabPointer(DisplayHandle, windowId, true, 0x4 | 0x8 | 0x40, 1, 1, windowId, DefineEmptyCursor(windowId), 0);
44                         Thread.Sleep(100);
45                     }
46                     while(res != 0);
47 
48                     XGrabKeyboard(DisplayHandle, windowId, true, 1, 1, 0);
49                 }
50             }
51         }
52 
UngrabCursor()53         public static void UngrabCursor()
54         {
55             lock(locker)
56             {
57                 using(var l = new XLibLocker(DisplayHandle))
58                 {
59                     XUngrabPointer(DisplayHandle, 0);
60                     XUngrabKeyboard(DisplayHandle, 0);
61                 }
62 
63                 XCloseDisplay(DisplayHandle);
64                 DisplayHandle = XOpenDisplay(IntPtr.Zero);
65             }
66         }
67 
GetCursorPosition(int win)68         public static Tuple<int, int> GetCursorPosition(int win)
69         {
70             IntPtr r = IntPtr.Zero, c = IntPtr.Zero;
71             int rx = -1, ry = -1, wx = -1, wy = -1;
72             uint m = 0;
73 
74             using(var l = new XLibLocker(DisplayHandle))
75             {
76                 XQueryPointer(DisplayHandle, win, ref r, ref c, ref rx, ref ry, ref wx, ref wy, ref m);
77             }
78             return Tuple.Create(rx, ry);
79         }
80 
MoveCursorRelative(int dx, int dy)81         public static void MoveCursorRelative(int dx, int dy)
82         {
83             XWarpPointer(DisplayHandle, 0, 0, 0, 0, 0, 0, dx, dy);
84         }
85 
MoveCursorAbsolute(int windowId, int x, int y)86         public static void MoveCursorAbsolute(int windowId, int x, int y)
87         {
88             XWarpPointer(DisplayHandle, 0, windowId, 0, 0, 0, 0, x, y);
89         }
90 
StartEventListenerLoop(IInputHandler handler)91         public static void StartEventListenerLoop(IInputHandler handler)
92         {
93             lock(EventListenerThread)
94             {
95                 if(!EventListenerThread.IsAlive)
96                 {
97                     EventListenerThread.Name = "XLib event listener";
98                     EventListenerThread.IsBackground = true;
99                     EventListenerThread.Start(handler);
100                 }
101             }
102         }
103 
104         public static bool IsAvailable
105         {
106             get
107             {
108                 try
109                 {
110                     XOpenDisplay(IntPtr.Zero);
111                     return true;
112                 }
113                 catch(Exception)
114                 {
115                 }
116 
117                 return false;
118             }
119         }
120 
121         public static Action X11Error;
122 
123         [DllImport("libX11", EntryPoint = "XChangeActivePointerGrab")]
XChangeActivePointerGrab(IntPtr display, uint eventMask, IntPtr cursor, int time)124         internal extern static int XChangeActivePointerGrab(IntPtr display, uint eventMask, IntPtr cursor, /*Time*/int time);
125 
126         [DllImport("libX11", EntryPoint = "XGrabPointer")]
XGrabPointer(IntPtr display, int grabWindow, bool ownerEvents, uint eventMask, int pointerMode, int keyboardMode, int confineTo, IntPtr cursor, int time)127         internal extern static int XGrabPointer(IntPtr display, int grabWindow, bool ownerEvents, uint eventMask, int pointerMode, int keyboardMode, int confineTo, IntPtr cursor, /*Time*/int time);
128 
129         [DllImport("libX11", EntryPoint = "XUngrabPointer")]
XUngrabPointer(IntPtr display, int time)130         internal extern static int XUngrabPointer(IntPtr display, int time);
131 
132         [DllImport("libX11", EntryPoint = "XGrabKeyboard")]
XGrabKeyboard(IntPtr display, int grabWindow, bool ownerEvents, int pointerMode, int keyboardMode, int time)133         internal extern static int XGrabKeyboard(IntPtr display, int grabWindow, bool ownerEvents, int pointerMode, int keyboardMode, int time);
134 
135         [DllImport("libX11", EntryPoint = "XUngrabKeyboard")]
XUngrabKeyboard(IntPtr display, int time)136         internal extern static int XUngrabKeyboard(IntPtr display, int time);
137 
138         [DllImport("libX11", EntryPoint = "XCreatePixmapFromBitmapData")]
XCreatePixmapFromBitmapData(IntPtr display, int drawable, byte[] data, int width, int height, IntPtr fg, IntPtr bg, int depth)139         internal extern static IntPtr XCreatePixmapFromBitmapData(IntPtr display, int drawable, byte[] data, int width, int height, IntPtr fg, IntPtr bg, int depth);
140 
141         [DllImport("libX11", EntryPoint = "XFreePixmap")]
XFreePixmap(IntPtr display, IntPtr pixmap)142         internal extern static IntPtr XFreePixmap(IntPtr display, IntPtr pixmap);
143 
144         [DllImport("libX11", EntryPoint = "XCreatePixmapCursor")]
XCreatePixmapCursor(IntPtr display, IntPtr source, IntPtr mask, ref IntPtr foreground_color, ref IntPtr background_color, int x_hot, int y_hot)145         internal extern static IntPtr XCreatePixmapCursor(IntPtr display, IntPtr source, IntPtr mask, ref IntPtr foreground_color, ref IntPtr background_color, int x_hot, int y_hot);
146 
147         [DllImport("libX11", EntryPoint = "XWhitePixel")]
XWhitePixel(IntPtr display, int screen_no)148         internal extern static IntPtr XWhitePixel(IntPtr display, int screen_no);
149 
150         [DllImport("libX11", EntryPoint = "XBlackPixel")]
XBlackPixel(IntPtr display, int screen_no)151         internal extern static IntPtr XBlackPixel(IntPtr display, int screen_no);
152 
153         [DllImport("libX11", EntryPoint = "XRootWindow")]
XRootWindow(IntPtr display, int screen_number)154         internal extern static int XRootWindow(IntPtr display, int screen_number);
155 
156         [DllImport("libX11", EntryPoint = "XDefaultScreen")]
XDefaultScreen(IntPtr display)157         internal extern static int XDefaultScreen(IntPtr display);
158 
159         [DllImport("libX11", EntryPoint = "XOpenDisplay")]
XOpenDisplay(IntPtr display)160         internal extern static IntPtr XOpenDisplay(IntPtr display);
161 
162         [DllImport("libX11", EntryPoint = "XCloseDisplay")]
XCloseDisplay(IntPtr display)163         internal extern static int XCloseDisplay(IntPtr display);
164 
165         [DllImport("libX11", EntryPoint = "XWarpPointer")]
XWarpPointer(IntPtr display, int srcWindowId, int dstWindowId, int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY)166         internal extern static void XWarpPointer(IntPtr display, int srcWindowId, int dstWindowId, int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY);
167 
168         [DllImport("libX11", EntryPoint = "XQueryPointer")]
XQueryPointer(IntPtr display, int window, ref IntPtr root, ref IntPtr child, ref int rootX, ref int rootY, ref int winX, ref int winY, ref uint mask)169         internal extern static void XQueryPointer(IntPtr display, int window, ref IntPtr root, ref IntPtr child, ref int rootX, ref int rootY, ref int winX, ref int winY, ref uint mask);
170 
171         [DllImport("libX11", EntryPoint = "XNextEvent")]
XNextEvent(IntPtr display, IntPtr e)172         internal extern static void XNextEvent(IntPtr display, IntPtr e);
173 
174         [DllImport("libX11", EntryPoint = "XLockDisplay")]
XLockDisplay(IntPtr display)175         internal extern static void XLockDisplay(IntPtr display);
176 
177         [DllImport("libX11", EntryPoint = "XUnlockDisplay")]
XUnlockDisplay(IntPtr display)178         internal extern static void XUnlockDisplay(IntPtr display);
179 
180         [DllImport("libX11", EntryPoint = "XSetErrorHandler")]
XSetErrorHandler(IntPtr handler)181         internal extern static int XSetErrorHandler(IntPtr handler);
182 
183         [Conditional("DEBUG")]
DebugPrint(string str, params object[] p)184         private static void DebugPrint(string str, params object[] p)
185         {
186             Console.WriteLine(str, p);
187         }
188 
DefineEmptyCursor(int windowId)189         private static IntPtr DefineEmptyCursor(int windowId)
190         {
191             var c = Marshal.AllocHGlobal(80);
192             var cursor_bits = new byte[2 * 16];
193             IntPtr cursor;
194 
195             using(var l = new XLibLocker(DisplayHandle))
196             {
197                 var pixmap = XCreatePixmapFromBitmapData(DisplayHandle, windowId, cursor_bits, 16, 16, (IntPtr)1, (IntPtr)0, 1);
198                 cursor = XCreatePixmapCursor(DisplayHandle, pixmap, pixmap, ref c, ref c, 0, 0);
199                 XFreePixmap(DisplayHandle, pixmap);
200             }
201             Marshal.FreeHGlobal(c);
202 
203             return cursor;
204         }
205 
EventListenerLoop(object h)206         private static void EventListenerLoop(object h)
207         {
208             var handler = h as IInputHandler;
209             if(handler == null)
210             {
211                 return;
212             }
213 
214             int initMouseX = -1;
215             int initMouseY = -1;
216 
217             using(var ev = new XEvent())
218             {
219                 handler.Stop = false;
220                 while(!handler.Stop)
221                 {
222                     XNextEvent(DisplayHandle, ev.Pointer);
223                     var type = ev.Type;
224                     switch(type)
225                     {
226                     /*
227                     * Mouse cursor moved
228                     */
229                     case XEvent.MotionNotify:
230 
231                         var rx = ev.MotionNotifyXRoot;
232                         var ry = ev.MotionNotifyYRoot;
233 
234                         if(initMouseX == -1 && initMouseY == -1)
235                         {
236                             initMouseX = rx;
237                             initMouseY = ry;
238                             continue;
239                         }
240 
241                         var dx = rx - initMouseX;
242                         var dy = ry - initMouseY;
243 
244                         if(!(dx == 0 && dy == 0))
245                         {
246                             handler.MouseMoved(rx, ry, dx, dy);
247 
248                             if(handler.CursorFixed)
249                             {
250                                 MoveCursorAbsolute(XRootWindow(DisplayHandle, XDefaultScreen(DisplayHandle)), initMouseX, initMouseY);
251                             }
252                         }
253                         break;
254                     /*
255                     * Mouse button pressed
256                     */
257                     case XEvent.ButtonPress:
258                         handler.ButtonPressed(ev.Button);
259                         break;
260                     /*
261                     * Mouse button released
262                     */
263                     case XEvent.ButtonRelease:
264                         handler.ButtonReleased(ev.Button);
265                         break;
266                     /*
267                     * Keyboard key pressed
268                     */
269                     case XEvent.KeyPress:
270                         handler.KeyPressed(ev.KeyCode);
271                         break;
272                     /*
273                     * Keyboard key released
274                     */
275                     case XEvent.KeyRelease:
276                         handler.KeyReleased(ev.KeyCode);
277                         break;
278                     }
279                 }
280             }
281 
282             EventListenerThread = new Thread(EventListenerLoop);
283         }
284 
285         private static Thread EventListenerThread = new Thread(EventListenerLoop);
286         private static IntPtr DisplayHandle = XOpenDisplay(IntPtr.Zero);
287         private static Func<IntPtr, IntPtr, int> errorHandler;
288         private static object locker = new object();
289 
290         // inspired by OpenTK solution
291         private class XLibLocker : IDisposable
292         {
XLibLocker(IntPtr display)293             public XLibLocker(IntPtr display)
294             {
295                 this.display = display;
296                 XLockDisplay(display);
297             }
298 
Dispose()299             public void Dispose()
300             {
301                 XUnlockDisplay(display);
302             }
303 
304             private readonly IntPtr display;
305         }
306 
307         private class XEvent : IDisposable
308         {
XEvent()309             public XEvent()
310             {
311                 Pointer = Marshal.AllocCoTaskMem(296);
312             }
313 
Dispose()314             public void Dispose()
315             {
316                 Marshal.FreeCoTaskMem(Pointer);
317             }
318 
319             public IntPtr Pointer { get; private set; }
320 
321             public int Type
322             {
323                 get { return Marshal.ReadInt32(Pointer, 0x0); }
324             }
325 
326             public long Time
327             {
328                 get { return Marshal.ReadInt64(Pointer, 0x38); }
329             }
330 
331             public int MotionNotifyXRoot
332             {
333                 get { return Marshal.ReadInt32(Pointer, 0x48); }
334             }
335 
336             public int MotionNotifyYRoot
337             {
338                 get { return Marshal.ReadInt32(Pointer, 0x4c); }
339             }
340 
341             public int Button
342             {
343                 get { return Marshal.ReadInt32(Pointer, 0x54); }
344             }
345 
346             public int KeyCode
347             {
348                 get
349                 {
350                     // yes, it's the same as Button!
351                     return Marshal.ReadInt32(Pointer, 0x54);
352                 }
353             }
354 
355             public const int KeyPress = 0x2;
356             public const int KeyRelease = 0x3;
357             public const int ButtonPress = 0x4;
358             public const int ButtonRelease = 0x5;
359             public const int MotionNotify = 0x6;
360         }
361     }
362 }
363 
364