1 module ws.wm.x11.window;
2 
3 version(Posix):
4 
5 import
6 	std.conv,
7 	std.uni,
8 	std.string,
9 	ws.draw,
10 	ws.wm,
11 	ws.gui.base,
12 	ws.list,
13 	ws.x.atoms,
14 	ws.x.draw,
15 	derelict.opengl3.gl3,
16 	ws.wm.x11.api;
17 
18 import derelictX = derelict.util.xtypes;
19 
20 __gshared:
21 
22 
23 class X11Window: Base {
24 
25 	Mouse.cursor cursor = Mouse.cursor.inherit;
26 	string title;
27 	bool isActive = true;
28 	WindowHandle windowHandle;
29 	GraphicsContext graphicsContext;
30 	List!WindowEvent eventQueue;
31 
32 	XIC inputContext;
33 	int oldX, oldY;
34 	int jumpTargetX, jumpTargetY;
35 	DrawEmpty _draw;
36 	int[2] _cursorPos;
37 
38 	bool _keyboardFocus;
39 	bool mouseFocus;
40 	Base _dragging;
41 	bool draggingUnfocus;
42 
43 	this(WindowHandle handle){
44 		assert(handle);
45 		windowHandle = handle;
46 		wmDelete = XInternAtom(wm.displayHandle, "WM_DELETE_WINDOW".toStringz, True);
47 		utf8 = XInternAtom(wm.displayHandle, "UTF8_STRING", false);
48 		netWmName = XInternAtom(wm.displayHandle, "_NET_WM_NAME".toStringz, False);
49 	}
50 
51 	XSetWindowAttributes windowAttributes;
52 
53 	Atom wmDelete;
54 	Atom utf8;
55 	Atom netWmName;
56 
57 	this(int w, int h, string t, bool override_redirect=false){
58 		hidden = true;
59 		size = [w, h];
60 		eventQueue = new List!WindowEvent;
61 
62 		auto eventMask =
63 				ExposureMask |
64 				StructureNotifyMask |
65 				SubstructureRedirectMask |
66 				KeyPressMask |
67 				KeyReleaseMask |
68 				KeymapStateMask |
69 				PointerMotionMask |
70 				ButtonPressMask |
71 				ButtonReleaseMask |
72 				EnterWindowMask |
73 				LeaveWindowMask |
74 				FocusChangeMask;
75 
76 		auto windowMask =
77 				CWBorderPixel |
78 				CWBitGravity |
79 				CWEventMask |
80 				CWColormap |
81 				CWBackPixmap |
82 				(override_redirect ? CWOverrideRedirect : 0);
83 
84 		auto root = XDefaultRootWindow(wm.displayHandle); 
85 
86 		windowAttributes.override_redirect = override_redirect;
87 		windowAttributes.background_pixmap = None;
88 		windowAttributes.event_mask = eventMask;
89 		windowAttributes.border_pixmap = None;
90 		windowAttributes.border_pixel = 0;
91 		windowAttributes.bit_gravity = NorthWestGravity;
92 		windowAttributes.colormap = XCreateColormap(wm.displayHandle, root, wm.graphicsInfo.visual, AllocNone);
93 
94 		windowHandle = XCreateWindow(
95 			wm.displayHandle,
96 			root,
97 			0, 0, cast(size_t)size.x, cast(size_t)size.y, 0,
98 			wm.graphicsInfo.depth,
99 			InputOutput,
100 			wm.graphicsInfo.visual,
101 			windowMask,
102 			&windowAttributes
103 		);
104 
105 		inputContext = XCreateIC(
106 			XOpenIM(wm.displayHandle, null, null, null),
107 			XNClientWindow, windowHandle,
108 			XNFocusWindow, windowHandle,
109 			XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
110 			null
111 		);
112 		XSelectInput(wm.displayHandle, windowHandle, eventMask);
113 		wmDelete = XInternAtom(wm.displayHandle, "WM_DELETE_WINDOW".toStringz, True);
114 		utf8 = XInternAtom(wm.displayHandle, "UTF8_STRING", false);
115 		netWmName = XInternAtom(wm.displayHandle, "_NET_WM_NAME".toStringz, False);
116 		XSetWMProtocols(wm.displayHandle, windowHandle, &wmDelete, 1);
117 		setTitle(t);
118 		drawInit;
119 		assert(windowHandle);
120 	}
121 
122 	override DrawEmpty draw(){
123 		return _draw;
124 	}
125 
126 	void draw(DrawEmpty draw){
127 		_draw = draw;
128 	}
129 
130 	override int[2] cursorPos(){
131 		return _cursorPos;
132 	}
133 
134 	override Base draggingChild(){
135 		return _dragging;
136 	}
137 
138 	override void onMouseButton(Mouse.button button, bool pressed, int x, int y){
139 		if(button == Mouse.buttonLeft){
140 			if(pressed){
141 				auto child = mouseChild;
142 				while(child && child.mouseChild){
143 					child = child.mouseChild;
144 				}
145 				_dragging = child;
146 			}else{
147 				_dragging = null;
148 				if(draggingUnfocus){
149 					onMouseFocus(false);
150 				}
151 			}
152 		}
153 		super.onMouseButton(button, pressed, x, y);
154 	}
155 
156 	override void show(){
157 		XMapWindow(wm.displayHandle, windowHandle);
158 	}
159 
160 	override void onShow(){
161 		hidden = false;
162 		resized(size);
163 	}
164 
165 	void close(){
166 		if(!isActive)
167 			return;
168 		hidden = true;
169 		isActive = false;
170 		XDestroyWindow(wm.displayHandle, windowHandle);
171 	}
172 
173 	override void hide(){
174 		XUnmapWindow(wm.displayHandle, windowHandle);
175 	}
176 
177 	override void onHide(){
178 		hidden = true;
179 	}
180 
181 	void swapBuffers(){
182 		glXSwapBuffers(wm.displayHandle, cast(uint)windowHandle);
183 	}
184 
185 	override void onDraw(){
186 		super.onDraw;
187 		draw.finishFrame;
188 	}
189 
190 	void onDestroy(){
191 		isActive = false;
192 		hidden = true;
193 		wm.windows.remove(this);
194 		draw.destroy;
195 	}
196 
197 	@property
198 	bool active(){
199 		return isActive;
200 	}
201 
202 	void onRawMouse(int x, int y){}
203 
204 
205 	override void setCursor(Mouse.cursor cursor){
206 		struct Cache {
207 			uint shape;
208 			ulong cached;
209 		}
210 		static Cache[] cache = [
211 			{XC_arrow, 0},
212 			{XC_top_left_arrow, 0},
213 			{XC_xterm, 0},
214 			{XC_crosshair, 0},
215 			{XC_sb_v_double_arrow, 0},
216 			{XC_sb_h_double_arrow, 0},
217 			{XC_top_right_corner, 0},
218 			{XC_top_left_corner, 0},
219 			{XC_bottom_right_corner, 0},
220 			{XC_bottom_left_corner, 0},
221 			{XC_hand1, 0},
222 		];
223 		ulong c = 0;
224 		if(cursor >= 0 && cursor <= cache.length){
225 			if(!cache[cursor].cached)
226 				cache[cursor].cached = XCreateFontCursor(wm.displayHandle, cache[cursor].shape);
227 			c = cache[cursor].cached;
228 		}else{
229 			switch(cursor){
230 				case Mouse.cursor.none:
231 					__gshared Cursor cursorNone = 0;
232 					if(cursorNone == 0){
233 						char[32] cursorNoneBits;
234 						foreach(ref ch; cursorNoneBits)
235 							ch = 0;
236 						XColor dontCare;
237 						Pixmap cursorNonePixmap;
238 						//memset(cursorNoneBits, 0, cursorNoneBits.sizeof);
239 						//memset(&dontCare, 0, dontCare.sizeof);
240 						cursorNonePixmap = XCreateBitmapFromData(
241 								wm.displayHandle,
242 								XDefaultRootWindow(wm.displayHandle),
243 								cursorNoneBits.ptr, 16, 16
244 						);
245 						if(cursorNonePixmap != 0){
246 							cursorNone = XCreatePixmapCursor(
247 								wm.displayHandle, cursorNonePixmap,
248 								cursorNonePixmap, &dontCare,
249 								&dontCare, 0, 0
250 							);
251 							XFreePixmap(wm.displayHandle, cursorNonePixmap);
252 						}
253 					}
254 					c = cast(int)cursorNone;
255 					break;
256 				case Mouse.cursor.inherit:
257 					c = 0;
258 					break;
259 				default: break;
260 			}
261 		}
262 		if(cursor == Mouse.cursor.inherit)
263 			XUndefineCursor(wm.displayHandle, windowHandle);
264 		else
265 			XDefineCursor(wm.displayHandle, windowHandle, c);
266 	}
267 
268 
269 	void setCursorPos(int x, int y){
270 		jumpTargetX = x;
271 		jumpTargetY = y;
272 		XWarpPointer(
273 				wm.displayHandle, XDefaultRootWindow(wm.displayHandle),
274 				windowHandle, 0,0,0,0, x, size.y - y
275 		);
276 		XFlush(wm.displayHandle);
277 	}
278 
279 	void setTitle(string t){
280 		title = t;
281 		if(!isActive)
282 			return;
283 		XTextProperty tp;
284 		char* c = cast(char*)title.toStringz;
285 		XStringListToTextProperty(&c, 1, &tp);
286 		XSetWMName(wm.displayHandle, windowHandle, &tp);
287 		XSetTextProperty(wm.displayHandle, windowHandle, &tp, Atoms._NET_WM_NAME);
288 	}
289 
290 
291 	bool gettextprop(WindowHandle w, Atom atom, ref string text){
292 		char** list;
293 		int n;
294 		XTextProperty name;
295 		XGetTextProperty(wm.displayHandle, w, &name, atom);
296 		if(!name.nitems)
297 			return false;
298 		if(name.encoding == Atoms.STRING){
299 			text = to!string(*name.value);
300 		}else{
301 			if(XmbTextPropertyToTextList(wm.displayHandle, &name, &list, &n) >= XErrorCode.Success && n > 0 && *list){
302 				text = (*list).to!string;
303 				XFreeStringList(list);
304 			}
305 		}
306 		XFree(name.value);
307 		return true;
308 	}
309 
310 	string getTitle(){
311 		Atom actType;
312 		size_t nItems, bytes;
313 		int actFormat;
314 		ubyte* data;
315 		XGetWindowProperty(
316 				wm.displayHandle, windowHandle, Atoms._NET_WM_NAME, 0, 0x77777777, False, Atoms.UTF8_STRING,
317 				&actType, &actFormat, &nItems, &bytes, &data
318 		);
319 		auto text = to!string(cast(char*)data);
320 		XFree(data);
321 		if(!text.length){
322 			if(!gettextprop(windowHandle, Atoms._NET_WM_NAME, text))
323 				gettextprop(windowHandle, Atoms.WM_NAME, text);
324 		}
325 		return text;
326 	}
327 
328 	GraphicsContext gcShare(){
329 		return glXCreateContext(wm.displayHandle, cast(derelictX.XVisualInfo*)wm.graphicsInfo, cast(__GLXcontextRec*)graphicsContext, True);
330 	}
331 
332 
333 	void makeCurrent(GraphicsContext c){
334 		glXMakeCurrent(wm.displayHandle, cast(uint)windowHandle, cast(__GLXcontextRec*)c);
335 	}
336 
337 	void onPaste(string){}
338 
339 	void processEvent(WindowEvent* e){
340 		if(!isActive)
341 			return;
342 		switch(e.type){
343 			case ConfigureNotify:
344 				if(size.x != e.xconfigure.width || size.y != e.xconfigure.height){
345 					resized([e.xconfigure.width, e.xconfigure.height]);
346 				}
347 				if(pos.x != e.xconfigure.x || pos.y != e.xconfigure.y){
348 					moved([e.xconfigure.x, e.xconfigure.y]);
349 				}
350 				break;
351 			case KeyPress:
352 				onKeyboard(cast(Keyboard.key)XLookupKeysym(&e.xkey,0), true);
353 				if(!isActive)
354 					break;
355 				char[25] str;
356 				KeySym ks;
357 				Status st;
358 				size_t l = Xutf8LookupString(inputContext, &e.xkey, str.ptr, 25, &ks, &st);
359 				foreach(dchar c; str[0..l])
360 					if(!c.isControl)
361 						onKeyboard(c);
362 				break;
363 			case KeyRelease: onKeyboard(cast(Keyboard.key)XLookupKeysym(&e.xkey,0), false); break;
364 			case MotionNotify:
365 				_cursorPos = [e.xmotion.x, size.y - e.xmotion.y];
366 				onMouseMove(e.xmotion.x, size.y - e.xmotion.y);
367 				if(distance(e.xmotion.x, jumpTargetX) > 1 || distance(e.xmotion.y, jumpTargetY) > 1)
368 				//if(e.xmotion.x != jumpTargetX || e.xmotion.y != jumpTargetY)
369 					onRawMouse((e.xmotion.x - oldX), (e.xmotion.y - oldY));
370 				else{
371 					jumpTargetX = int.max;
372 					jumpTargetY = int.max;
373 				}
374 				oldX = e.xmotion.x;
375 				oldY = e.xmotion.y;
376 				break;
377 			case ButtonPress: onMouseButton(e.xbutton.button, true, cursorPos.x, cursorPos.y); break;
378 			case ButtonRelease: onMouseButton(e.xbutton.button, false, cursorPos.x, cursorPos.y); break;
379 			case EnterNotify:
380 				onMouseFocus(true);
381 				mouseFocus=true;
382 				draggingUnfocus = false;
383 				break;
384 			case LeaveNotify:
385 				if(!draggingChild){
386 					onMouseFocus(false);
387 					mouseFocus=false;
388 				}else{
389 					draggingUnfocus = true;
390 				}
391 				break;
392 			case FocusIn: onKeyboardFocus(true); _keyboardFocus=true; break;
393 			case FocusOut: onKeyboardFocus(false); _keyboardFocus=false; break;
394 			case MapNotify: onShow; break;
395 			case UnmapNotify: onHide; break;
396 			case DestroyNotify: onDestroy; break;
397 			case Expose: onDraw(); break;
398 			case ClientMessage:
399 				if(e.xclient.message_type == wmDelete){
400 					close();
401 				}
402 				break;
403 			case KeymapNotify: XRefreshKeyboardMapping(&e.xmapping); break;
404 			case SelectionNotify:
405 				if(e.xselection.property == utf8){
406 					char* p;
407 					int actualFormat;
408 					size_t count;
409 					Atom actualType;
410 					XGetWindowProperty(
411 						wm.displayHandle, windowHandle, utf8, 0, 1024, false, utf8,
412 						&actualType, &actualFormat, &count, &count, cast(ubyte**)&p
413 					);
414 					onPaste(p.to!string);
415 					XFree(p);
416 				}
417 				break;
418 			default:break;
419 		}
420 	}
421 
422 	@property
423 	override bool hasMouseFocus(){
424 		return mouseFocus;
425 	}
426 
427 	override bool hasFocus(){
428 		return _keyboardFocus;
429 	}
430 
431 	override void resize(int[2] size){
432 		XResizeWindow(wm.displayHandle, windowHandle, size.w, size.h);
433 	}
434 
435 	override void move(int[2] pos){
436 		XMoveWindow(wm.displayHandle, windowHandle, pos.x, pos.y);
437 	}
438 
439 	void resized(int[2] size){
440 		this.size = size;
441 		if(draw)
442 			draw.resize(size);
443 	}
444 
445 	void moved(int[2] pos){
446 		this.pos = pos;
447 	}
448 
449 	void drawInit(){
450 		draw = new XDraw(this);
451 	}
452 
453 	long[2] getScreenSize(){
454 		version(Windows){
455 			RECT size;
456 			GetWindowRect(windowHandle, &size);
457 			return [
458 				size.right - size.left,
459 				size.bottom - size.top
460 			];
461 		}else
462 			assert(false, "Not implemented");
463 	}
464 		
465 }
466 
467 
468 import std.math;
469 
470 int distance(int a, int b){
471 	return abs(a-b);
472 }