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 	Image!BGR captureWindow(Window window)
75 	{
76 		// TODO haveX11
77 		auto result = execute(["import", "-window", text(window), "bmp:-"]);
78 		enforce(result.status == 0, "ImageMagick import failed");
79 		return result.output.parseBMP!BGR();
80 	}
81 
82 	Window findWindowByName(string name)
83 	{
84 		// TODO haveX11
85 		auto result = execute(["xdotool", "search", "--name", name]);
86 		enforce(result.status == 0, "xdotool failed");
87 		return result.output.chomp.to!Window;
88 	}
89 
90 	static if (haveX11)
91 	Rect!int getWindowGeometry(Window window)
92 	{
93 		auto dpy = getDisplay();
94 		Window child;
95 		XWindowAttributes xwa;
96 		XGetWindowAttributes(dpy, window, &xwa);
97 		XTranslateCoordinates(dpy, window, XRootWindow(dpy, 0), xwa.x, xwa.y, &xwa.x, &xwa.y, &child);
98 		return Rect!int(xwa.x, xwa.y, xwa.x + xwa.width, xwa.y + xwa.height);
99 	}
100 
101 	float ease(float t, float speed)
102 	{
103 		speed = 0.3f + speed * 0.4f;
104 		t = t * 2 - 1;
105 		t = (1-pow(1-abs(t), 1/speed)) * sign(t);
106 		t = (t + 1) / 2;
107 		return t;
108 	}
109 
110 	void easeMousePos(int x0, int y0, int x1, int y1, Duration duration)
111 	{
112 		auto xSpeed = uniform01!float;
113 		auto ySpeed = uniform01!float;
114 
115 		auto start = MonoTime.currTime();
116 		auto end = start + duration;
117 		while (true)
118 		{
119 			auto now = MonoTime.currTime();
120 			if (now >= end)
121 				break;
122 			float t = 1f * (now - start).total!"hnsecs" / duration.total!"hnsecs";
123 			setMousePos(
124 				x0 + cast(int)(ease(t, xSpeed) * (x1 - x0)),
125 				y0 + cast(int)(ease(t, ySpeed) * (y1 - y0)),
126 			);
127 			Thread.sleep(1.msecs);
128 		}
129 		x0 = x1;
130 		y0 = y1;
131 
132 		setMousePos(x1, y1);
133 	}
134 
135 	void mouseButton(int button, bool down)
136 	{
137 		// TODO haveX11
138 		enforce(spawnProcess(["xdotool", down ? "mousedown" : "mouseup", text(button)]).wait() == 0, "xdotool failed");
139 	}
140 }
141 
142 version (Windows)
143 {
144 	// TODO
145 }