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 	private Display* getDisplay()
46 	{
47 		static Display* dpy;
48 		if (!dpy)
49 			dpy = XOpenDisplay(null);
50 		enforce(dpy, "Can't open display!");
51 		return dpy;
52 	}
53 
54 	void setMousePos(int x, int y)
55 	{
56 		static if (haveX11)
57 		{
58 			auto dpy = getDisplay();
59 			auto rootWindow = XRootWindow(dpy, 0);
60 			XSelectInput(dpy, rootWindow, KeyReleaseMask);
61 			XWarpPointer(dpy, None, rootWindow, 0, 0, 0, 0, x, y);
62 			XFlush(dpy);
63 		}
64 		else
65 			enforce(spawnProcess(["xdotool", "mousemove", text(windowX + x), text(windowY + y)]).wait() == 0, "xdotool failed");
66 	}
67 
68 	alias Window = deimos.X11.X.Window;
69 
70 	Image!BGR captureWindow(Window window)
71 	{
72 		// TODO haveX11
73 		auto result = execute(["import", "-window", text(window), "bmp:-"]);
74 		enforce(result.status == 0, "ImageMagick import failed");
75 		return result.output.parseBMP!BGR();
76 	}
77 
78 	Window findWindowByName(string name)
79 	{
80 		// TODO haveX11
81 		auto result = execute(["xdotool", "search", "--name", name]);
82 		enforce(result.status == 0, "xdotool failed");
83 		return result.output.chomp.to!Window;
84 	}
85 
86 	Rect!int getWindowGeometry(Window window)
87 	{
88 		auto dpy = getDisplay();
89 		Window child;
90 		XWindowAttributes xwa;
91 		XGetWindowAttributes(dpy, window, &xwa);
92 		XTranslateCoordinates(dpy, window, XRootWindow(dpy, 0), xwa.x, xwa.y, &xwa.x, &xwa.y, &child);
93 		return Rect!int(xwa.x, xwa.y, xwa.x + xwa.width, xwa.y + xwa.height);
94 	}
95 
96 	float ease(float t, float speed)
97 	{
98 		speed = 0.3f + speed * 0.4f;
99 		t = t * 2 - 1;
100 		t = (1-pow(1-abs(t), 1/speed)) * sign(t);
101 		t = (t + 1) / 2;
102 		return t;
103 	}
104 
105 	void easeMousePos(int x0, int y0, int x1, int y1, Duration duration)
106 	{
107 		auto xSpeed = uniform01!float;
108 		auto ySpeed = uniform01!float;
109 
110 		auto start = MonoTime.currTime();
111 		auto end = start + duration;
112 		while (true)
113 		{
114 			auto now = MonoTime.currTime();
115 			if (now >= end)
116 				break;
117 			float t = 1f * (now - start).total!"hnsecs" / duration.total!"hnsecs";
118 			setMousePos(
119 				x0 + cast(int)(ease(t, xSpeed) * (x1 - x0)),
120 				y0 + cast(int)(ease(t, ySpeed) * (y1 - y0)),
121 			);
122 			Thread.sleep(1.msecs);
123 		}
124 		x0 = x1;
125 		y0 = y1;
126 
127 		setMousePos(x1, y1);
128 	}
129 
130 	void mouseButton(int button, bool down)
131 	{
132 		// TODO haveX11
133 		enforce(spawnProcess(["xdotool", down ? "mousedown" : "mouseup", text(button)]).wait() == 0, "xdotool failed");
134 	}
135 }
136 
137 version (Windows)
138 {
139 	// TODO
140 }