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.gui.base,
13 	ws.wm;
14 
15 
16 class BaseProperty {
17 
18 	x11.X.Window window;
19 	Atom property;
20 	string name;
21 
22 	abstract void update();
23 
24 }
25 
26 
27 class PropertyList {
28 
29 	private BaseProperty[] properties;
30 
31 	void add(BaseProperty property){
32 		properties ~= property;
33 	}
34 
35 	void remove(BaseProperty property){
36 		properties = properties.without(property);
37 	}
38 
39 	void remove(x11.X.Window window){
40 		properties = properties.filter!(a => a.window != window).array;
41 	}
42 
43 	void update(XPropertyEvent* event){
44 		foreach(property; properties){
45 			if(property.property == event.atom && property.window == event.window){
46 				property.update;
47 			}
48 		}
49 	}
50 
51 	void update(){
52 		foreach(property; properties){
53 			property.update;
54 		}
55 	}
56 
57 }
58 
59 mixin template PropertiesMixin(string name, string atom, int type, bool isList, Args...){
60 	mixin("Property!(type, isList) " ~ name ~ ";");
61 	static if(Args.length)
62 		mixin PropertiesMixin!Args;
63 }
64 
65 
66 struct Properties(Args...) {
67 
68 	mixin PropertiesMixin!Args;
69 	PropertyList propertyList;
70 
71 	void window(x11.X.Window window){
72 		propertyList = new PropertyList;
73 		void init(string name, string atom, int type, bool isList, Args...)(){
74 			mixin(name ~ " = new Property!(type, isList)(window, atom, propertyList);");
75 			static if(Args.length)
76 				init!Args;
77 		}
78 		init!Args;
79 	}
80 
81 	void update(Args...)(Args args){
82 		propertyList.update(args);
83 	}
84 
85 }
86 
87 
88 class Property(ulong Format, bool List): BaseProperty {
89 
90 	static if(Format == XA_CARDINAL || Format == XA_PIXMAP)
91 		alias Type = long;
92 	static if(Format == XA_ATOM)
93 		alias Type = Atom;
94 	static if(Format == XA_WINDOW)
95 		alias Type = x11.X.Window;
96 	static if(Format == XA_STRING)
97 		alias Type = string;
98 
99 	static if(List)
100 		alias FullType = Type[];
101 	else
102 		alias FullType = Type;
103 
104 	FullType value;
105 
106 	void delegate(FullType)[] handlers;
107 
108 	void opAssign(FullType value){
109 		this.value = value;
110 		set(value);
111 	}
112 
113 	void opOpAssign(string op)(void delegate(FullType) handler){
114 		static if(op == "~")
115 			handlers ~= handler;
116 		else static assert(false, op ~ "= not supported");
117 	}
118 
119 	alias value this;
120 
121 
122 	this(x11.X.Window window, string name, PropertyList list = null){
123 		if(list)
124 			list.add(this);
125 		this.window = window;
126 		this.name = name;
127 		property = XInternAtom(wm.displayHandle, name.toStringz, false);
128 		update;
129 	}
130 
131 	override void update(){
132 		value = get;
133 		foreach(handler; handlers)
134 			handler(value);
135 	}
136 
137 	ubyte* raw(ref ulong count){
138 		int di;
139 		ulong dl;
140 		ubyte* p;
141 		Atom da;
142 		if(XGetWindowProperty(wm.displayHandle, window, property, 0L, List || is(Type == string) ? long.max : 1, 0, is(Type == string) ? XInternAtom(wm.displayHandle, "UTF8_STRING", False) : Format, &da, &di, &count, &dl, &p) == 0 && p){
143 			return p;
144 		}
145 		return null;
146 	}
147 
148 	void rawset(T1, T2)(Atom format, int size, int mode, T1* data, T2 length){
149 		XChangeProperty(wm.displayHandle, window, property, format, size, mode, cast(ubyte*)data, cast(int)length);
150 	}
151 
152 	void request(Type[] data){
153 		XEvent e;
154 		e.type = ClientMessage;
155 		e.xclient.window = window;
156 		e.xclient.message_type = property;
157 		e.xclient.format = 32;
158 		e.xclient.data.l[0..data.length] = cast(long[])data;
159 		XSendEvent(wm.displayHandle, XDefaultRootWindow(wm.displayHandle), false, SubstructureNotifyMask|SubstructureRedirectMask, &e);
160 	}
161 
162 	FullType get(){
163 		ulong count = List ? 0 : 1;
164 		auto p = raw(count);
165 		if(!p)
166 			return FullType.init;
167 		FullType value;
168 		static if(List)
169 			value = (cast(Type*)p)[0..count].dup;
170 		else static if(is(Type == string))
171 			value = (cast(char*)p)[0..count].to!string;
172 		else
173 			value = *(cast(Type*)p);
174 		XFree(p);
175 		return value;
176 	}
177 
178 	void set(FullType data){
179 		static if(List){
180 			rawset(Format, 32, PropModeReplace, data.ptr, data.length);
181 		}else static if(is(Type == string)){
182 			rawset(XInternAtom(wm.displayHandle, "UTF8_STRING", False), 8, PropModeReplace, data.toStringz, data.length);
183 		}else{
184 			rawset(Format, 32, PropModeReplace, &data, 1);
185 		}
186 	}
187 
188 }