1 /++
2 	Cross platform window manager utilities for interacting with other unknown windows on the OS.
3 
4 	Based on [arsd.simpledisplay].
5 +/
6 module arsd.wmutil;
7 
8 public import arsd.simpledisplay;
9 
10 version(Windows)
11 	import core.sys.windows.windows;
12 
13 static assert(UsingSimpledisplayX11 || UsingSimpledisplayWindows, "wmutil only works on X11 or Windows");
14 
15 static if (UsingSimpledisplayX11) {
16 	extern(C) nothrow @nogc {
17 		Atom* XListProperties(Display *display, Window w, int *num_prop_return);
18 		Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
19 		Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
20 	}
21 }
22 
23 /// A foreachable object that iterates window children
24 struct WindowChildrenIterator {
25 	NativeWindowHandle parent;
26 
27 	version(Windows)
28 	struct EnumParams {
29 		int result;
30 		int delegate(NativeWindowHandle) dg;
31 		Exception ex;
32 	}
33 
34 
35 
36 	version(Windows)
37 	extern(Windows)
38 	nothrow private static int helper(HWND window, LPARAM lparam) {
39 		EnumParams* args = cast(EnumParams*)lparam;
40 		try {
41 			args.result = args.dg(window);
42 			if (args.result)
43 				return 0;
44 			else
45 				return 1;
46 		} catch (Exception e) {
47 			args.ex = e;
48 			return 0;
49 		}
50 	}
51 
52 	///
53 	int opApply(int delegate(NativeWindowHandle) dg) const {
54 		version (Windows) {
55 			EnumParams params;
56 
57 			// the cast is cuz druntime seems to have a wrong definition here, missing the const
58 			EnumChildWindows(cast(void*) parent, &helper, cast(LPARAM)&params);
59 
60 			if (params.ex)
61 				throw params.ex;
62 
63 			return params.result;
64 		} else static if (UsingSimpledisplayX11) {
65 			int result;
66 			Window unusedWindow;
67 			Window* children;
68 			uint numChildren;
69 			Status status = XQueryTree(XDisplayConnection.get(), parent, &unusedWindow, &unusedWindow, &children, &numChildren);
70 			if (status == 0 || children is null)
71 				return 0;
72 			scope (exit)
73 				XFree(children);
74 
75 			foreach (window; children[0 .. numChildren]) {
76 				result = dg(window);
77 				if (result)
78 					break;
79 			}
80 			return result;
81 		} else
82 			static assert(0);
83 
84 	}
85 }
86 
87 ///
88 WindowChildrenIterator iterateWindows(NativeWindowHandle parent = NativeWindowHandle.init) {
89 	static if (UsingSimpledisplayX11)
90 		if (parent == NativeWindowHandle.init)
91 			parent = RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get));
92 
93 	return WindowChildrenIterator(parent);
94 }
95 
96 /++
97 	Searches for a window with the specified class name and returns the native window handle to it.
98 
99 	Params:
100 		className = the class name to check the window for, case-insensitive.
101 +/
102 NativeWindowHandle findWindowByClass(string className) {
103 	version (Windows)
104 		return findWindowByClass(className.toWStringz);
105 	else static if (UsingSimpledisplayX11) {
106 		import std.algorithm : splitter;
107 		import std.uni : sicmp;
108 
109 		auto classAtom = GetAtom!"WM_CLASS"(XDisplayConnection.get());
110 		Atom returnType;
111 		int returnFormat;
112 		arch_ulong numItems, bytesAfter;
113 		char* strs;
114 		foreach (window; iterateWindows) {
115 			if (0 == XGetWindowProperty(XDisplayConnection.get(), window, classAtom, 0, 64, false, AnyPropertyType, &returnType, &returnFormat, &numItems, &bytesAfter, cast(void**)&strs)) {
116 				scope (exit)
117 					XFree(strs);
118 				if (returnFormat == 8) {
119 					foreach (windowClassName; strs[0 .. numItems].splitter('\0')) {
120 						if (sicmp(windowClassName, className) == 0)
121 							return window;
122 					}
123 				}
124 			}
125 		}
126 		return NativeWindowHandle.init;
127 
128 	}
129 }
130 
131 /// ditto
132 version (Windows)
133 NativeWindowHandle findWindowByClass(LPCTSTR className) {
134 	return FindWindow(className, null);
135 }
136 
137 /++
138 	Get the PID that owns the window.
139 
140 	Params:
141 		window = The window to check who created it
142 	Returns: the PID of the owner who created this window. On windows this will always work and be accurate. On X11 this might return -1 if none is specified and might not actually be the actual owner.
143 +/
144 int ownerPID(NativeWindowHandle window) @property {
145 	version (Windows) {
146 		DWORD ret;
147 		GetWindowThreadProcessId(window, &ret);
148 		return cast(int) ret;
149 	} else static if (UsingSimpledisplayX11) {
150 		auto pidAtom = GetAtom!"_NET_WM_PID"(XDisplayConnection.get());
151 		Atom returnType;
152 		int returnFormat;
153 		arch_ulong numItems, bytesAfter;
154 		uint* ints;
155 		if (0 == XGetWindowProperty(XDisplayConnection.get(), window, pidAtom, 0, 1, false, AnyPropertyType, &returnType, &returnFormat, &numItems, &bytesAfter, cast(void**)&ints)) {
156 			scope (exit)
157 				XFree(ints);
158 			if (returnFormat < 64 && numItems > 0) {
159 				return *ints;
160 			}
161 		}
162 		return -1;
163 	}
164 }
165 
166 unittest {
167 	import std.stdio;
168 	auto window = findWindowByClass("x-terminal-emulator");
169 	writeln("Terminal: ", window.ownerPID);
170 	foreach (w; iterateWindows)
171 		writeln(w.ownerPID);
172 }