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 } 41 42 import std.conv; 43 import std.process; 44 45 static if (haveX11) 46 private Display* getDisplay() 47 { 48 static Display* dpy; 49 if (!dpy) 50 dpy = XOpenDisplay(null); 51 enforce(dpy, "Can't open display!"); 52 return dpy; 53 } 54 55 void setMousePos(int x, int y) 56 { 57 static if (haveX11) 58 { 59 auto dpy = getDisplay(); 60 auto rootWindow = XRootWindow(dpy, 0); 61 XSelectInput(dpy, rootWindow, KeyReleaseMask); 62 XWarpPointer(dpy, None, rootWindow, 0, 0, 0, 0, x, y); 63 XFlush(dpy); 64 } 65 else 66 enforce(spawnProcess(["xdotool", "mousemove", text(x), text(y)]).wait() == 0, "xdotool failed"); 67 } 68 69 static if (haveX11) 70 alias Window = deimos.X11.X.Window; 71 else 72 alias Window = uint; 73 74 auto captureWindow(Window window) 75 { 76 static if (haveX11) 77 { 78 auto g = getWindowGeometry(window); 79 return captureWindowRect(window, Rect!int(0, 0, g.w, g.h)); 80 } 81 else 82 { 83 auto result = execute(["import", "-window", text(window), "bmp:-"]); 84 enforce(result.status == 0, "ImageMagick import failed"); 85 return result.output.parseBMP!BGR(); 86 } 87 } 88 89 auto captureWindowRect(Window window, Rect!int r) 90 { 91 static if (haveX11) 92 { 93 auto dpy = getDisplay(); 94 auto ximage = XGetImage(dpy, window, r.x0, r.y0, r.w, r.h, AllPlanes, ZPixmap).xEnforce("XGetImage"); 95 scope(exit) XFree(ximage); 96 97 enforce(ximage.format == ZPixmap, "Wrong image format (expected ZPixmap)"); 98 enforce(ximage.bits_per_pixel == 32, "Wrong image bits_per_pixel (expected 32)"); 99 100 alias COLOR = BGRA; 101 return ImageRef!COLOR(ximage.width, ximage.height, ximage.chars_per_line, cast(COLOR*) ximage.data).copy(); 102 } 103 else 104 assert(false, "TODO"); 105 } 106 107 auto captureRect(Rect!int r) 108 { 109 static if (haveX11) 110 { 111 auto dpy = getDisplay(); 112 return captureWindowRect(RootWindow(dpy, DefaultScreen(dpy)), r); 113 } 114 else 115 assert(false, "TODO"); 116 } 117 118 Window findWindowByName(string name) 119 { 120 // TODO haveX11 121 auto result = execute(["xdotool", "search", "--name", name]); 122 enforce(result.status == 0, "xdotool failed"); 123 return result.output.chomp.to!Window; 124 } 125 126 static if (haveX11) 127 Rect!int getWindowGeometry(Window window) 128 { 129 auto dpy = getDisplay(); 130 Window child; 131 XWindowAttributes xwa; 132 XGetWindowAttributes(dpy, window, &xwa).xEnforce("XGetWindowAttributes"); 133 XTranslateCoordinates(dpy, window, XRootWindow(dpy, 0), xwa.x, xwa.y, &xwa.x, &xwa.y, &child).xEnforce("XTranslateCoordinates"); 134 return Rect!int(xwa.x, xwa.y, xwa.x + xwa.width, xwa.y + xwa.height); 135 } 136 137 float ease(float t, float speed) 138 { 139 import std.math : pow, abs; 140 speed = 0.3f + speed * 0.4f; 141 t = t * 2 - 1; 142 t = (1-pow(1-abs(t), 1/speed)) * sign(t); 143 t = (t + 1) / 2; 144 return t; 145 } 146 147 static if (haveX11) 148 T xEnforce(T)(T cond, string msg) 149 { 150 return enforce(cond, msg); 151 } 152 153 void easeMousePos(int x0, int y0, int x1, int y1, Duration duration) 154 { 155 auto xSpeed = uniform01!float; 156 auto ySpeed = uniform01!float; 157 158 auto start = MonoTime.currTime(); 159 auto end = start + duration; 160 while (true) 161 { 162 auto now = MonoTime.currTime(); 163 if (now >= end) 164 break; 165 float t = 1f * (now - start).total!"hnsecs" / duration.total!"hnsecs"; 166 setMousePos( 167 x0 + cast(int)(ease(t, xSpeed) * (x1 - x0)), 168 y0 + cast(int)(ease(t, ySpeed) * (y1 - y0)), 169 ); 170 Thread.sleep(1.msecs); 171 } 172 x0 = x1; 173 y0 = y1; 174 175 setMousePos(x1, y1); 176 } 177 178 void mouseButton(int button, bool down) 179 { 180 // TODO haveX11 181 enforce(spawnProcess(["xdotool", down ? "mousedown" : "mouseup", text(button)]).wait() == 0, "xdotool failed"); 182 } 183 } 184 185 version (Windows) 186 { 187 // TODO 188 }