1 /** 2 * Control, screen-scrape, and send input to other 3 * graphical programs. 4 * 5 * License: 6 * This Source Code Form is subject to the terms of 7 * the Mozilla Public License, v. 2.0. If a copy of 8 * the MPL was not distributed with this file, You 9 * can obtain one at http://mozilla.org/MPL/2.0/. 10 * 11 * Authors: 12 * Vladimir Panteleev <vladimir@thecybershadow.net> 13 */ 14 15 module ae.sys.sendinput; 16 17 import core.thread; 18 import core.time; 19 20 import std.exception; 21 import std.random; 22 import std.string; 23 24 import ae.utils.graphics.color; 25 import ae.utils.graphics.image; 26 import ae.utils.geometry : Rect; 27 import ae.utils.math; 28 29 version (linux) 30 { 31 enum haveX11 = is(typeof({ import deimos.X11.X; })); 32 //version = HAVE_X11; 33 34 static if (haveX11) 35 { 36 pragma(lib, "X11"); 37 38 import deimos.X11.X; 39 import deimos.X11.Xlib; 40 import deimos.X11.Xutil; 41 } 42 43 import std.conv; 44 import std.process; 45 46 static if (haveX11) 47 { 48 private static Display* dpy; 49 Display* getDisplay() 50 { 51 if (!dpy) 52 dpy = XOpenDisplay(null); 53 enforce(dpy, "Can't open display!"); 54 return dpy; 55 } 56 static ~this() 57 { 58 if (dpy) 59 XCloseDisplay(dpy); 60 dpy = null; 61 } 62 } 63 64 void setMousePos(int x, int y) 65 { 66 static if (haveX11) 67 { 68 auto dpy = getDisplay(); 69 auto rootWindow = XRootWindow(dpy, 0); 70 XSelectInput(dpy, rootWindow, KeyReleaseMask); 71 XWarpPointer(dpy, None, rootWindow, 0, 0, 0, 0, x, y); 72 XFlush(dpy); 73 } 74 else 75 enforce(spawnProcess(["xdotool", "mousemove", text(x), text(y)]).wait() == 0, "xdotool failed"); 76 } 77 78 static if (haveX11) 79 alias Window = deimos.X11.X.Window; 80 else 81 alias Window = uint; 82 83 auto captureWindow(Window window) 84 { 85 static if (haveX11) 86 { 87 auto g = getWindowGeometry(window); 88 return captureWindowRect(window, Rect!int(0, 0, g.w, g.h)); 89 } 90 else 91 { 92 auto result = execute(["import", "-window", text(window), "bmp:-"]); 93 enforce(result.status == 0, "ImageMagick import failed"); 94 return result.output.parseBMP!BGR(); 95 } 96 } 97 98 void captureWindowRect(Window window, Rect!int r, ref Image!BGRA image) 99 { 100 static if (haveX11) 101 { 102 auto dpy = getDisplay(); 103 auto ximage = XGetImage(dpy, window, r.x0, r.y0, r.w, r.h, AllPlanes, ZPixmap).xEnforce("XGetImage"); 104 scope(exit) XDestroyImage(ximage); 105 106 enforce(ximage.format == ZPixmap, "Wrong image format (expected ZPixmap)"); 107 enforce(ximage.bits_per_pixel == 32, "Wrong image bits_per_pixel (expected 32)"); 108 109 alias COLOR = BGRA; 110 ImageRef!COLOR(ximage.width, ximage.height, ximage.chars_per_line, cast(COLOR*) ximage.data).copy(image); 111 } 112 else 113 assert(false, "TODO"); 114 } 115 116 auto captureWindowRect(Window window, Rect!int r) 117 { 118 Image!BGRA image; 119 captureWindowRect(window, r, image); 120 return image; 121 } 122 123 auto captureRect(Rect!int r, ref Image!BGRA image) 124 { 125 static if (haveX11) 126 { 127 auto dpy = getDisplay(); 128 return captureWindowRect(RootWindow(dpy, DefaultScreen(dpy)), r, image); 129 } 130 else 131 assert(false, "TODO"); 132 } 133 134 auto captureRect(Rect!int r) 135 { 136 Image!BGRA image; 137 captureRect(r, image); 138 return image; 139 } 140 141 auto getPixel(int x, int y) 142 { 143 static if (haveX11) 144 { 145 static Image!BGRA r; 146 captureRect(Rect!int(x, y, x+1, y+1), r); 147 return r[0, 0]; 148 } 149 else 150 assert(false, "TODO"); 151 } 152 153 Window findWindowByName(string name) 154 { 155 // TODO haveX11 156 auto result = execute(["xdotool", "search", "--name", name]); 157 enforce(result.status == 0, "xdotool failed"); 158 return result.output.chomp.to!Window; 159 } 160 161 static if (haveX11) 162 Rect!int getWindowGeometry(Window window) 163 { 164 auto dpy = getDisplay(); 165 Window child; 166 XWindowAttributes xwa; 167 XGetWindowAttributes(dpy, window, &xwa).xEnforce("XGetWindowAttributes"); 168 XTranslateCoordinates(dpy, window, XRootWindow(dpy, 0), xwa.x, xwa.y, &xwa.x, &xwa.y, &child).xEnforce("XTranslateCoordinates"); 169 return Rect!int(xwa.x, xwa.y, xwa.x + xwa.width, xwa.y + xwa.height); 170 } 171 172 float ease(float t, float speed) 173 { 174 import std.math : pow, abs; 175 speed = 0.3f + speed * 0.4f; 176 t = t * 2 - 1; 177 t = (1-pow(1-abs(t), 1/speed)) * sign(t); 178 t = (t + 1) / 2; 179 return t; 180 } 181 182 static if (haveX11) 183 T xEnforce(T)(T cond, string msg) 184 { 185 return enforce(cond, msg); 186 } 187 188 void easeMousePos(int x0, int y0, int x1, int y1, Duration duration) 189 { 190 auto xSpeed = uniform01!float; 191 auto ySpeed = uniform01!float; 192 193 auto start = MonoTime.currTime(); 194 auto end = start + duration; 195 while (true) 196 { 197 auto now = MonoTime.currTime(); 198 if (now >= end) 199 break; 200 float t = 1f * (now - start).total!"hnsecs" / duration.total!"hnsecs"; 201 setMousePos( 202 x0 + cast(int)(ease(t, xSpeed) * (x1 - x0)), 203 y0 + cast(int)(ease(t, ySpeed) * (y1 - y0)), 204 ); 205 Thread.sleep(1.msecs); 206 } 207 x0 = x1; 208 y0 = y1; 209 210 setMousePos(x1, y1); 211 } 212 213 void mouseButton(int button, bool down) 214 { 215 // TODO haveX11 216 enforce(spawnProcess(["xdotool", down ? "mousedown" : "mouseup", text(button)]).wait() == 0, "xdotool failed"); 217 } 218 } 219 220 version (Windows) 221 { 222 // TODO 223 }