1 module ws.x.property;
2 
3 import
4 	std.algorithm,
5 	std.array,
6 	std.string,
7 	std.conv,
8 	x11.X,
9 	x11.Xlib,
10 	x11.Xutil,
11 	x11.Xatom,
12 	ws.x.atoms,
13 	ws.gui.base,
14 	ws.wm;
15 
16 
17 class BaseProperty {
18 
19 	x11.X.Window window;
20 	Atom property;
21 	string name;
22 
23 	abstract void update();
24 
25 }
26 
27 
28 class PropertyList {
29 
30 	private BaseProperty[] properties;
31 
32 	void add(BaseProperty property){
33 		properties ~= property;
34 	}
35 
36 	void remove(BaseProperty property){
37 		properties = properties.without(property);
38 	}
39 
40 	void remove(x11.X.Window window){
41 		properties = properties.filter!(a => a.window != window).array;
42 	}
43 
44 	void update(XPropertyEvent* event){
45 		foreach(property; properties){
46 			if(property.property == event.atom && property.window == event.window){
47 				property.update;
48 			}
49 		}
50 	}
51 
52 	void update(){
53 		foreach(property; properties){
54 			property.update;
55 		}
56 	}
57 
58 }
59 
60 mixin template PropertiesMixin(string name, string atom, int type, bool isList, Args...){
61 	mixin("Property!(type, isList) " ~ name ~ ";");
62 	static if(Args.length)
63 		mixin PropertiesMixin!Args;
64 }
65 
66 
67 struct Properties(Args...) {
68 
69 	mixin PropertiesMixin!Args;
70 	PropertyList propertyList;
71 
72 	void window(x11.X.Window window){
73 		propertyList = new PropertyList;
74 		void init(string name, string atom, int type, bool isList, Args...)(){
75 			mixin("this." ~ name ~ " = new Property!(type, isList)(window, atom, propertyList);");
76 			static if(Args.length)
77 				init!Args;
78 		}
79 		init!Args;
80 	}
81 
82 	void update(Args...)(Args args){
83 		propertyList.update(args);
84 	}
85 
86 }
87 
88 
89 class Property(ulong Format, bool List): BaseProperty {
90 
91 	bool exists;
92 
93 	static if(Format == XA_CARDINAL || Format == XA_PIXMAP || Format == XA_VISUALID)
94 		alias Type = long;
95 	static if(Format == XA_ATOM)
96 		alias Type = Atom;
97 	static if(Format == XA_WINDOW)
98 		alias Type = x11.X.Window;
99 	static if(Format == XA_STRING)
100 		alias Type = string;
101 
102 	static if(List)
103 		alias FullType = Type[];
104 	else
105 		alias FullType = Type;
106 
107 	FullType value;
108 
109 	void delegate(FullType)[] handlers;
110 
111 	void opAssign(FullType value){
112 		this.value = value;
113 		set(value);
114 	}
115 
116 	void opOpAssign(string op)(void delegate(FullType) handler){
117 		static if(op == "~")
118 			handlers ~= handler;
119 		else static assert(false, op ~ "= not supported");
120 	}
121 
122 	alias value this;
123 
124 
125 	this(x11.X.Window window, string name, PropertyList list = null){
126 		if(list)
127 			list.add(this);
128 		this.window = window;
129 		this.name = name;
130 		property = XInternAtom(wm.displayHandle, name.toStringz, false);
131 		update;
132 	}
133 
134 	this(x11.X.Window window, Atom property, PropertyList list = null){
135 		if(list)
136 			list.add(this);
137 		this.window = window;
138 		this.name = name;
139 		this.property = property;
140 		update;
141 	}
142 
143 	override void update(){
144 		auto newValue = get;
145 		foreach(handler; handlers)
146 			handler(newValue);
147 		value = newValue;
148 	}
149 
150 	ubyte* raw(ref ulong count){
151 		int di;
152 		ulong dl;
153 		ubyte* p;
154 		Atom da;
155 		if(XGetWindowProperty(wm.displayHandle, window, property, 0L, List || is(Type == string) ? long.max : 1, 0, is(Type == string) ? Atoms.UTF8_STRING : Format, &da, &di, &count, &dl, &p) == 0 && p){
156 			exists = true;
157 			return p;
158 		}
159 		exists = false;
160 		return null;
161 	}
162 
163 	void rawset(T1, T2)(Atom format, int size, int mode, T1* data, T2 length){
164 		XChangeProperty(wm.displayHandle, window, property, format, size, mode, cast(ubyte*)data, cast(int)length);
165 	}
166 
167 	void request(x11.X.Window window, Type[] data){
168 		XEvent e;
169 		e.type = ClientMessage;
170 		e.xclient.window = window;
171 		e.xclient.message_type = property;
172 		e.xclient.format = 32;
173 		e.xclient.data.l[0..data.length] = cast(long[])data;
174 		XSendEvent(wm.displayHandle, this.window, false, SubstructureNotifyMask|SubstructureRedirectMask, &e);
175 	}
176 
177 	void request(Type[] data){
178 		request(window, data);
179 	}
180 
181 	FullType get(){
182 		ulong count = List ? 0 : 1;
183 		auto p = raw(count);
184 		if(!p)
185 			return FullType.init;
186 		FullType value;
187 		static if(List){
188 			value = (cast(Type*)p)[0..count].dup;
189 		}else static if(is(Type == string))
190 			value = (cast(char*)p)[0..count].to!string;
191 		else
192 			value = *(cast(Type*)p);
193 		XFree(p);
194 		return value;
195 	}
196 
197 	void set(FullType data){
198 		static if(List){
199 			rawset(Format, 32, PropModeReplace, data.ptr, data.length);
200 		}else static if(is(Type == string)){
201 			rawset(Atoms.UTF8_STRING, 8, PropModeReplace, data.toStringz, data.length);
202 		}else{
203 			rawset(Format, 32, PropModeReplace, &data, 1);
204 		}
205 	}
206 
207 }
208 
209 
210 class PropertyError: Exception {
211 	this(string msg){
212 		super(msg);
213 	}
214 }
215 
216 
217 auto dispatchProperty(string name)(x11.X.Window window){
218 
219 	struct Proxy {
220 
221 		T get(T)(){
222 			ulong count;
223 			int format;
224 			ulong bytes_after;
225 			ubyte* p;
226 			Atom type;
227 
228 			if(XGetWindowProperty(wm.displayHandle, window, Atoms.opDispatch!name, 0L, long.max, 0, AnyPropertyType,
229 			   &type, &format, &count, &bytes_after, &p) == 0 && p){
230 
231 				scope(exit)
232 					XFree(p);
233 
234 				import std.stdio, std.traits, std.range;
235 
236 				static if(is(T == string)){
237 					return (cast(char*)p)[0..count].to!string;
238 				}else static if(isIterable!T){
239 					alias Type = ElementType!T;
240 					Type[] result;
241 					result.length = count;
242 					auto casted = cast(Type*)p;
243 					foreach(i; 0..count){
244 						result[i] = casted[i];
245 					}
246 					return result;
247 				}else{
248 					return *(cast(T*)p);
249 				}
250 
251 			}
252 			return T.init;
253 		}
254 
255 		void get(T)(void delegate(T) fn){
256 			ulong count;
257 			int format;
258 			ulong bytes_after;
259 			ubyte* p;
260 			Atom type;
261 
262 			if(XGetWindowProperty(wm.displayHandle, window, Atoms.opDispatch!name, 0L, long.max, 0, AnyPropertyType,
263 			   &type, &format, &count, &bytes_after, &p) == 0 && p){
264 
265 				import std.stdio, std.traits, std.range;
266 				writeln(type, ' ', format, ' ', count);
267 
268 				static if(is(T == string)){
269 					fn((cast(char*)p)[0..count].to!string);
270 				}else static if(isIterable!T){
271 					alias Type = ElementType!T;
272 					Type[] result;
273 					result.length = count;
274 					auto casted = cast(Type*)p;
275 					foreach(i; 0..count){
276 						result[i] = casted[i];
277 					}
278 					fn(result);
279 				}else{
280 					fn(cast(T*)p);
281 				}
282 
283 				XFree(p);
284 			}
285 		}
286 
287 		void get(T)(void function(T) fn){
288 			import std.functional;
289 			get(fn.toDelegate);
290 		}
291 
292 	}
293 
294 	return Proxy();
295 
296 }
297 
298 
299 auto props(x11.X.Window window){
300 
301 	struct Dispatcher {
302 
303 		auto opDispatch(string name)(){
304 			return dispatchProperty!name(window);
305 		}
306 
307 	}
308 
309 	return Dispatcher();
310 
311 }
312