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