1 // https://dpaste.dzfl.pl/7a77355acaec
2 
3 // https://freedesktop.org/wiki/Specifications/XDND/
4 
5 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
6 
7 
8 // on Mac with X11: -L-L/usr/X11/lib 
9 
10 /+
11 
12 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works
13 
14 	Progress bar in taskbar
15 		- i can probably just set a property on the window...
16 		  it sets that prop to an integer 0 .. 100. Taskbar
17 		  deletes it or window deletes it when it is handled.
18 		- prolly display it as a nice little line at the bottom.
19 
20 
21 from gtk:
22 
23 #define PROGRESS_HINT  "_NET_WM_XAPP_PROGRESS"
24 #define PROGRESS_PULSE_HINT  "_NET_WM_XAPP_PROGRESS_PULSE"
25 
26 >+  if (cardinal > 0)
27 >+  {
28 >+    XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
29 >+                     xid,
30 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name),
31 >+                     XA_CARDINAL, 32,
32 >+                     PropModeReplace,
33 >+                     (guchar *) &cardinal, 1);
34 >+  }
35 >+  else
36 >+  {
37 >+    XDeleteProperty (GDK_DISPLAY_XDISPLAY (display),
38 >+                     xid,
39 >+                     gdk_x11_get_xatom_by_name_for_display (display, atom_name));
40 >+  }
41 
42 from Windows:
43 
44 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3
45 
46 interface
47 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 ); 
48 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”)); 
49 listen for msg, return TRUE
50 interface->SetProgressState(hwnd, TBPF_NORMAL); 
51 interface->SetProgressValue(hwnd, 40, 100); 
52 
53 
54 	My new notification system.
55 		- use a unix socket? or a x property? or a udp port?
56 		- could of course also get on the dbus train but ugh.
57 		- it could also reply with the info as a string for easy remote examination.
58 
59 +/
60 
61 /*
62 	Event Loop would be nices:
63 
64 	* add on idle - runs when nothing else happens
65 		* which can specify how long to yield for
66 	* send messages without a recipient window
67 	* setTimeout
68 	* setInterval
69 */
70 
71 /*
72 	Classic games I want to add:
73 		* my tetris clone
74 		* pac man
75 */
76 
77 /*
78 	Text layout needs a lot of work. Plain drawText is useful but too
79 	limited. It will need some kind of text context thing which it will
80 	update and you can pass it on and get more details out of it.
81 
82 	It will need a bounding box, a current cursor location that is updated
83 	as drawing continues, and various changable facts (which can also be
84 	changed on the painter i guess) like font, color, size, background,
85 	etc.
86 
87 	We can also fetch the caret location from it somehow.
88 
89 	Should prolly be an overload of drawText
90 
91 		blink taskbar / demand attention cross platform. FlashWindow and demandAttention
92 
93 		WS_EX_NOACTIVATE
94 		WS_CHILD - owner and owned vs parent and child. Does X have something similar?
95 		full screen windows. Can just set the atom on X. Windows will be harder.
96 
97 		moving windows. resizing windows.
98 
99 		hide cursor, capture cursor, change cursor.
100 
101 	REMEMBER: simpledisplay does NOT have to do everything! It just needs to make
102 	sure the pieces are there to do its job easily and make other jobs possible.
103 */
104 
105 /++
106 	simpledisplay.d provides basic cross-platform GUI-related functionality,
107 	including creating windows, drawing on them, working with the clipboard,
108 	timers, OpenGL, and more. However, it does NOT provide high level GUI
109 	widgets. See my minigui.d, an extension to this module, for that
110 	functionality.
111 
112 	simpledisplay provides cross-platform wrapping for Windows and Linux
113 	(and perhaps other OSes that use X11), but also does not prevent you
114 	from using the underlying facilities if you need them. It has a goal
115 	of working efficiently over a remote X link (at least as far as Xlib
116 	reasonably allows.)
117 
118 	simpledisplay depends on [arsd.color|color.d], which should be available from the
119 	same place where you got this file. Other than that, however, it has
120 	very few dependencies and ones that don't come with the OS and/or the
121 	compiler are all opt-in.
122 
123 	simpledisplay.d's home base is on my arsd repo on Github. The file is:
124 	https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d
125 
126 	simpledisplay is basically stable. I plan to refactor the internals,
127 	and may add new features and fix bugs, but It do not expect to
128 	significantly change the API. It has been stable a few years already now.
129 
130 	Installation_instructions:
131 
132 	`simpledisplay.d` does not have any dependencies outside the
133 	operating system and `color.d`, so it should just work most the
134 	time, but there are a few caveats on some systems:
135 
136 	Please note when compiling on Win64, you need to explicitly list
137 	`-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows
138 	subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`.
139 
140 	If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`;
141 	note the "w".
142 
143 	On Win32, you can pass `-L/subsystem:windows` if you don't want a
144 	console to be automatically allocated.
145 
146 	On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command.
147 
148 	On Ubuntu, you might need to install X11 development libraries to
149 	successfully link.
150 
151 	$(CONSOLE
152 		$ sudo apt-get install libglc-dev
153 		$ sudo apt-get install libx11-dev
154 	)
155 
156 
157 	Jump_list:
158 
159 	Don't worry, you don't have to read this whole documentation file!
160 
161 	Check out the [#Event-example] and [#Pong-example] to get started quickly.
162 
163 	The main classes you may want to create are [SimpleWindow], [Timer],
164 	[Image], and [Sprite].
165 
166 	The main functions you'll want are [setClipboardText] and [getClipboardText].
167 
168 	There are also platform-specific functions available such as [XDisplayConnection]
169 	and [GetAtom] for X11, among others.
170 
171 	See the examples and topics list below to learn more.
172 
173 	$(WARNING
174 		There should only be one GUI thread per application,
175 		and all windows should be created in it and your
176 		event loop should run there.
177 
178 		To do otherwise is undefined behavior and has no
179 		cross platform guarantees.
180 	)
181 
182 	$(H2 About this documentation)
183 
184 	The goal here is to give some complete programs as overview examples first, then a look at each major feature with working examples first, then, finally, the inline class and method list will follow.
185 
186 	Scan for headers for a topic - $(B they will visually stand out) - you're interested in to get started quickly and feel free to copy and paste any example as a starting point for your program. I encourage you to learn the library by experimenting with the examples!
187 
188 	All examples are provided with no copyright restrictions whatsoever. You do not need to credit me or carry any kind of notice with the source if you copy and paste from them.
189 
190 	To get started, download `simpledisplay.d` and `color.d` to a working directory. Copy an example info a file called `example.d` and compile using the command given at the top of each example.
191 
192 	If you need help, email me: destructionator@gmail.com or IRC us, #d on Freenode (I am destructionator or adam_d_ruppe there). If you learn something that isn't documented, I appreciate pull requests on github to this file.
193 
194 	At points, I will talk about implementation details in the documentation. These are sometimes
195 	subject to change, but nevertheless useful to understand what is really going on. You can learn
196 	more about some of the referenced things by searching the web for info about using them from C.
197 	You can always look at the source of simpledisplay.d too for the most authoritative source on
198 	its specific implementation. If you disagree with how I did something, please contact me so we
199 	can discuss it!
200 
201 	$(H2 Using with fibers)
202 
203 	simpledisplay can be used with [core.thread.Fiber], but be warned many of the functions can use a significant amount of stack space. I recommend at least 64 KB stack for each fiber (just set through the second argument to Fiber's constructor).
204 
205 	Examples:
206 
207 	$(H3 Event-example)
208 	This program creates a window and draws events inside them as they
209 	happen, scrolling the text in the window as needed. Run this program
210 	and experiment to get a feel for where basic input events take place
211 	in the library.
212 
213 	---
214 	// dmd example.d simpledisplay.d color.d
215 	import arsd.simpledisplay;
216 	import std.conv;
217 
218 	void main() {
219 		auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
220 
221 		int y = 0;
222 
223 		void addLine(string text) {
224 			auto painter = window.draw();
225 
226 			if(y + painter.fontHeight >= window.height) {
227 				painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
228 				y -= painter.fontHeight;
229 			}
230 
231 			painter.outlineColor = Color.red;
232 			painter.fillColor = Color.black;
233 			painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
234 
235 			painter.outlineColor = Color.white;
236 
237 			painter.drawText(Point(10, y), text);
238 
239 			y += painter.fontHeight;
240 		}
241 
242 		window.eventLoop(1000,
243 		  () {
244 			addLine("Timer went off!");
245 		  },
246 		  (KeyEvent event) {
247 			addLine(to!string(event));
248 		  },
249 		  (MouseEvent event) {
250 			addLine(to!string(event));
251 		  },
252 		  (dchar ch) {
253 			addLine(to!string(ch));
254 		  }
255 		);
256 	}
257 	---
258 
259 	If you are interested in more game writing with D, check out my gamehelpers.d which builds upon simpledisplay, and its other stand-alone support modules, simpleaudio.d and joystick.d, too.
260 
261 	This program displays a pie chart. Clicking on a color will increase its share of the pie.
262 
263 	---
264 
265 	---
266 
267 	$(H2 Topics)
268 
269 	$(H3 $(ID topic-windows) Windows)
270 		The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single
271 		window on the user's screen.
272 
273 		You may create multiple windows, if the underlying platform supports it. You may check
274 		`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
275 		SimpleWindow's constructor at runtime to handle those cases.
276 
277 		A single running event loop will handle as many windows as needed.
278 
279 		setEventHandlers function
280 		eventLoop function
281 		draw function
282 		title property
283 
284 	$(H3 $(ID topic-event-loops) Event loops)
285 		The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.
286 
287 		The most common scenario is creating a window, then calling [SimpleWindow.eventLoop|window.eventLoop] when setup is complete. You can pass several handlers to the `eventLoop` method right there:
288 
289 		---
290 		// dmd example.d simpledisplay.d color.d
291 		import arsd.simpledisplay;
292 		void main() {
293 			auto window = new SimpleWindow(200, 200);
294 			window.eventLoop(0,
295 			  delegate (dchar) { /* got a character key press */ }
296 			);
297 		}
298 		---
299 
300 		$(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.)
301 
302 		On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run.
303 
304 		On Linux, simpledisplay also supports my [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch.
305 
306 		It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried.
307 
308 	$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
309 		Notification area icons are currently implemented on X11 and Windows. On X11, it defaults to using `libnotify` to show bubbles, if available, and will do a custom bubble window if not. You can `version=without_libnotify` to avoid this run-time dependency, if you like.
310 
311 	$(H3 $(ID topic-input-handling) Input handling)
312 		There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
313 
314 	$(H3 $(ID topic-2d-drawing) 2d Drawing)
315 		To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods.
316 
317 		Important: `ScreenPainter` double-buffers and will not actually update the window until its destructor is run. Always ensure the painter instance goes out-of-scope before proceeding. You can do this by calling it inside an event handler, a timer callback, or an small scope inside main. For example:
318 
319 		---
320 		// dmd example.d simpledisplay.d color.d
321 		import arsd.simpledisplay;
322 		void main() {
323 			auto window = new SimpleWindow(200, 200);
324 			{ // introduce sub-scope
325 				auto painter = window.draw(); // begin drawing
326 				/* draw here */
327 				painter.outlineColor = Color.red;
328 				painter.fillColor = Color.black;
329 				painter.drawRectangle(Point(0, 0), 200, 200);
330 			} // end scope, calling `painter`'s destructor, drawing to the screen.
331 			window.eventLoop(0); // handle events
332 		}
333 		---
334 
335 		Painting is done based on two color properties, a pen and a brush.
336 
337 		At this time, the 2d drawing does not support alpha blending. If you need that, use a 2d OpenGL context instead.
338 		FIXME add example of 2d opengl drawing here
339 	$(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
340 		simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
341 
342 		Note that it is still possible to draw 2d on top of an OpenGL window, using the `draw` method, though I don't recommend it.
343 
344 		To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor.
345 
346 		Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
347 
348 		To force a redraw of the scene, call [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlSceneNow()].
349 
350 		Please note that my experience with OpenGL is very out-of-date, and the bindings in simpledisplay reflect that. If you want to use more modern functions, you may have to define the bindings yourself, or import them from another module. However, the OpenGL context creation done in simpledisplay will work for any version.
351 
352 		This example program will draw a rectangle on your window:
353 
354 		---
355 		// dmd example.d simpledisplay.d color.d
356 		import arsd.simpledisplay;
357 
358 		void main() {
359 
360 		}
361 		---
362 	$(H3 $(ID topic-modern-opengl) Modern OpenGL)
363 		simpledisplay's opengl support, by default, is for "legacy" opengl. To use "modern" functions, you must opt-into them with a little more setup. But the library providers helpers for this too.
364 
365 		This example program shows how you can set up a shader to draw a rectangle:
366 
367 		---
368 module opengl3test;
369 import arsd.simpledisplay;
370 
371 // based on https://learnopengl.com/Getting-started/Hello-Triangle
372 
373 void main() {
374 	// First thing we do, before creating the window, is declare what version we want.
375 	setOpenGLContextVersion(3, 3);
376 	// turning off legacy compat is required to use version 3.3 and newer
377 	openGLContextCompatible = false;
378 
379 	uint VAO;
380 	OpenGlShader shader;
381 
382 	// then we can create the window.
383 	auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
384 
385 	// additional setup needs to be done when it is visible, simpledisplay offers a property
386 	// for exactly that:
387 	window.visibleForTheFirstTime = delegate() {
388 		// now with the window loaded, we can start loading the modern opengl functions.
389 
390 		// you MUST set the context first.
391 		window.setAsCurrentOpenGlContext;
392 		// then load the remainder of the library
393   		gl3.loadDynamicLibrary();
394 
395 		// now you can create the shaders, etc.
396 		shader = new OpenGlShader(
397 			OpenGlShader.Source(GL_VERTEX_SHADER, `
398 				#version 330 core
399 				layout (location = 0) in vec3 aPos;
400 				void main() {
401 					gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
402 				}
403 			`),
404 			OpenGlShader.Source(GL_FRAGMENT_SHADER, `
405 				#version 330 core
406 				out vec4 FragColor;
407 				uniform vec4 mycolor;
408 				void main() {
409 					FragColor = mycolor;
410 				}
411 			`),
412 		);
413 
414 		// and do whatever other setup you want.
415 
416 		float[] vertices = [
417 			0.5f,  0.5f, 0.0f,  // top right
418 			0.5f, -0.5f, 0.0f,  // bottom right
419 			-0.5f, -0.5f, 0.0f,  // bottom left
420 			-0.5f,  0.5f, 0.0f   // top left 
421 		];
422 		uint[] indices = [  // note that we start from 0!
423 			0, 1, 3,  // first Triangle
424 			1, 2, 3   // second Triangle
425 		];
426 		uint VBO, EBO;
427 		glGenVertexArrays(1, &VAO);
428 		// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
429 		glBindVertexArray(VAO);
430 
431 		glGenBuffers(1, &VBO);
432 		glGenBuffers(1, &EBO);
433 
434 		glBindBuffer(GL_ARRAY_BUFFER, VBO);
435 		glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
436 
437 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
438 		glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
439 
440 		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
441 		glEnableVertexAttribArray(0);
442 
443 		// the library will set the initial viewport and trigger our first draw,
444 		// so these next two lines are NOT needed. they are just here as comments
445 		// to show what would happen next.
446 
447 		// glViewport(0, 0, window.width, window.height);
448 		// window.redrawOpenGlSceneNow();
449 	};
450 
451 	// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
452 	// it is our render method.
453 	window.redrawOpenGlScene = delegate() {
454 		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
455 		glClear(GL_COLOR_BUFFER_BIT);
456 
457 		glUseProgram(shader.shaderProgram);
458 
459 		// the shader helper class has methods to set uniforms too
460 		shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
461 
462 		glBindVertexArray(VAO);
463 		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
464 	};
465 
466 	window.eventLoop(0);
467 }
468 		---
469 
470 	This program only draws the image once because that's all that is necessary, since it is static. If you want to do animation, you might set a pulse timer (which would be a fixed max fps, not necessarily consistent) or use a render loop in a separate thread.
471 
472 
473 	$(H3 $(ID topic-images) Displaying images)
474 		You can also load PNG images using [arsd.png].
475 
476 		---
477 		// dmd example.d simpledisplay.d color.d png.d
478 		import arsd.simpledisplay;
479 		import arsd.png;
480 
481 		void main() {
482 			auto image = Image.fromMemoryImage(readPng("image.png"));
483 			displayImage(image);
484 		}
485 		---
486 
487 		Compile with `dmd example.d simpledisplay.d png.d`.
488 
489 		If you find an image file which is a valid png that [arsd.png] fails to load, please let me know. In the mean time of fixing the bug, you can probably convert the file into an easier-to-load format. Be sure to turn OFF png interlacing, as that isn't supported. Other things to try would be making the image smaller, or trying 24 bit truecolor mode with an alpha channel.
490 
491 	$(H3 $(ID topic-sprites) Sprites)
492 		The [Sprite] class is used to make images on the display server for fast blitting to screen. This is especially important to use to support fast drawing of repeated images on a remote X11 link.
493 
494 	$(H3 $(ID topic-clipboard) Clipboard)
495 		The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
496 
497 		It also has helpers for handling X-specific events.
498 
499 	$(H3 $(ID topic-timers) Timers)
500 		There are two timers in simpledisplay: one is the pulse timeout you can set on the call to `window.eventLoop`, and the other is a customizable class, [Timer].
501 
502 		The pulse timeout is used by setting a non-zero interval as the first argument to `eventLoop` function and adding a zero-argument delegate to handle the pulse.
503 
504 		---
505 			import arsd.simpledisplay;
506 
507 			void main() {
508 				auto window = new SimpleWindow(400, 400);
509 				// every 100 ms, it will draw a random line
510 				// on the window.
511 				window.eventLoop(100, {
512 					auto painter = window.draw();
513 
514 					import std.random;
515 					// random color
516 					painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
517 					// random line
518 					painter.drawLine(
519 						Point(uniform(0, window.width), uniform(0, window.height)),
520 						Point(uniform(0, window.width), uniform(0, window.height)));
521 
522 				});
523 			}
524 		---
525 
526 		The `Timer` class works similarly, but is created separately from the event loop. (It still fires through the event loop, though.) You may make as many instances of `Timer` as you wish.
527 
528 		The pulse timer and instances of the [Timer] class may be combined at will.
529 
530 		---
531 			import arsd.simpledisplay;
532 
533 			void main() {
534 				auto window = new SimpleWindow(400, 400);
535 				auto timer = new Timer(1000, delegate {
536 					auto painter = window.draw();
537 					painter.clear();
538 				});
539 
540 				window.eventLoop(0);
541 			}
542 		---
543 
544 		Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
545 
546 	$(H3 $(ID topic-os-helpers) OS-specific helpers)
547 		simpledisplay carries a lot of code to help implement itself without extra dependencies, and much of this code is available for you too, so you may extend the functionality yourself.
548 
549 		See also: `xwindows.d` from my github.
550 
551 	$(H3 $(ID topic-os-extension) Extending with OS-specific functionality)
552 		`handleNativeEvent` and `handleNativeGlobalEvent`.
553 
554 	$(H3 $(ID topic-integration) Integration with other libraries)
555 		Integration with a third-party event loop is possible.
556 
557 		On Linux, you might want to support both terminal input and GUI input. You can do this by using simpledisplay together with eventloop.d and terminal.d.
558 
559 	$(H3 $(ID topic-guis) GUI widgets)
560 		simpledisplay does not provide GUI widgets such as text areas, buttons, checkboxes, etc. It only gives basic windows, the ability to draw on it, receive input from it, and access native information for extension. You may write your own gui widgets with these, but you don't have to because I already did for you!
561 
562 		Download `minigui.d` from my github repository and add it to your project. minigui builds these things on top of simpledisplay and offers its own Window class (and subclasses) to use that wrap SimpleWindow, adding a new event and drawing model that is hookable by subwidgets, represented by their own classes.
563 
564 		Migrating to minigui from simpledisplay is often easy though, because they both use the same ScreenPainter API, and the same simpledisplay events are available, if you want them. (Though you may like using the minigui model, especially if you are familiar with writing web apps in the browser with Javascript.)
565 
566 		minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
567 
568 	$(H2 Platform-specific tips and tricks)
569 
570 	Windows_tips:
571 
572 	You can add icons or manifest files to your exe using a resource file.
573 
574 	To create a Windows .ico file, use the gimp or something. I'll write a helper
575 	program later.
576 
577 	Create `yourapp.rc`:
578 
579 	```rc
580 		1 ICON filename.ico
581 		CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
582 	```
583 
584 	And `yourapp.exe.manifest`:
585 
586 	```xml
587 		<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
588 		<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
589 		<assemblyIdentity
590 		    version="1.0.0.0"
591 		    processorArchitecture="*"
592 		    name="CompanyName.ProductName.YourApplication"
593 		    type="win32"
594 		/>
595 		<description>Your application description here.</description>
596 		<dependency>
597 		    <dependentAssembly>
598 			<assemblyIdentity
599 			    type="win32"
600 			    name="Microsoft.Windows.Common-Controls"
601 			    version="6.0.0.0"
602 			    processorArchitecture="*"
603 			    publicKeyToken="6595b64144ccf1df"
604 			    language="*"
605 			/>
606 		    </dependentAssembly>
607 		</dependency>
608 		</assembly>
609 	```
610 
611 
612 	$(H2 $(ID developer-notes) Developer notes)
613 
614 	I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
615 	implementation though.
616 
617 	The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
618 	suck. If I was rewriting it, I wouldn't do it that way again.
619 
620 	This file must not have any more required dependencies. If you need bindings, add
621 	them right to this file. Once it gets into druntime and is there for a while, remove
622 	bindings from here to avoid conflicts (or put them in an appropriate version block
623 	so it continues to just work on old dmd), but wait a couple releases before making the
624 	transition so this module remains usable with older versions of dmd.
625 
626 	You may have optional dependencies if needed by putting them in version blocks or
627 	template functions. You may also extend the module with other modules with UFCS without
628 	actually editing this - that is nice to do if you can.
629 
630 	Try to make functions work the same way across operating systems. I typically make
631 	it thinly wrap Windows, then emulate that on Linux.
632 
633 	A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
634 	Phobos! So try to avoid it.
635 
636 	See more comments throughout the source.
637 
638 	I realize this file is fairly large, but over half that is just bindings at the bottom
639 	or documentation at the top. Some of the classes are a bit big too, but hopefully easy
640 	to understand. I suggest you jump around the source by looking for a particular
641 	declaration you're interested in, like `class SimpleWindow` using your editor's search
642 	function, then look at one piece at a time.
643 
644 	Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
645 	destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode. I go by
646 	Destructionator or adam_d_ruppe, depending on which computer I'm logged into.
647 
648 	I live in the eastern United States, so I will most likely not be around at night in
649 	that US east timezone.
650 
651 	License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License.
652 
653 	Building documentation: You may wish to use the `arsd.ddoc` file from my github with
654 	building the documentation for simpledisplay yourself. It will give it a bit more style.
655 	Simply download the arsd.ddoc file and add it to your compile command when building docs.
656 	`dmd -c simpledisplay.d color.d -D arsd.ddoc`
657 +/
658 module arsd.simpledisplay;
659 
660 // FIXME: tetris demo
661 // FIXME: space invaders demo
662 // FIXME: asteroids demo
663 
664 /++ $(ID Pong-example)
665 	$(H3 Pong)
666 
667 	This program creates a little Pong-like game. Player one is controlled
668 	with the keyboard.  Player two is controlled with the mouse. It demos
669 	the pulse timer, event handling, and some basic drawing.
670 +/
671 version(demos)
672 unittest {
673 	// dmd example.d simpledisplay.d color.d
674 	import arsd.simpledisplay;
675 
676 	enum paddleMovementSpeed = 8;
677 	enum paddleHeight = 48;
678 
679 	void main() {
680 		auto window = new SimpleWindow(600, 400, "Pong game!");
681 
682 		int playerOnePosition, playerTwoPosition;
683 		int playerOneMovement, playerTwoMovement;
684 		int playerOneScore, playerTwoScore;
685 
686 		int ballX, ballY;
687 		int ballDx, ballDy;
688 
689 		void serve() {
690 			import std.random;
691 
692 			ballX = window.width / 2;
693 			ballY = window.height / 2;
694 			ballDx = uniform(-4, 4) * 3;
695 			ballDy = uniform(-4, 4) * 3;
696 			if(ballDx == 0)
697 				ballDx = uniform(0, 2) == 0 ? 3 : -3;
698 		}
699 
700 		serve();
701 
702 		window.eventLoop(50, // set a 50 ms timer pulls
703 			// This runs once per timer pulse
704 			delegate () {
705 				auto painter = window.draw();
706 
707 				painter.clear();
708 
709 				// Update everyone's motion
710 				playerOnePosition += playerOneMovement;
711 				playerTwoPosition += playerTwoMovement;
712 
713 				ballX += ballDx;
714 				ballY += ballDy;
715 
716 				// Bounce off the top and bottom edges of the window
717 				if(ballY + 7 >= window.height)
718 					ballDy = -ballDy;
719 				if(ballY - 8 <= 0)
720 					ballDy = -ballDy;
721 
722 				// Bounce off the paddle, if it is in position
723 				if(ballX - 8 <= 16) {
724 					if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
725 						ballDx = -ballDx + 1; // add some speed to keep it interesting
726 						ballDy += playerOneMovement; // and y movement based on your controls too
727 						ballX = 24; // move it past the paddle so it doesn't wiggle inside
728 					} else {
729 						// Missed it
730 						playerTwoScore ++;
731 						serve();
732 					}
733 				}
734 
735 				if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
736 					if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
737 						ballDx = -ballDx - 1;
738 						ballDy += playerTwoMovement;
739 						ballX = window.width - 24;
740 					} else {
741 						// Missed it
742 						playerOneScore ++;
743 						serve();
744 					}
745 				}
746 
747 				// Draw the paddles
748 				painter.outlineColor = Color.black;
749 				painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
750 				painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
751 
752 				// Draw the ball
753 				painter.fillColor = Color.red;
754 				painter.outlineColor = Color.yellow;
755 				painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
756 
757 				// Draw the score
758 				painter.outlineColor = Color.blue;
759 				import std.conv;
760 				painter.drawText(Point(64, 4), to!string(playerOneScore));
761 				painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
762 
763 			},
764 			delegate (KeyEvent event) {
765 				// Player 1's controls are the arrow keys on the keyboard
766 				if(event.key == Key.Down)
767 					playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
768 				if(event.key == Key.Up)
769 					playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
770 
771 			},
772 			delegate (MouseEvent event) {
773 				// Player 2's controls are mouse movement while the left button is held down
774 				if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
775 					if(event.dy > 0)
776 						playerTwoMovement = paddleMovementSpeed;
777 					else if(event.dy < 0)
778 						playerTwoMovement = -paddleMovementSpeed;
779 				} else {
780 					playerTwoMovement = 0;
781 				}
782 			}
783 		);
784 	}
785 }
786 
787 /++ $(ID example-minesweeper)
788 
789 	This minesweeper demo shows how we can implement another classic
790 	game with simpledisplay and shows some mouse input and basic output
791 	code.
792 +/
793 version(demos)
794 unittest {
795 	import arsd.simpledisplay;
796 
797 	enum GameSquare {
798 		mine = 0,
799 		clear,
800 		m1, m2, m3, m4, m5, m6, m7, m8
801 	}
802 
803 	enum UserSquare {
804 		unknown,
805 		revealed,
806 		flagged,
807 		questioned
808 	}
809 
810 	enum GameState {
811 		inProgress,
812 		lose,
813 		win
814 	}
815 
816 	GameSquare[] board;
817 	UserSquare[] userState;
818 	GameState gameState;
819 	int boardWidth;
820 	int boardHeight;
821 
822 	bool isMine(int x, int y) {
823 		if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
824 			return false;
825 		return board[y * boardWidth + x] == GameSquare.mine;
826 	}
827 
828 	GameState reveal(int x, int y) {
829 		if(board[y * boardWidth + x] == GameSquare.clear) {
830 			floodFill(userState, boardWidth, boardHeight,
831 				UserSquare.unknown, UserSquare.revealed,
832 				x, y,
833 				(x, y) {
834 					if(board[y * boardWidth + x] == GameSquare.clear)
835 						return true;
836 					else {
837 						userState[y * boardWidth + x] = UserSquare.revealed;
838 						return false;
839 					}
840 				});
841 		} else {
842 			userState[y * boardWidth + x] = UserSquare.revealed;
843 			if(isMine(x, y))
844 				return GameState.lose;
845 		}
846 
847 		foreach(state; userState) {
848 			if(state == UserSquare.unknown || state == UserSquare.questioned)
849 				return GameState.inProgress;
850 		}
851 
852 		return GameState.win;
853 	}
854 
855 	void initializeBoard(int width, int height, int numberOfMines) {
856 		boardWidth = width;
857 		boardHeight = height;
858 		board.length = width * height;
859 
860 		userState.length = width * height;
861 		userState[] = UserSquare.unknown; 
862 
863 		import std.algorithm, std.random, std.range;
864 
865 		board[] = GameSquare.clear;
866 
867 		foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
868 			board[minePosition] = GameSquare.mine;
869 
870 		int x;
871 		int y;
872 		foreach(idx, ref square; board) {
873 			if(square == GameSquare.clear) {
874 				int danger = 0;
875 				danger += isMine(x-1, y-1)?1:0;
876 				danger += isMine(x-1, y)?1:0;
877 				danger += isMine(x-1, y+1)?1:0;
878 				danger += isMine(x, y-1)?1:0;
879 				danger += isMine(x, y+1)?1:0;
880 				danger += isMine(x+1, y-1)?1:0;
881 				danger += isMine(x+1, y)?1:0;
882 				danger += isMine(x+1, y+1)?1:0;
883 
884 				square = cast(GameSquare) (danger + 1);
885 			}
886 
887 			x++;
888 			if(x == width) {
889 				x = 0;
890 				y++;
891 			}
892 		}
893 	}
894 
895 	void redraw(SimpleWindow window) {
896 		import std.conv;
897 
898 		auto painter = window.draw();
899 
900 		painter.clear();
901 
902 		final switch(gameState) with(GameState) {
903 			case inProgress:
904 				break;
905 			case win:
906 				painter.fillColor = Color.green;
907 				painter.drawRectangle(Point(0, 0), window.width, window.height);
908 				return;
909 			case lose:
910 				painter.fillColor = Color.red;
911 				painter.drawRectangle(Point(0, 0), window.width, window.height);
912 				return;
913 		}
914 
915 		int x = 0;
916 		int y = 0;
917 
918 		foreach(idx, square; board) {
919 			auto state = userState[idx];
920 
921 			final switch(state) with(UserSquare) {
922 				case unknown:
923 					painter.outlineColor = Color.black;
924 					painter.fillColor = Color(128,128,128);
925 
926 					painter.drawRectangle(
927 						Point(x * 20, y * 20),
928 						20, 20
929 					);
930 				break;
931 				case revealed:
932 					if(square == GameSquare.clear) {
933 						painter.outlineColor = Color.white;
934 						painter.fillColor = Color.white;
935 
936 						painter.drawRectangle(
937 							Point(x * 20, y * 20),
938 							20, 20
939 						);
940 					} else {
941 						painter.outlineColor = Color.black;
942 						painter.fillColor = Color.white;
943 
944 						painter.drawText(
945 							Point(x * 20, y * 20),
946 							to!string(square)[1..2],
947 							Point(x * 20 + 20, y * 20 + 20),
948 							TextAlignment.Center | TextAlignment.VerticalCenter);
949 					}
950 				break;
951 				case flagged:
952 					painter.outlineColor = Color.black;
953 					painter.fillColor = Color.red;
954 					painter.drawRectangle(
955 						Point(x * 20, y * 20),
956 						20, 20
957 					);
958 				break;
959 				case questioned:
960 					painter.outlineColor = Color.black;
961 					painter.fillColor = Color.yellow;
962 					painter.drawRectangle(
963 						Point(x * 20, y * 20),
964 						20, 20
965 					);
966 				break;
967 			}
968 
969 			x++;
970 			if(x == boardWidth) {
971 				x = 0;
972 				y++;
973 			}
974 		}
975 
976 	}
977 
978 	void main() {
979 		auto window = new SimpleWindow(200, 200);
980 
981 		initializeBoard(10, 10, 10);
982 
983 		redraw(window);
984 		window.eventLoop(0,
985 			delegate (MouseEvent me) {
986 				if(me.type != MouseEventType.buttonPressed)
987 					return;
988 				auto x = me.x / 20;
989 				auto y = me.y / 20;
990 				if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
991 					if(me.button == MouseButton.left) {
992 						gameState = reveal(x, y);
993 					} else {
994 						userState[y*boardWidth+x] = UserSquare.flagged;
995 					}
996 					redraw(window);
997 				}
998 			}
999 		);
1000 	}
1001 }
1002 
1003 /*
1004 version(OSX) {
1005 	version=without_opengl;
1006 	version=allow_unimplemented_features;
1007 	version=OSXCocoa;
1008 	pragma(linkerDirective, "-framework Cocoa");
1009 }
1010 */
1011 
1012 version(without_opengl) {
1013 	enum SdpyIsUsingIVGLBinds = false;
1014 } else /*version(Posix)*/ {
1015 	static if (__traits(compiles, (){import iv.glbinds;})) {
1016 		enum SdpyIsUsingIVGLBinds = true;
1017 		public import iv.glbinds;
1018 		//pragma(msg, "SDPY: using iv.glbinds");
1019 	} else {
1020 		enum SdpyIsUsingIVGLBinds = false;
1021 	}
1022 //} else {
1023 //	enum SdpyIsUsingIVGLBinds = false;
1024 }
1025 
1026 
1027 version(Windows) {
1028 	//import core.sys.windows.windows;
1029 	import core.sys.windows.winnls;
1030 	import core.sys.windows.windef;
1031 	import core.sys.windows.basetyps;
1032 	import core.sys.windows.winbase;
1033 	import core.sys.windows.winuser;
1034 	import core.sys.windows.shellapi;
1035 	import core.sys.windows.wingdi;
1036 	static import gdi = core.sys.windows.wingdi; // so i
1037 
1038 	pragma(lib, "gdi32");
1039 	pragma(lib, "user32");
1040 } else version (linux) {
1041 	//k8: this is hack for rdmd. sorry.
1042 	static import core.sys.linux.epoll;
1043 	static import core.sys.linux.timerfd;
1044 }
1045 
1046 
1047 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
1048 
1049 // http://wiki.dlang.org/Simpledisplay.d
1050 
1051 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
1052 
1053 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
1054 // but can i control the scroll lock led
1055 
1056 
1057 // Note: if you are using Image on X, you might want to do:
1058 /*
1059 	static if(UsingSimpledisplayX11) {
1060 		if(!Image.impl.xshmAvailable) {
1061 			// the images will use the slower XPutImage, you might
1062 			// want to consider an alternative method to get better speed
1063 		}
1064 	}
1065 
1066 	If the shared memory extension is available though, simpledisplay uses it
1067 	for a significant speed boost whenever you draw large Images.
1068 */
1069 
1070 // CHANGE FROM LAST VERSION: the window background is no longer fixed, so you might want to fill the screen with a particular color before drawing.
1071 
1072 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
1073 
1074 /*
1075 	Biggest FIXME:
1076 		make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
1077 
1078 		clean up opengl contexts when their windows close
1079 
1080 		fix resizing the bitmaps/pixmaps
1081 */
1082 
1083 // BTW on Windows:
1084 // -L/SUBSYSTEM:WINDOWS:5.0
1085 // to dmd will make a nice windows binary w/o a console if you want that.
1086 
1087 /*
1088 	Stuff to add:
1089 
1090 	use multibyte functions everywhere we can
1091 
1092 	OpenGL windows
1093 	more event stuff
1094 	extremely basic windows w/ no decoration for tooltips, splash screens, etc.
1095 
1096 
1097 	resizeEvent
1098 		and make the windows non-resizable by default,
1099 		or perhaps stretched (if I can find something in X like StretchBlt)
1100 
1101 	take a screenshot function!
1102 
1103 	Pens and brushes?
1104 	Maybe a global event loop?
1105 
1106 	Mouse deltas
1107 	Key items
1108 */
1109 
1110 /*
1111 From MSDN:
1112 
1113 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
1114 
1115 Important  Do not use the LOWORD or HIWORD macros to extract the x- and y- coordinates of the cursor position because these macros return incorrect results on systems with multiple monitors. Systems with multiple monitors can have negative x- and y- coordinates, and LOWORD and HIWORD treat the coordinates as unsigned quantities.
1116 
1117 */
1118 
1119 version(linux) {
1120 	version = X11;
1121 	version(without_libnotify) {
1122 		// we cool
1123 	}
1124 	else
1125 		version = libnotify;
1126 }
1127 
1128 version(libnotify) {
1129 	pragma(lib, "dl");
1130 	import core.sys.posix.dlfcn;
1131 
1132 	void delegate()[int] libnotify_action_delegates;
1133 	int libnotify_action_delegates_count;
1134 	extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) {
1135 		auto idx = cast(int) user_data;
1136 		if(auto dgptr = idx in libnotify_action_delegates) {
1137 			(*dgptr)();
1138 			libnotify_action_delegates.remove(idx);
1139 		}
1140 	}
1141 
1142 	struct C_DynamicLibrary {
1143 		void* handle;
1144 		this(string name) {
1145 			handle = dlopen((name ~ "\0").ptr, RTLD_NOW);
1146 			if(handle is null)
1147 				throw new Exception("dlopen");
1148 		}
1149 
1150 		void close() {
1151 			dlclose(handle);
1152 		}
1153 
1154 		~this() {
1155 			// close
1156 		}
1157 
1158 		// FIXME: this looks up by name every time.... 
1159 		template call(string func, Ret, Args...) {
1160 			extern(C) Ret function(Args) fptr;
1161 			typeof(fptr) call() {
1162 				fptr = cast(typeof(fptr)) dlsym(handle, func);
1163 				return fptr;
1164 			}
1165 		}
1166 	}
1167 
1168 	C_DynamicLibrary* libnotify;
1169 }
1170 
1171 version(OSX) {
1172 	version(OSXCocoa) {}
1173 	else { version = X11; }
1174 }
1175 	//version = OSXCocoa; // this was written by KennyTM
1176 version(FreeBSD)
1177 	version = X11;
1178 version(Solaris)
1179 	version = X11;
1180 
1181 version(X11) {
1182 	version(without_xft) {}
1183 	else version=with_xft;
1184 }
1185 
1186 void featureNotImplemented()() {
1187 	version(allow_unimplemented_features)
1188 		throw new NotYetImplementedException();
1189 	else
1190 		static assert(0);
1191 }
1192 
1193 // these are so the static asserts don't trigger unless you want to
1194 // add support to it for an OS
1195 version(Windows)
1196 	version = with_timer;
1197 version(linux)
1198 	version = with_timer;
1199 
1200 version(with_timer)
1201 	enum bool SimpledisplayTimerAvailable = true;
1202 else
1203 	enum bool SimpledisplayTimerAvailable = false;
1204 
1205 /// If you have to get down and dirty with implementation details, this helps figure out if Windows is available you can `static if(UsingSimpledisplayWindows) ...` more reliably than `version()` because `version` is module-local.
1206 version(Windows)
1207 	enum bool UsingSimpledisplayWindows = true;
1208 else
1209 	enum bool UsingSimpledisplayWindows = false;
1210 
1211 /// If you have to get down and dirty with implementation details, this helps figure out if X is available you can `static if(UsingSimpledisplayX11) ...` more reliably than `version()` because `version` is module-local.
1212 version(X11)
1213 	enum bool UsingSimpledisplayX11 = true;
1214 else
1215 	enum bool UsingSimpledisplayX11 = false;
1216 
1217 /// If you have to get down and dirty with implementation details, this helps figure out if Cocoa is available you can `static if(UsingSimpledisplayCocoa) ...` more reliably than `version()` because `version` is module-local.
1218 version(OSXCocoa)
1219 	enum bool UsingSimpledisplayCocoa = true;
1220 else
1221 	enum bool UsingSimpledisplayCocoa = false;
1222 
1223 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
1224 version(Windows)
1225 	enum multipleWindowsSupported = true;
1226 else version(X11)
1227 	enum multipleWindowsSupported = true;
1228 else version(OSXCocoa)
1229 	enum multipleWindowsSupported = true;
1230 else
1231 	static assert(0);
1232 
1233 version(without_opengl)
1234 	enum bool OpenGlEnabled = false;
1235 else
1236 	enum bool OpenGlEnabled = true;
1237 
1238 
1239 /++
1240 	After selecting a type from [WindowTypes], you may further customize
1241 	its behavior by setting one or more of these flags.
1242 
1243 
1244 	The different window types have different meanings of `normal`. If the
1245 	window type already is a good match for what you want to do, you should
1246 	just use [WindowFlags.normal], the default, which will do the right thing
1247 	for your users.
1248 
1249 	The window flags will not always be honored by the operating system
1250 	and window managers; they are hints, not commands.
1251 +/
1252 enum WindowFlags : int {
1253 	normal = 0, ///
1254 	skipTaskbar = 1, ///
1255 	alwaysOnTop = 2, ///
1256 	alwaysOnBottom = 4, ///
1257 	cannotBeActivated = 8, ///
1258 	alwaysRequestMouseMotionEvents = 16, /// By default, simpledisplay will attempt to optimize mouse motion event reporting when it detects a remote connection, causing them to only be issued if input is grabbed (see: [SimpleWindow.grabInput]). This means doing hover effects and mouse game control on a remote X connection may not work right. Include this flag to override this optimization and always request the motion events. However btw, if you are doing mouse game control, you probably want to grab input anyway, and hover events are usually expendable! So think before you use this flag.
1259 	extraComposite = 32, /// On windows this will make this a layered windows (not supported for child windows before windows 8) to support transparency and improve animation performance.
1260 	/++
1261 		Sets the window as a short-lived child of its parent, but unlike an ordinary child,
1262 		it is still a top-level window. This should NOT be set separately for most window types.
1263 
1264 		A transient window will not keep the application open if its main window closes.
1265 
1266 		$(PITFALL This may not be correctly implemented and its behavior is subject to change.)
1267 
1268 
1269 		From the ICCM:
1270 
1271 		$(BLOCKQUOTE
1272 			It is important not to confuse WM_TRANSIENT_FOR with override-redirect. WM_TRANSIENT_FOR should be used in those cases where the pointer is not grabbed while the window is mapped (in other words, if other windows are allowed to be active while the transient is up). If other windows must be prevented from processing input (for example, when implementing pop-up menus), use override-redirect and grab the pointer while the window is mapped. 
1273 
1274 			$(CITE https://tronche.com/gui/x/icccm/sec-4.html)
1275 		)
1276 
1277 		So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag.
1278 
1279 		History:
1280 			Added February 23, 2021 but not yet stabilized.
1281 	+/
1282 	transient = 64,
1283 	dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
1284 }
1285 
1286 /++
1287 	When creating a window, you can pass a type to SimpleWindow's constructor,
1288 	then further customize the window by changing `WindowFlags`.
1289 
1290 
1291 	You should mostly only need [normal], [undecorated], and [eventOnly] for normal
1292 	use. The others are there to build a foundation for a higher level GUI toolkit,
1293 	but are themselves not as high level as you might think from their names.
1294 
1295 	This list is based on the EMWH spec for X11.
1296 	http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896
1297 +/
1298 enum WindowTypes : int {
1299 	/// An ordinary application window.
1300 	normal,
1301 	/// A generic window without a title bar or border. You can draw on the entire area of the screen it takes up and use it as you wish. Remember that users don't really expect these though, so don't use it where a window of any other type is appropriate.
1302 	undecorated,
1303 	/// A window that doesn't actually display on screen. You can use it for cases where you need a dummy window handle to communicate with or something.
1304 	eventOnly,
1305 	/// A drop down menu, such as from a menu bar
1306 	dropdownMenu,
1307 	/// A popup menu, such as from a right click
1308 	popupMenu,
1309 	/// A popup bubble notification
1310 	notification,
1311 	/*
1312 	menu, /// a tearable menu bar
1313 	splashScreen, /// a loading splash screen for your application
1314 	tooltip, /// A tiny window showing temporary help text or something.
1315 	comboBoxDropdown,
1316 	dialog,
1317 	toolbar
1318 	*/
1319 	/// a child nested inside the parent. You must pass a parent window to the ctor
1320 	nestedChild,
1321 }
1322 
1323 
1324 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
1325 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
1326 private __gshared char* sdpyWindowClassStr = null;
1327 private __gshared bool sdpyOpenGLContextAllowFallback = false;
1328 
1329 /**
1330 	Set OpenGL context version to use. This has no effect on non-OpenGL windows.
1331 	You may want to change context version if you want to use advanced shaders or
1332 	other modern OpenGL techinques. This setting doesn't affect already created
1333 	windows. You may use version 2.1 as your default, which should be supported
1334 	by any box since 2006, so seems to be a reasonable choice.
1335 
1336 	Note that by default version is set to `0`, which forces SimpleDisplay to use
1337 	old context creation code without any version specified. This is the safest
1338 	way to init OpenGL, but it may not give you access to advanced features.
1339 
1340 	See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL
1341 */
1342 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
1343 
1344 /**
1345 	Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
1346 	pipeline functions, and without "compatible" mode you won't be able to use
1347 	your old non-shader-based code with such contexts. By default SimpleDisplay
1348 	creates compatible context, so you can gradually upgrade your OpenGL code if
1349 	you want to (or leave it as is, as it should "just work").
1350 */
1351 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
1352 
1353 /**
1354 	Set to `true` to allow creating OpenGL context with lower version than requested
1355 	instead of throwing. If fallback was activated (or legacy OpenGL was requested),
1356 	`openGLContextFallbackActivated()` will return `true`.
1357 	*/
1358 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; }
1359 
1360 /**
1361 	After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.
1362 	*/
1363 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
1364 
1365 
1366 /**
1367 	Set window class name for all following `new SimpleWindow()` calls.
1368 
1369 	WARNING! For Windows, you should set your class name before creating any
1370 	window, and NEVER change it after that!
1371 */
1372 void sdpyWindowClass (const(char)[] v) {
1373 	import core.stdc.stdlib : realloc;
1374 	if (v.length == 0) v = "SimpleDisplayWindow";
1375 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1);
1376 	if (sdpyWindowClassStr is null) return; // oops
1377 	sdpyWindowClassStr[0..v.length+1] = 0;
1378 	sdpyWindowClassStr[0..v.length] = v[];
1379 }
1380 
1381 /**
1382 	Get current window class name.
1383 */
1384 string sdpyWindowClass () {
1385 	if (sdpyWindowClassStr is null) return null;
1386 	foreach (immutable idx; 0..size_t.max-1) {
1387 		if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup;
1388 	}
1389 	return null;
1390 }
1391 
1392 /++
1393 	Returns the DPI of the default monitor. [0] is width, [1] is height (they are usually the same though). You may wish to round the numbers off.
1394 +/
1395 float[2] getDpi() {
1396 	float[2] dpi;
1397 	version(Windows) {
1398 		HDC screen = GetDC(null);
1399 		dpi[0] = GetDeviceCaps(screen, LOGPIXELSX);
1400 		dpi[1] = GetDeviceCaps(screen, LOGPIXELSY);
1401 	} else version(X11) {
1402 		auto display = XDisplayConnection.get;
1403 		auto screen = DefaultScreen(display);
1404 
1405 		void fallback() {
1406 			// 25.4 millimeters in an inch...
1407 			dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4;
1408 			dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4;
1409 		}
1410 
1411 		char* resourceString = XResourceManagerString(display);
1412 		XrmInitialize();
1413 
1414 		auto db = XrmGetStringDatabase(resourceString);
1415 
1416 		if (resourceString) {
1417 			XrmValue value;
1418 			char* type;
1419 			if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
1420 				if (value.addr) {
1421 					import core.stdc.stdlib;
1422 					dpi[0] = atof(cast(char*) value.addr);
1423 					dpi[1] = dpi[0];
1424 				} else {
1425 					fallback();
1426 				}
1427 			} else {
1428 				fallback();
1429 			}
1430 		} else {
1431 			fallback();
1432 		}
1433 	}
1434 
1435 	return dpi;
1436 }
1437 
1438 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width, int height) {
1439 	TrueColorImage got;
1440 	version(X11) {
1441 		auto display = XDisplayConnection.get;
1442 		auto image = XGetImage(display, handle, 0, 0, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap);
1443 
1444 		// https://github.com/adamdruppe/arsd/issues/98
1445 
1446 		auto i = new Image(image);
1447 		got = i.toTrueColorImage();
1448 
1449 		XDestroyImage(image);
1450 	} else version(Windows) {
1451 		// I just need to BitBlt that shit... BUT WAIT IT IS ALREADY IN A DIB!!!!!!!
1452 
1453 		auto hdc = GetDC(handle);
1454 		scope(exit) ReleaseDC(handle, hdc);
1455 		auto i = new Image(width, height);
1456 		HDC hdcMem = CreateCompatibleDC(hdc);
1457 		HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
1458 		BitBlt(hdcMem, 0, 0, width, height, hdc, 0, 0, SRCCOPY);
1459 		SelectObject(hdcMem, hbmOld);
1460 		DeleteDC(hdcMem);
1461 
1462 		got = i.toTrueColorImage();
1463 	} else featureNotImplemented();
1464 
1465 	return got;
1466 }
1467 
1468 /++
1469 	The flagship window class.
1470 
1471 
1472 	SimpleWindow tries to make ordinary windows very easy to create and use without locking you
1473 	out of more advanced or complex features of the underlying windowing system.
1474 
1475 	For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
1476 	and get a suitable window to work with.
1477 
1478 	From there, you can opt into additional features, like custom resizability and OpenGL support
1479 	with the next two constructor arguments. Or, if you need even more, you can set a window type
1480 	and customization flags with the final two constructor arguments.
1481 
1482 	If none of that works for you, you can also create a window using native function calls, then
1483 	wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
1484 	though, if you do this, managing the window is still your own responsibility! Notably, you
1485 	will need to destroy it yourself.
1486 +/
1487 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
1488 
1489 	/++
1490 		Copies the window's current state into a [TrueColorImage].
1491 
1492 		Be warned: this can be a very slow operation
1493 
1494 		History:
1495 			Actually implemented on March 14, 2021
1496 	+/
1497 	TrueColorImage takeScreenshot() {
1498 		version(Windows)
1499 			return trueColorImageFromNativeHandle(impl.hwnd, width, height);
1500 		else version(OSXCocoa)
1501 			throw new NotYetImplementedException();
1502 		else
1503 			return trueColorImageFromNativeHandle(impl.window, width, height);
1504 	}
1505 
1506 	version(X11) {
1507 		void recreateAfterDisconnect() {
1508 			if(!stateDiscarded) return;
1509 
1510 			if(_parent !is null && _parent.stateDiscarded)
1511 				_parent.recreateAfterDisconnect();
1512 
1513 			bool wasHidden = hidden;
1514 
1515 			activeScreenPainter = null; // should already be done but just to confirm
1516 
1517 			impl.createWindow(_width, _height, _title, openglMode, _parent);
1518 
1519 			if(auto dh = dropHandler) {
1520 				dropHandler = null;
1521 				enableDragAndDrop(this, dh);
1522 			}
1523 
1524 			if(recreateAdditionalConnectionState)
1525 				recreateAdditionalConnectionState();
1526 
1527 			hidden = wasHidden;
1528 			stateDiscarded = false;
1529 		}
1530 
1531 		bool stateDiscarded;
1532 		void discardConnectionState() {
1533 			if(XDisplayConnection.display)
1534 				impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway
1535 			if(discardAdditionalConnectionState)
1536 				discardAdditionalConnectionState();
1537 			stateDiscarded = true;
1538 		}
1539 
1540 		void delegate() discardAdditionalConnectionState;
1541 		void delegate() recreateAdditionalConnectionState;
1542 
1543 	}
1544 
1545 	private DropHandler dropHandler;
1546 
1547 	SimpleWindow _parent;
1548 	bool beingOpenKeepsAppOpen = true;
1549 	/++
1550 		This creates a window with the given options. The window will be visible and able to receive input as soon as you start your event loop. You may draw on it immediately after creating the window, without needing to wait for the event loop to start if you want.
1551 
1552 		The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
1553 
1554 		Params:
1555 
1556 		width = the width of the window's client area, in pixels
1557 		height = the height of the window's client area, in pixels
1558 		title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property.
1559 		opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window.
1560 		resizable = [Resizability] has three options:
1561 			$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
1562 			$(P `fixedSize` will not allow the user to resize the window.)
1563 			$(P `automaticallyScaleIfPossible` will allow the user to resize, but will still present the original size to the API user. The contents you draw will be scaled to the size the user chose. If this scaling is not efficient, the window will be fixed size. The `windowResized` event handler will never be called. This is the default.)
1564 		windowType = The type of window you want to make.
1565 		customizationFlags = A way to make a window without a border, always on top, skip taskbar, and more. Do not use this if one of the pre-defined [WindowTypes], given in the `windowType` argument, is a good match for what you need.
1566 		parent = the parent window, if applicable. This makes the child window nested inside the parent unless you set [WindowFlags.transient], which makes it a top-level window merely owned by the "parent".
1567 	+/
1568 	this(int width = 640, int height = 480, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
1569 		claimGuiThread();
1570 		version(sdpy_thread_checks) assert(thisIsGuiThread);
1571 		this._width = width;
1572 		this._height = height;
1573 		this.openglMode = opengl;
1574 		this.resizability = resizable;
1575 		this.windowType = windowType;
1576 		this.customizationFlags = customizationFlags;
1577 		this._title = (title is null ? "D Application" : title);
1578 		this._parent = parent;
1579 		impl.createWindow(width, height, this._title, opengl, parent);
1580 
1581 		if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient))
1582 			beingOpenKeepsAppOpen = false;
1583 	}
1584 
1585 	/// ditto
1586 	this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
1587 		this(width, height, title, opengl, resizable, windowType, customizationFlags, parent);
1588 	}
1589 
1590 	/// Same as above, except using the `Size` struct instead of separate width and height.
1591 	this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) {
1592 		this(size.width, size.height, title, opengl, resizable);
1593 	}
1594 
1595 	/// ditto
1596 	this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) {
1597 		this(size, title, opengl, resizable);
1598 	}
1599 
1600 
1601 	/++
1602 		Creates a window based on the given [Image]. It's client area
1603 		width and height is equal to the image. (A window's client area
1604 		is the drawable space inside; it excludes the title bar, etc.)
1605 
1606 		Windows based on images will not be resizable and do not use OpenGL.
1607 
1608 		It will draw the image in upon creation, but this will be overwritten
1609 		upon any draws, including the initial window visible event.
1610 
1611 		You probably do not want to use this and it may be removed from
1612 		the library eventually, or I might change it to be a "permanent"
1613 		background image; one that is automatically drawn on it before any
1614 		other drawing event. idk.
1615 	+/
1616 	this(Image image, string title = null) {
1617 		this(image.width, image.height, title);
1618 		this.image = image;
1619 	}
1620 
1621 	/++
1622 		Wraps a native window handle with very little additional processing - notably no destruction
1623 		this is incomplete so don't use it for much right now. The purpose of this is to make native
1624 		windows created through the low level API (so you can use platform-specific options and
1625 		other details SimpleWindow does not expose) available to the event loop wrappers.
1626 	+/
1627 	this(NativeWindowHandle nativeWindow) {
1628 		version(Windows)
1629 			impl.hwnd = nativeWindow;
1630 		else version(X11) {
1631 			impl.window = nativeWindow;
1632 			display = XDisplayConnection.get(); // get initial display to not segfault
1633 		} else version(OSXCocoa)
1634 			throw new NotYetImplementedException();
1635 		else featureNotImplemented();
1636 		// FIXME: set the size correctly
1637 		_width = 1;
1638 		_height = 1;
1639 		nativeMapping[nativeWindow] = this;
1640 
1641 		beingOpenKeepsAppOpen = false;
1642 
1643 		CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
1644 		_suppressDestruction = true; // so it doesn't try to close
1645 	}
1646 
1647 	/// Experimental, do not use yet
1648 	/++
1649 		Grabs exclusive input from the user until you release it with
1650 		[releaseInputGrab].
1651 
1652 
1653 		Note: it is extremely rude to do this without good reason.
1654 		Reasons may include doing some kind of mouse drag operation
1655 		or popping up a temporary menu that should get events and will
1656 		be dismissed at ease by the user clicking away.
1657 
1658 		Params:
1659 			keyboard = do you want to grab keyboard input?
1660 			mouse = grab mouse input?
1661 			confine = confine the mouse cursor to inside this window?
1662 
1663 		History:
1664 			Prior to March 11, 2021, grabbing the keyboard would always also
1665 			set the X input focus. Now, it only focuses if it is a non-transient
1666 			window and otherwise manages the input direction internally.
1667 
1668 			This means spurious focus/blur events will no longer be sent and the
1669 			application will not steal focus from other applications (which the
1670 			window manager may have rejected anyway).
1671 	+/
1672 	void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) {
1673 		static if(UsingSimpledisplayX11) {
1674 			XSync(XDisplayConnection.get, 0);
1675 			if(keyboard) {
1676 				if(isTransient && _parent) {
1677 					/*
1678 					FIXME:
1679 						setting the keyboard focus is not actually that helpful, what I more likely want
1680 						is the events from the parent window to be sent over here if we're transient.
1681 					*/
1682 
1683 					_parent.inputProxy = this;
1684 				} else {
1685 					XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime);
1686 				}
1687 			}
1688 			if(mouse) {
1689 			if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 
1690 				EventMask.PointerMotionMask // FIXME: not efficient
1691 				| EventMask.ButtonPressMask
1692 				| EventMask.ButtonReleaseMask
1693 			/* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime)
1694 				)
1695 			{
1696 				XSync(XDisplayConnection.get, 0);
1697 				import core.stdc.stdio;
1698 				printf("Grab input failed %d\n", res);
1699 				//throw new Exception("Grab input failed");
1700 			} else {
1701 				// cool
1702 			}
1703 			}
1704 
1705 		} else version(Windows) {
1706 			// FIXME: keyboard?
1707 			SetCapture(impl.hwnd);
1708 			if(confine) {
1709 				RECT rcClip;
1710 				//RECT rcOldClip;
1711 				//GetClipCursor(&rcOldClip); 
1712 				GetWindowRect(hwnd, &rcClip); 
1713 				ClipCursor(&rcClip); 
1714 			}
1715 		} else version(OSXCocoa) {
1716 			throw new NotYetImplementedException();
1717 		} else static assert(0);
1718 	}
1719 
1720 	private bool isTransient() {
1721 		with(WindowTypes)
1722 		final switch(windowType) {
1723 			case normal, undecorated, eventOnly:
1724 			case nestedChild:
1725 				return (customizationFlags & WindowFlags.transient) ? true : false;
1726 			case dropdownMenu, popupMenu, notification:
1727 				return true;
1728 		}
1729 	}
1730 
1731 	private SimpleWindow inputProxy;
1732 
1733 	/++
1734 		Releases the grab acquired by [grabInput].
1735 	+/
1736 	void releaseInputGrab() {
1737 		static if(UsingSimpledisplayX11) {
1738 			XUngrabPointer(XDisplayConnection.get, CurrentTime);
1739 			if(_parent)
1740 				_parent.inputProxy = null;
1741 		} else version(Windows) {
1742 			ReleaseCapture();
1743 			ClipCursor(null); 
1744 		} else version(OSXCocoa) {
1745 			throw new NotYetImplementedException();
1746 		} else static assert(0);
1747 	}
1748 
1749 	/++
1750 		Sets the input focus to this window.
1751 
1752 		You shouldn't call this very often - please let the user control the input focus.
1753 	+/
1754 	void focus() {
1755 		static if(UsingSimpledisplayX11) {
1756 			XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime);
1757 		} else version(Windows) {
1758 			SetFocus(this.impl.hwnd);
1759 		} else version(OSXCocoa) {
1760 			throw new NotYetImplementedException();
1761 		} else static assert(0);
1762 	}
1763 
1764 	/++
1765 		Requests attention from the user for this window.
1766 
1767 
1768 		The typical result of this function is to change the color
1769 		of the taskbar icon, though it may be tweaked on specific
1770 		platforms.
1771 
1772 		It is meant to unobtrusively tell the user that something
1773 		relevant to them happened in the background and they should
1774 		check the window when they get a chance. Upon receiving the
1775 		keyboard focus, the window will automatically return to its
1776 		natural state.
1777 
1778 		If the window already has the keyboard focus, this function
1779 		may do nothing, because the user is presumed to already be
1780 		giving the window attention.
1781 
1782 		Implementation_note:
1783 
1784 		`requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION
1785 		atom on X11 and the FlashWindow function on Windows.
1786 	+/
1787 	void requestAttention() {
1788 		if(_focused)
1789 			return;
1790 
1791 		version(Windows) {
1792 			FLASHWINFO info;
1793 			info.cbSize = info.sizeof;
1794 			info.hwnd = impl.hwnd;
1795 			info.dwFlags = FLASHW_TRAY;
1796 			info.uCount = 1;
1797 
1798 			FlashWindowEx(&info);
1799 
1800 		} else version(X11) {
1801 			demandingAttention = true;
1802 			demandAttention(this, true);
1803 		} else version(OSXCocoa) {
1804 			throw new NotYetImplementedException();
1805 		} else static assert(0);
1806 	}
1807 
1808 	private bool _focused;
1809 
1810 	version(X11) private bool demandingAttention;
1811 
1812 	/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
1813 	/// You'll have to call `close()` manually if you set this delegate.
1814 	void delegate () closeQuery;
1815 
1816 	/// This will be called when window visibility was changed.
1817 	void delegate (bool becomesVisible) visibilityChanged;
1818 
1819 	/// This will be called when window becomes visible for the first time.
1820 	/// You can do OpenGL initialization here. Note that in X11 you can't call
1821 	/// [setAsCurrentOpenGlContext] right after window creation, or X11 may
1822 	/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
1823 	private bool _visibleForTheFirstTimeCalled;
1824 	void delegate () visibleForTheFirstTime;
1825 
1826 	/// Returns true if the window has been closed.
1827 	final @property bool closed() { return _closed; }
1828 
1829 	/// Returns true if the window is focused.
1830 	final @property bool focused() { return _focused; }
1831 
1832 	private bool _visible;
1833 	/// Returns true if the window is visible (mapped).
1834 	final @property bool visible() { return _visible; }
1835 
1836 	/// Closes the window. If there are no more open windows, the event loop will terminate.
1837 	void close() {
1838 		if (!_closed) {
1839 			runInGuiThread( {
1840 				if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued
1841 				if (onClosing !is null) onClosing();
1842 				impl.closeWindow();
1843 				_closed = true;
1844 			} );
1845 		}
1846 	}
1847 
1848 	/++
1849 		`close` is one of the few methods that can be called from other threads. This `shared` overload reflects that.
1850 
1851 		History:
1852 			Overload added on March 7, 2021.
1853 	+/
1854 	void close() shared {
1855 		(cast() this).close();
1856 	}
1857 
1858 	/++
1859 
1860 	+/
1861 	void maximize() {
1862 		version(Windows)
1863 			ShowWindow(impl.hwnd, SW_MAXIMIZE);
1864 		else version(X11) {
1865 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get));
1866 
1867 			// also note _NET_WM_STATE_FULLSCREEN
1868 		}
1869 
1870 	}
1871 
1872 	private bool _fullscreen;
1873 
1874 	/// not fully implemented but planned for a future release
1875 	void fullscreen(bool yes) {
1876 		version(X11)
1877 			setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes);
1878 
1879 		_fullscreen = yes;
1880 
1881 	}
1882 
1883 	bool fullscreen() {
1884 		return _fullscreen;
1885 	}
1886 
1887 	/++
1888 		Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead.
1889 
1890 	+/
1891 	void minimize() {
1892 		version(Windows)
1893 			ShowWindow(impl.hwnd, SW_MINIMIZE);
1894 		//else version(X11)
1895 			//setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true);
1896 	}
1897 
1898 	/// Alias for `hidden = false`
1899 	void show() {
1900 		hidden = false;
1901 	}
1902 
1903 	/// Alias for `hidden = true`
1904 	void hide() {
1905 		hidden = true;
1906 	}
1907 
1908 	/// Hide cursor when it enters the window.
1909 	void hideCursor() {
1910 		version(OSXCocoa) throw new NotYetImplementedException(); else
1911 		if (!_closed) impl.hideCursor();
1912 	}
1913 
1914 	/// Don't hide cursor when it enters the window.
1915 	void showCursor() {
1916 		version(OSXCocoa) throw new NotYetImplementedException(); else
1917 		if (!_closed) impl.showCursor();
1918 	}
1919 
1920 	/** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.
1921 	 *
1922 	 * Please remember that the cursor is a shared resource that should usually be left to the user's
1923 	 * control. Try to think for other approaches before using this function.
1924 	 *
1925 	 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want
1926 	 *       to use it to move mouse pointer to some active GUI area, for example, as your window won't
1927 	 *       receive "mouse moved here" event.
1928 	 */
1929 	bool warpMouse (int x, int y) {
1930 		version(X11) {
1931 			if (!_closed) { impl.warpMouse(x, y); return true; }
1932 		} else version(Windows) {
1933 			if (!_closed) {
1934 				POINT point;
1935 				point.x = x;
1936 				point.y = y;
1937 				if(ClientToScreen(impl.hwnd, &point)) {
1938 					SetCursorPos(point.x, point.y);
1939 					return true;
1940 				}
1941 			}
1942 		}
1943 		return false;
1944 	}
1945 
1946 	/// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.
1947 	void sendDummyEvent () {
1948 		version(X11) {
1949 			if (!_closed) { impl.sendDummyEvent(); }
1950 		}
1951 	}
1952 
1953 	/// Set window minimal size.
1954 	void setMinSize (int minwidth, int minheight) {
1955 		version(OSXCocoa) throw new NotYetImplementedException(); else
1956 		if (!_closed) impl.setMinSize(minwidth, minheight);
1957 	}
1958 
1959 	/// Set window maximal size.
1960 	void setMaxSize (int maxwidth, int maxheight) {
1961 		version(OSXCocoa) throw new NotYetImplementedException(); else
1962 		if (!_closed) impl.setMaxSize(maxwidth, maxheight);
1963 	}
1964 
1965 	/// Set window resize step (window size will be changed with the given granularity on supported platforms).
1966 	/// Currently only supported on X11.
1967 	void setResizeGranularity (int granx, int grany) {
1968 		version(OSXCocoa) throw new NotYetImplementedException(); else
1969 		if (!_closed) impl.setResizeGranularity(granx, grany);
1970 	}
1971 
1972 	/// Move window.
1973 	void move(int x, int y) {
1974 		version(OSXCocoa) throw new NotYetImplementedException(); else
1975 		if (!_closed) impl.move(x, y);
1976 	}
1977 
1978 	/// ditto
1979 	void move(Point p) {
1980 		version(OSXCocoa) throw new NotYetImplementedException(); else
1981 		if (!_closed) impl.move(p.x, p.y);
1982 	}
1983 
1984 	/++
1985 		Resize window.
1986 
1987 		Note that the width and height of the window are NOT instantly
1988 		updated - it waits for the window manager to approve the resize
1989 		request, which means you must return to the event loop before the
1990 		width and height are actually changed.
1991 	+/
1992 	void resize(int w, int h) {
1993 		if(!_closed && _fullscreen) fullscreen = false;
1994 		version(OSXCocoa) throw new NotYetImplementedException(); else
1995 		if (!_closed) impl.resize(w, h);
1996 	}
1997 
1998 	/// Move and resize window (this can be faster and more visually pleasant than doing it separately).
1999 	void moveResize (int x, int y, int w, int h) {
2000 		if(!_closed && _fullscreen) fullscreen = false;
2001 		version(OSXCocoa) throw new NotYetImplementedException(); else
2002 		if (!_closed) impl.moveResize(x, y, w, h);
2003 	}
2004 
2005 	private bool _hidden;
2006 
2007 	/// Returns true if the window is hidden.
2008 	final @property bool hidden() {
2009 		return _hidden;
2010 	}
2011 
2012 	/// Shows or hides the window based on the bool argument.
2013 	final @property void hidden(bool b) {
2014 		_hidden = b;
2015 		version(Windows) {
2016 			ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
2017 		} else version(X11) {
2018 			if(b)
2019 				//XUnmapWindow(impl.display, impl.window);
2020 				XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
2021 			else
2022 				XMapWindow(impl.display, impl.window);
2023 		} else version(OSXCocoa) {
2024 			throw new NotYetImplementedException();
2025 		} else static assert(0);
2026 	}
2027 
2028 	/// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.
2029 	void opacity(double opacity) @property
2030 	in {
2031 		assert(opacity >= 0 && opacity <= 1);
2032 	} do {
2033 		version (Windows) {
2034 			impl.setOpacity(cast(ubyte)(255 * opacity));
2035 		} else version (X11) {
2036 			impl.setOpacity(cast(uint)(uint.max * opacity));
2037 		} else throw new NotYetImplementedException();
2038 	}
2039 
2040 	/++
2041 		Sets your event handlers, without entering the event loop. Useful if you
2042 		have multiple windows - set the handlers on each window, then only do eventLoop on your main window.
2043 	+/
2044 	void setEventHandlers(T...)(T eventHandlers) {
2045 		// FIXME: add more events
2046 		foreach(handler; eventHandlers) {
2047 			static if(__traits(compiles, handleKeyEvent = handler)) {
2048 				handleKeyEvent = handler;
2049 			} else static if(__traits(compiles, handleCharEvent = handler)) {
2050 				handleCharEvent = handler;
2051 			} else static if(__traits(compiles, handlePulse = handler)) {
2052 				handlePulse = handler;
2053 			} else static if(__traits(compiles, handleMouseEvent = handler)) {
2054 				handleMouseEvent = handler;
2055 			} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
2056 		}
2057 	}
2058 
2059 	/// The event loop automatically returns when the window is closed
2060 	/// pulseTimeout is given in milliseconds. If pulseTimeout == 0, no
2061 	/// pulse timer is created. The event loop will block until an event
2062 	/// arrives or the pulse timer goes off.
2063 	final int eventLoop(T...)(
2064 		long pulseTimeout,    /// set to zero if you don't want a pulse.
2065 		T eventHandlers) /// delegate list like std.concurrency.receive
2066 	{
2067 		setEventHandlers(eventHandlers);
2068 
2069 		version(with_eventloop) {
2070 			// delegates event loop to my other module
2071 			version(X11)
2072 				XFlush(display);
2073 
2074 			import arsd.eventloop;
2075 			auto handle = setInterval(handlePulse, cast(int) pulseTimeout);
2076 			scope(exit) clearInterval(handle);
2077 
2078 			loop();
2079 			return 0;
2080 		} else version(OSXCocoa) {
2081 			// FIXME
2082 			if (handlePulse !is null && pulseTimeout != 0) {
2083 				timer = scheduledTimer(pulseTimeout*1e-3,
2084 					view, sel_registerName("simpledisplay_pulse"),
2085 					null, true);
2086 			}
2087 
2088             		setNeedsDisplay(view, true);
2089             		run(NSApp);
2090             		return 0;
2091         	} else {
2092 			EventLoop el = EventLoop(pulseTimeout, handlePulse);
2093 			return el.run();
2094 		}
2095 	}
2096 
2097 	/++
2098 		This lets you draw on the window (or its backing buffer) using basic
2099 		2D primitives.
2100 
2101 		Be sure to call this in a limited scope because your changes will not
2102 		actually appear on the window until ScreenPainter's destructor runs.
2103 
2104 		Returns: an instance of [ScreenPainter], which has the drawing methods
2105 		on it to draw on this window.
2106 	+/
2107 	ScreenPainter draw() {
2108 		return impl.getPainter();
2109 	}
2110 
2111 	// This is here to implement the interface we use for various native handlers.
2112 	NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
2113 
2114 	// maps native window handles to SimpleWindow instances, if there are any
2115 	// you shouldn't need this, but it is public in case you do in a native event handler or something
2116 	public __gshared SimpleWindow[NativeWindowHandle] nativeMapping;
2117 
2118 	/// Width of the window's drawable client area, in pixels.
2119 	@scriptable
2120 	final @property int width() const pure nothrow @safe @nogc { return _width; }
2121 
2122 	/// Height of the window's drawable client area, in pixels.
2123 	@scriptable
2124 	final @property int height() const pure nothrow @safe @nogc { return _height; }
2125 
2126 	private int _width;
2127 	private int _height;
2128 
2129 	// HACK: making the best of some copy constructor woes with refcounting
2130 	private ScreenPainterImplementation* activeScreenPainter_;
2131 
2132 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
2133 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
2134 
2135 	private OpenGlOptions openglMode;
2136 	private Resizability resizability;
2137 	private WindowTypes windowType;
2138 	private int customizationFlags;
2139 
2140 	/// `true` if OpenGL was initialized for this window.
2141 	@property bool isOpenGL () const pure nothrow @safe @nogc {
2142 		version(without_opengl)
2143 			return false;
2144 		else
2145 			return (openglMode == OpenGlOptions.yes);
2146 	}
2147 	@property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability.
2148 	@property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type.
2149 	@property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags.
2150 
2151 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
2152 	/// to call this, as it's not recommended to share window between threads.
2153 	void mtLock () {
2154 		version(X11) {
2155 			XLockDisplay(this.display);
2156 		}
2157 	}
2158 
2159 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
2160 	/// to call this, as it's not recommended to share window between threads.
2161 	void mtUnlock () {
2162 		version(X11) {
2163 			XUnlockDisplay(this.display);
2164 		}
2165 	}
2166 
2167 	/// Emit a beep to get user's attention.
2168 	void beep () {
2169 		version(X11) {
2170 			XBell(this.display, 100);
2171 		} else version(Windows) {
2172 			MessageBeep(0xFFFFFFFF);
2173 		}
2174 	}
2175 
2176 
2177 
2178 	version(without_opengl) {} else {
2179 
2180 		/// Put your code in here that you want to be drawn automatically when your window is uncovered. Set a handler here *before* entering your event loop any time you pass `OpenGlOptions.yes` to the constructor. Ideally, you will set this delegate immediately after constructing the `SimpleWindow`.
2181 		void delegate() redrawOpenGlScene;
2182 
2183 		/// This will allow you to change OpenGL vsync state.
2184 		final @property void vsync (bool wait) {
2185 		  if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2186 		  version(X11) {
2187 		    setAsCurrentOpenGlContext();
2188 		    glxSetVSync(display, impl.window, wait);
2189 		  } else version(Windows) {
2190 		    setAsCurrentOpenGlContext();
2191                     wglSetVSync(wait);
2192 		  }
2193 		}
2194 
2195 		/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
2196 		/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
2197 		/// enough without waiting 'em to finish their frame bussiness.
2198 		bool useGLFinish = true;
2199 
2200 		// FIXME: it should schedule it for the end of the current iteration of the event loop...
2201 		/// call this to invoke your delegate. It automatically sets up the context and flips the buffer. If you need to redraw the scene in response to an event, call this.
2202 		void redrawOpenGlSceneNow() {
2203 		  version(X11) if (!this._visible) return; // no need to do this if window is invisible
2204 			if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2205 			if(redrawOpenGlScene is null)
2206 				return;
2207 
2208 			this.mtLock();
2209 			scope(exit) this.mtUnlock();
2210 
2211 			this.setAsCurrentOpenGlContext();
2212 
2213 			redrawOpenGlScene();
2214 
2215 			this.swapOpenGlBuffers();
2216 			// at least nvidia proprietary crap segfaults on exit if you won't do this and will call glTexSubImage2D() too fast; no, `glFlush()` won't work.
2217 			if (useGLFinish) glFinish();
2218 		}
2219 
2220 		private bool redrawOpenGlSceneSoonSet = false;
2221 		private static class RedrawOpenGlSceneEvent {
2222 			SimpleWindow w;
2223 			this(SimpleWindow w) { this.w = w; }
2224 		}
2225 		private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent;
2226 		void redrawOpenGlSceneSoon() {
2227 			if(!redrawOpenGlSceneSoonSet) {
2228 				redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
2229 				this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
2230 				redrawOpenGlSceneSoonSet = true;
2231 			}
2232 			this.postEvent(redrawOpenGlSceneEvent, true);
2233 		}
2234 
2235 
2236 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
2237 		void setAsCurrentOpenGlContext() {
2238 			assert(openglMode == OpenGlOptions.yes);
2239 			version(X11) {
2240 				if(glXMakeCurrent(display, impl.window, impl.glc) == 0)
2241 					throw new Exception("glXMakeCurrent");
2242 			} else version(Windows) {
2243 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2244 				if (!wglMakeCurrent(ghDC, ghRC))
2245 					throw new Exception("wglMakeCurrent"); // let windows users suffer too
2246 			}
2247 		}
2248 
2249 		/// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
2250 		/// This doesn't throw, returning success flag instead.
2251 		bool setAsCurrentOpenGlContextNT() nothrow {
2252 			assert(openglMode == OpenGlOptions.yes);
2253 			version(X11) {
2254 				return (glXMakeCurrent(display, impl.window, impl.glc) != 0);
2255 			} else version(Windows) {
2256 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2257 				return wglMakeCurrent(ghDC, ghRC) ? true : false;
2258 			}
2259 		}
2260 
2261 		/// Releases OpenGL context, so it can be reused in, for example, different thread. This is only valid if you passed `OpenGlOptions.yes` to the constructor.
2262 		/// This doesn't throw, returning success flag instead.
2263 		bool releaseCurrentOpenGlContext() nothrow {
2264 			assert(openglMode == OpenGlOptions.yes);
2265 			version(X11) {
2266 				return (glXMakeCurrent(display, 0, null) != 0);
2267 			} else version(Windows) {
2268 				static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
2269 				return wglMakeCurrent(ghDC, null) ? true : false;
2270 			}
2271 		}
2272 
2273 		/++
2274 			simpledisplay always uses double buffering, usually automatically. This
2275 			manually swaps the OpenGL buffers.
2276 
2277 
2278 			You should not need to call this yourself because simpledisplay will do it
2279 			for you after calling your `redrawOpenGlScene`.
2280 
2281 			Remember that this may throw an exception, which you can catch in a multithreaded
2282 			application to keep your thread from dying from an unhandled exception.
2283 		+/
2284 		void swapOpenGlBuffers() {
2285 			assert(openglMode == OpenGlOptions.yes);
2286 			version(X11) {
2287 				if (!this._visible) return; // no need to do this if window is invisible
2288 				if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
2289 				glXSwapBuffers(display, impl.window);
2290 			} else version(Windows) {
2291 				SwapBuffers(ghDC);
2292 			}
2293 		}
2294 	}
2295 
2296 	/++
2297 		Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.
2298 
2299 
2300 		---
2301 			auto window = new SimpleWindow(100, 100, "First title");
2302 			window.title = "A new title";
2303 		---
2304 
2305 		You may call this function at any time.
2306 	+/
2307 	@property void title(string title) {
2308 		_title = title;
2309 		version(OSXCocoa) throw new NotYetImplementedException(); else
2310 		impl.setTitle(title);
2311 	}
2312 
2313 	private string _title;
2314 
2315 	/// Gets the title
2316 	@property string title() {
2317 		if(_title is null)
2318 			_title = getRealTitle();
2319 		return _title;
2320 	}
2321 
2322 	/++
2323 		Get the title as set by the window manager.
2324 		May not match what you attempted to set.
2325 	+/
2326 	string getRealTitle() {
2327 		static if(is(typeof(impl.getTitle())))
2328 			return impl.getTitle();
2329 		else
2330 			return null;
2331 	}
2332 
2333 	// don't use this generally it is not yet really released
2334 	version(X11)
2335 	@property Image secret_icon() {
2336 		return secret_icon_inner;
2337 	}
2338 	private Image secret_icon_inner;
2339 
2340 
2341 	/// Set the icon that is seen in the title bar or taskbar, etc., for the user.
2342 	@property void icon(MemoryImage icon) {
2343 		auto tci = icon.getAsTrueColorImage();
2344 		version(Windows) {
2345 			winIcon = new WindowsIcon(icon);
2346 			 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG
2347 		} else version(X11) {
2348 			secret_icon_inner = Image.fromMemoryImage(icon);
2349 			// FIXME: ensure this is correct
2350 			auto display = XDisplayConnection.get;
2351 			arch_ulong[] buffer;
2352 			buffer ~= icon.width;
2353 			buffer ~= icon.height;
2354 			foreach(c; tci.imageData.colors) {
2355 				arch_ulong b;
2356 				b |= c.a << 24;
2357 				b |= c.r << 16;
2358 				b |= c.g << 8;
2359 				b |= c.b;
2360 				buffer ~= b;
2361 			}
2362 
2363 			XChangeProperty(
2364 				display,
2365 				impl.window,
2366 				GetAtom!("_NET_WM_ICON", true)(display),
2367 				GetAtom!"CARDINAL"(display),
2368 				32 /* bits */,
2369 				0 /*PropModeReplace*/,
2370 				buffer.ptr,
2371 				cast(int) buffer.length);
2372 		} else version(OSXCocoa) {
2373 			throw new NotYetImplementedException();
2374 		} else static assert(0);
2375 	}
2376 
2377 	version(Windows)
2378 		private WindowsIcon winIcon;
2379 
2380 	bool _suppressDestruction;
2381 
2382 	~this() {
2383 		if(_suppressDestruction)
2384 			return;
2385 		impl.dispose();
2386 	}
2387 
2388 	private bool _closed;
2389 
2390 	// the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor
2391 	/*
2392 	ScreenPainter drawTransiently() {
2393 		return impl.getPainter();
2394 	}
2395 	*/
2396 
2397 	/// Draws an image on the window. This is meant to provide quick look
2398 	/// of a static image generated elsewhere.
2399 	@property void image(Image i) {
2400 		version(Windows) {
2401 			BITMAP bm;
2402 			HDC hdc = GetDC(hwnd);
2403 			HDC hdcMem = CreateCompatibleDC(hdc);
2404 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
2405 
2406 			GetObject(i.handle, bm.sizeof, &bm);
2407 
2408 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
2409 
2410 			SelectObject(hdcMem, hbmOld);
2411 			DeleteDC(hdcMem);
2412 			ReleaseDC(hwnd, hdc);
2413 
2414 			/*
2415 			RECT r;
2416 			r.right = i.width;
2417 			r.bottom = i.height;
2418 			InvalidateRect(hwnd, &r, false);
2419 			*/
2420 		} else
2421 		version(X11) {
2422 			if(!destroyed) {
2423 				if(i.usingXshm)
2424 				XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
2425 				else
2426 				XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
2427 			}
2428 		} else
2429 		version(OSXCocoa) {
2430 			draw().drawImage(Point(0, 0), i);
2431 			setNeedsDisplay(view, true);
2432 		} else static assert(0);
2433 	}
2434 
2435 	/++
2436 		Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect.
2437 
2438 		---
2439 		window.cursor = GenericCursor.Help;
2440 		// now the window mouse cursor is set to a generic help
2441 		---
2442 
2443 	+/
2444 	@property void cursor(MouseCursor cursor) {
2445 		version(OSXCocoa)
2446 			featureNotImplemented();
2447 		else
2448 		if(this.impl.curHidden <= 0) {
2449 			static if(UsingSimpledisplayX11) {
2450 				auto ch = cursor.cursorHandle;
2451 				XDefineCursor(XDisplayConnection.get(), this.impl.window, ch);
2452 			} else version(Windows) {
2453 				auto ch = cursor.cursorHandle;
2454 				impl.currentCursor = ch;
2455 				SetCursor(ch); // redraw without waiting for mouse movement to update
2456 			} else featureNotImplemented();
2457 		}
2458 
2459 	}
2460 
2461 	/// What follows are the event handlers. These are set automatically
2462 	/// by the eventLoop function, but are still public so you can change
2463 	/// them later. wasPressed == true means key down. false == key up.
2464 
2465 	/// Handles a low-level keyboard event. Settable through setEventHandlers.
2466 	void delegate(KeyEvent ke) handleKeyEvent;
2467 
2468 	/// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.
2469 	void delegate(dchar c) handleCharEvent;
2470 
2471 	/// Handles a timer pulse. Settable through setEventHandlers.
2472 	void delegate() handlePulse;
2473 
2474 	/// Called when the focus changes, param is if we have it (true) or are losing it (false).
2475 	void delegate(bool) onFocusChange;
2476 
2477 	/** Called inside `close()` method. Our window is still alive, and we can free various resources.
2478 	 * Sometimes it is easier to setup the delegate instead of subclassing. */
2479 	void delegate() onClosing;
2480 
2481 	/** Called when we received destroy notification. At this stage we cannot do much with our window
2482 	 * (as it is already dead, and it's native handle cannot be used), but we still can do some
2483 	 * last minute cleanup. */
2484 	void delegate() onDestroyed;
2485 
2486 	static if (UsingSimpledisplayX11)
2487 	/** Called when Expose event comes. See Xlib manual to understand the arguments.
2488 	 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself.
2489 	 * You will probably never need to setup this handler, it is for very low-level stuff.
2490 	 *
2491 	 * WARNING! Xlib is multithread-locked when this handles is called! */
2492 	bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;
2493 
2494 	//version(Windows)
2495 	//bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT;
2496 
2497 	private {
2498 		int lastMouseX = int.min;
2499 		int lastMouseY = int.min;
2500 		void mdx(ref MouseEvent ev) {
2501 			if(lastMouseX == int.min || lastMouseY == int.min) {
2502 				ev.dx = 0;
2503 				ev.dy = 0;
2504 			} else {
2505 				ev.dx = ev.x - lastMouseX;
2506 				ev.dy = ev.y - lastMouseY;
2507 			}
2508 
2509 			lastMouseX = ev.x;
2510 			lastMouseY = ev.y;
2511 		}
2512 	}
2513 
2514 	/// Mouse event handler. Settable through setEventHandlers.
2515 	void delegate(MouseEvent) handleMouseEvent;
2516 
2517 	/// use to redraw child widgets if you use system apis to add stuff
2518 	void delegate() paintingFinished;
2519 
2520 	void delegate() paintingFinishedDg() {
2521 		return paintingFinished;
2522 	}
2523 
2524 	/// handle a resize, after it happens. You must construct the window with Resizability.allowResizing
2525 	/// for this to ever happen.
2526 	void delegate(int width, int height) windowResized;
2527 
2528 	/** Platform specific - handle any native messages this window gets.
2529 	  *
2530 	  * Note: this is called *in addition to* other event handlers, unless you return zero indicating that you handled it.
2531 
2532 	  * On Windows, it takes the form of int delegate(HWND,UINT, WPARAM, LPARAM).
2533 
2534 	  * On X11, it takes the form of int delegate(XEvent).
2535 
2536 	  * IMPORTANT: it used to be static in old versions of simpledisplay.d, but I always used
2537 	  * it as if it wasn't static... so now I just fixed it so it isn't anymore.
2538 	**/
2539 	NativeEventHandler handleNativeEvent;
2540 
2541 	/// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop.
2542 	/// If you used to use handleNativeEvent depending on it being static, just change it to use
2543 	/// this instead and it will work the same way.
2544 	__gshared NativeEventHandler handleNativeGlobalEvent;
2545 
2546 //  private:
2547 	/// The native implementation is available, but you shouldn't use it unless you are
2548 	/// familiar with the underlying operating system, don't mind depending on it, and
2549 	/// know simpledisplay.d's internals too. It is virtually private; you can hopefully
2550 	/// do what you need to do with handleNativeEvent instead.
2551 	///
2552 	/// This is likely to eventually change to be just a struct holding platform-specific
2553 	/// handles instead of a template mixin at some point because I'm not happy with the
2554 	/// code duplication here (ironically).
2555 	mixin NativeSimpleWindowImplementation!() impl;
2556 
2557 	/**
2558 		This is in-process one-way (from anything to window) event sending mechanics.
2559 		It is thread-safe, so it can be used in multi-threaded applications to send,
2560 		for example, "wake up and repaint" events when thread completed some operation.
2561 		This will allow to avoid using timer pulse to check events with synchronization,
2562 		'cause event handler will be called in UI thread. You can stop guessing which
2563 		pulse frequency will be enough for your app.
2564 		Note that events handlers may be called in arbitrary order, i.e. last registered
2565 		handler can be called first, and vice versa.
2566 	*/
2567 public:
2568 	/** Is our custom event queue empty? Can be used in simple cases to prevent
2569 	 * "spamming" window with events it can't cope with.
2570 	 * It is safe to call this from non-UI threads.
2571 	 */
2572 	@property bool eventQueueEmpty() () {
2573 		synchronized(this) {
2574 			foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false;
2575 		}
2576 		return true;
2577 	}
2578 
2579 	/** Does our custom event queue contains at least one with the given type?
2580 	 * Can be used in simple cases to prevent "spamming" window with events
2581 	 * it can't cope with.
2582 	 * It is safe to call this from non-UI threads.
2583 	 */
2584 	@property bool eventQueued(ET:Object) () {
2585 		synchronized(this) {
2586 			foreach (const ref o; eventQueue[0..eventQueueUsed]) {
2587 				if (!o.doProcess) {
2588 					if (cast(ET)(o.evt)) return true;
2589 				}
2590 			}
2591 		}
2592 		return false;
2593 	}
2594 
2595 	/** Add listener for custom event. Can be used like this:
2596 	 *
2597 	 * ---------------------
2598 	 *   auto eid = win.addEventListener((MyStruct evt) { ... });
2599 	 *   ...
2600 	 *   win.removeEventListener(eid);
2601 	 * ---------------------
2602 	 *
2603 	 * Returns: 0 on failure (should never happen, so ignore it)
2604 	 *
2605 	 * $(WARNING Don't use this method in object destructors!)
2606 	 *
2607 	 * $(WARNING It is better to register all event handlers and don't remove 'em,
2608 	 *           'cause if event handler id counter will overflow, you won't be able
2609 	 *           to register any more events.)
2610 	 */
2611 	uint addEventListener(ET:Object) (void delegate (ET) dg) {
2612 		if (dg is null) return 0; // ignore empty handlers
2613 		synchronized(this) {
2614 			//FIXME: abort on overflow?
2615 			if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all.
2616 			EventHandlerEntry e;
2617 			e.dg = delegate (Object o) {
2618 				if (auto co = cast(ET)o) {
2619 					try {
2620 						dg(co);
2621 					} catch (Exception) {
2622 						// sorry!
2623 					}
2624 					return true;
2625 				}
2626 				return false;
2627 			};
2628 			e.id = lastUsedHandlerId;
2629 			auto optr = eventHandlers.ptr;
2630 			eventHandlers ~= e;
2631 			if (eventHandlers.ptr !is optr) {
2632 				import core.memory : GC;
2633 				if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR);
2634 			}
2635 			return lastUsedHandlerId;
2636 		}
2637 	}
2638 
2639 	/// Remove event listener. It is safe to pass invalid event id here.
2640 	/// $(WARNING Don't use this method in object destructors!)
2641 	void removeEventListener() (uint id) {
2642 		if (id == 0 || id > lastUsedHandlerId) return;
2643 		synchronized(this) {
2644 			foreach (immutable idx; 0..eventHandlers.length) {
2645 				if (eventHandlers[idx].id == id) {
2646 					foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c];
2647 					eventHandlers[$-1].dg = null;
2648 					eventHandlers.length -= 1;
2649 					eventHandlers.assumeSafeAppend;
2650 					return;
2651 				}
2652 			}
2653 		}
2654 	}
2655 
2656 	/// Post event to queue. It is safe to call this from non-UI threads.
2657 	/// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds.
2658 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
2659 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
2660 	bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) {
2661 		if (this.closed) return false; // closed windows can't handle events
2662 
2663 		// remove all events of type `ET`
2664 		void removeAllET () {
2665 			uint eidx = 0, ec = eventQueueUsed;
2666 			auto eptr = eventQueue.ptr;
2667 			while (eidx < ec) {
2668 				if (eptr.doProcess) { ++eidx; ++eptr; continue; }
2669 				if (cast(ET)eptr.evt !is null) {
2670 					// i found her!
2671 					if (inCustomEventProcessor) {
2672 						// if we're in custom event processing loop, processor will clear it for us
2673 						eptr.evt = null;
2674 						++eidx;
2675 						++eptr;
2676 					} else {
2677 						foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
2678 						ec = --eventQueueUsed;
2679 						// clear last event (it is already copied)
2680 						eventQueue.ptr[ec].evt = null;
2681 					}
2682 				} else {
2683 					++eidx;
2684 					++eptr;
2685 				}
2686 			}
2687 		}
2688 
2689 		if (evt is null) {
2690 			if (replace) { synchronized(this) removeAllET(); }
2691 			// ignore empty events, they can't be handled anyway
2692 			return false;
2693 		}
2694 
2695 		// add events even if no event FD/event object created yet
2696 		synchronized(this) {
2697 			if (replace) removeAllET();
2698 			if (eventQueueUsed == uint.max) return false; // just in case
2699 			if (eventQueueUsed < eventQueue.length) {
2700 				eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs);
2701 			} else {
2702 				if (eventQueue.capacity == eventQueue.length) {
2703 					// need to reallocate; do a trick to ensure that old array is cleared
2704 					auto oarr = eventQueue;
2705 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
2706 					// just in case, do yet another check
2707 					if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null;
2708 					import core.memory : GC;
2709 					if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR);
2710 				} else {
2711 					auto optr = eventQueue.ptr;
2712 					eventQueue ~= QueuedEvent(evt, timeoutmsecs);
2713 					assert(eventQueue.ptr is optr);
2714 				}
2715 				++eventQueueUsed;
2716 				assert(eventQueueUsed == eventQueue.length);
2717 			}
2718 			if (!eventWakeUp()) {
2719 				// can't wake up event processor, so there is no reason to keep the event
2720 				assert(eventQueueUsed > 0);
2721 				eventQueue[--eventQueueUsed].evt = null;
2722 				return false;
2723 			}
2724 			return true;
2725 		}
2726 	}
2727 
2728 	/// Post event to queue. It is safe to call this from non-UI threads.
2729 	/// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all)
2730 	/// Returns `true` if event was queued. Always returns `false` if `evt` is null.
2731 	bool postEvent(ET:Object) (ET evt, bool replace=false) {
2732 		return postTimeout!ET(evt, 0, replace);
2733 	}
2734 
2735 private:
2736 	private import core.time : MonoTime;
2737 
2738 	version(Posix) {
2739 		__gshared int customEventFDRead = -1;
2740 		__gshared int customEventFDWrite = -1;
2741 		__gshared int customSignalFD = -1;
2742 	} else version(Windows) {
2743 		__gshared HANDLE customEventH = null;
2744 	}
2745 
2746 	// wake up event processor
2747 	static bool eventWakeUp () {
2748 		version(X11) {
2749 			import core.sys.posix.unistd : write;
2750 			ulong n = 1;
2751 			if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof);
2752 			return true;
2753 		} else version(Windows) {
2754 			if (customEventH !is null) SetEvent(customEventH);
2755 			return true;
2756 		} else {
2757 			// not implemented for other OSes
2758 			return false;
2759 		}
2760 	}
2761 
2762 	static struct QueuedEvent {
2763 		Object evt;
2764 		bool timed = false;
2765 		MonoTime hittime = MonoTime.zero;
2766 		bool doProcess = false; // process event at the current iteration (internal flag)
2767 
2768 		this (Object aevt, uint toutmsecs) {
2769 			evt = aevt;
2770 			if (toutmsecs > 0) {
2771 				import core.time : msecs;
2772 				timed = true;
2773 				hittime = MonoTime.currTime+toutmsecs.msecs;
2774 			}
2775 		}
2776 	}
2777 
2778 	alias CustomEventHandler = bool delegate (Object o) nothrow;
2779 	static struct EventHandlerEntry {
2780 		CustomEventHandler dg;
2781 		uint id;
2782 	}
2783 
2784 	uint lastUsedHandlerId;
2785 	EventHandlerEntry[] eventHandlers;
2786 	QueuedEvent[] eventQueue = null;
2787 	uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes
2788 	bool inCustomEventProcessor = false; // required to properly remove events
2789 
2790 	// process queued events and call custom event handlers
2791 	// this will not process events posted from called handlers (such events are postponed for the next iteration)
2792 	void processCustomEvents () {
2793 		bool hasSomethingToDo = false;
2794 		uint ecount;
2795 		bool ocep;
2796 		synchronized(this) {
2797 			ocep = inCustomEventProcessor;
2798 			inCustomEventProcessor = true;
2799 			ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration
2800 			auto ctt = MonoTime.currTime;
2801 			bool hasEmpty = false;
2802 			// mark events to process (this is required for `eventQueued()`)
2803 			foreach (ref qe; eventQueue[0..ecount]) {
2804 				if (qe.evt is null) { hasEmpty = true; continue; }
2805 				if (qe.timed) {
2806 					qe.doProcess = (qe.hittime <= ctt);
2807 				} else {
2808 					qe.doProcess = true;
2809 				}
2810 				hasSomethingToDo = (hasSomethingToDo || qe.doProcess);
2811 			}
2812 			if (!hasSomethingToDo) {
2813 				// remove empty events
2814 				if (hasEmpty) {
2815 					uint eidx = 0, ec = eventQueueUsed;
2816 					auto eptr = eventQueue.ptr;
2817 					while (eidx < ec) {
2818 						if (eptr.evt is null) {
2819 							foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
2820 							ec = --eventQueueUsed;
2821 							eventQueue.ptr[ec].evt = null; // make GC life easier
2822 						} else {
2823 							++eidx;
2824 							++eptr;
2825 						}
2826 					}
2827 				}
2828 				inCustomEventProcessor = ocep;
2829 				return;
2830 			}
2831 		}
2832 		// process marked events
2833 		uint efree = 0; // non-processed events will be put at this index
2834 		EventHandlerEntry[] eh;
2835 		Object evt;
2836 		foreach (immutable eidx; 0..ecount) {
2837 			synchronized(this) {
2838 				if (!eventQueue[eidx].doProcess) {
2839 					// skip this event
2840 					assert(efree <= eidx);
2841 					if (efree != eidx) {
2842 						// copy this event to queue start
2843 						eventQueue[efree] = eventQueue[eidx];
2844 						eventQueue[eidx].evt = null; // just in case
2845 					}
2846 					++efree;
2847 					continue;
2848 				}
2849 				evt = eventQueue[eidx].evt;
2850 				eventQueue[eidx].evt = null; // in case event handler will hit GC
2851 				if (evt is null) continue; // just in case
2852 				// try all handlers; this can be slow, but meh...
2853 				eh = eventHandlers;
2854 			}
2855 			foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt);
2856 			evt = null;
2857 			eh = null;
2858 		}
2859 		synchronized(this) {
2860 			// move all unprocessed events to queue top; efree holds first "free index"
2861 			foreach (immutable eidx; ecount..eventQueueUsed) {
2862 				assert(efree <= eidx);
2863 				if (efree != eidx) eventQueue[efree] = eventQueue[eidx];
2864 				++efree;
2865 			}
2866 			eventQueueUsed = efree;
2867 			// wake up event processor on next event loop iteration if we have more queued events
2868 			// also, remove empty events
2869 			bool awaken = false;
2870 			uint eidx = 0, ec = eventQueueUsed;
2871 			auto eptr = eventQueue.ptr;
2872 			while (eidx < ec) {
2873 				if (eptr.evt is null) {
2874 					foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c];
2875 					ec = --eventQueueUsed;
2876 					eventQueue.ptr[ec].evt = null; // make GC life easier
2877 				} else {
2878 					if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; }
2879 					++eidx;
2880 					++eptr;
2881 				}
2882 			}
2883 			inCustomEventProcessor = ocep;
2884 		}
2885 	}
2886 
2887 	// for all windows in nativeMapping
2888 	static void processAllCustomEvents () {
2889 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
2890 			if (sw is null || sw.closed) continue;
2891 			sw.processCustomEvents();
2892 		}
2893 
2894 		runPendingRunInGuiThreadDelegates();
2895 	}
2896 
2897 	// 0: infinite (i.e. no scheduled events in queue)
2898 	uint eventQueueTimeoutMSecs () {
2899 		synchronized(this) {
2900 			if (eventQueueUsed == 0) return 0;
2901 			if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
2902 			uint res = int.max;
2903 			auto ctt = MonoTime.currTime;
2904 			foreach (const ref qe; eventQueue[0..eventQueueUsed]) {
2905 				if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c)
2906 				if (qe.doProcess) continue; // just in case
2907 				if (!qe.timed) return 1; // minimal
2908 				if (qe.hittime <= ctt) return 1; // minimal
2909 				auto tms = (qe.hittime-ctt).total!"msecs";
2910 				if (tms < 1) tms = 1; // safety net
2911 				if (tms >= int.max) tms = int.max-1; // and another safety net
2912 				if (res > tms) res = cast(uint)tms;
2913 			}
2914 			return (res >= int.max ? 0 : res);
2915 		}
2916 	}
2917 
2918 	// for all windows in nativeMapping
2919 	static uint eventAllQueueTimeoutMSecs () {
2920 		uint res = uint.max;
2921 		foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) {
2922 			if (sw is null || sw.closed) continue;
2923 			uint to = sw.eventQueueTimeoutMSecs();
2924 			if (to && to < res) {
2925 				res = to;
2926 				if (to == 1) break; // can't have less than this
2927 			}
2928 		}
2929 		return (res >= int.max ? 0 : res);
2930 	}
2931 }
2932 
2933 /* Drag and drop support { */
2934 version(X11) {
2935 
2936 } else version(Windows) {
2937 	import core.sys.windows.uuid;
2938 	import core.sys.windows.ole2;
2939 	import core.sys.windows.oleidl;
2940 	import core.sys.windows.objidl;
2941 	import core.sys.windows.wtypes;
2942 
2943 	pragma(lib, "ole32");
2944 	void initDnd() {
2945 		auto err = OleInitialize(null);
2946 		if(err != S_OK && err != S_FALSE)
2947 			throw new Exception("init");//err);
2948 	}
2949 }
2950 /* } End drag and drop support */
2951 
2952 
2953 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
2954 /// See [GenericCursor]
2955 class MouseCursor {
2956 	int osId;
2957 	bool isStockCursor;
2958 	private this(int osId) {
2959 		this.osId = osId;
2960 		this.isStockCursor = true;
2961 	}
2962 
2963 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
2964 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
2965 
2966 	version(Windows) {
2967 		HCURSOR cursor_;
2968 		HCURSOR cursorHandle() {
2969 			if(cursor_ is null)
2970 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
2971 			return cursor_;
2972 		}
2973 
2974 	} else static if(UsingSimpledisplayX11) {
2975 		Cursor cursor_ = None;
2976 		int xDisplaySequence;
2977 
2978 		Cursor cursorHandle() {
2979 			if(this.osId == None)
2980 				return None;
2981 
2982 			// we need to reload if we on a new X connection
2983 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
2984 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
2985 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
2986 			}
2987 			return cursor_;
2988 		}
2989 	}
2990 }
2991 
2992 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
2993 // https://tronche.com/gui/x/xlib/appendix/b/
2994 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
2995 /// Note that there is no exact appearance guaranteed for any of these items; it may change appearance on different operating systems or future simpledisplay versions
2996 enum GenericCursorType {
2997 	Default, /// The default arrow pointer.
2998 	Wait, /// A cursor indicating something is loading and the user must wait.
2999 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
3000 	Help, /// A cursor indicating the user can get help about the pointer location.
3001 	Cross, /// A crosshair.
3002 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
3003 	Move, /// Pointer indicating movement is possible. May also be used as SizeAll
3004 	UpArrow, /// An arrow pointing straight up.
3005 	Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11.
3006 	NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11.
3007 	SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator)
3008 	SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator)
3009 	SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator)
3010 	SizeWe, /// Arrow pointing west and east (left/right edge resize indicator)
3011 
3012 }
3013 
3014 /*
3015 	X_plus == css cell == Windows ?
3016 */
3017 
3018 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
3019 static struct GenericCursor {
3020 	static:
3021 	///
3022 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
3023 		static MouseCursor mc;
3024 
3025 		auto type = __traits(getMember, GenericCursorType, str);
3026 
3027 		if(mc is null) {
3028 
3029 			version(Windows) {
3030 				int osId;
3031 				final switch(type) {
3032 					case GenericCursorType.Default: osId = IDC_ARROW; break;
3033 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
3034 					case GenericCursorType.Hand: osId = IDC_HAND; break;
3035 					case GenericCursorType.Help: osId = IDC_HELP; break;
3036 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
3037 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
3038 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
3039 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
3040 					case GenericCursorType.Progress: osId = IDC_APPSTARTING; break;
3041 					case GenericCursorType.NotAllowed: osId = IDC_NO; break;
3042 					case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break;
3043 					case GenericCursorType.SizeNs: osId = IDC_SIZENS; break;
3044 					case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break;
3045 					case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break;
3046 				}
3047 			} else static if(UsingSimpledisplayX11) {
3048 				int osId;
3049 				final switch(type) {
3050 					case GenericCursorType.Default: osId = None; break;
3051 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
3052 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
3053 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
3054 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
3055 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
3056 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
3057 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
3058 					case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break;
3059 
3060 					case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break;
3061 					case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break;
3062 					case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break;
3063 					case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break;
3064 					case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break;
3065 				}
3066 
3067 			} else featureNotImplemented();
3068 
3069 			mc = new MouseCursor(osId);
3070 		}
3071 		return mc;
3072 	}
3073 }
3074 
3075 
3076 /++
3077 	If you want to get more control over the event loop, you can use this.
3078 
3079 	Typically though, you can just call [SimpleWindow.eventLoop].
3080 +/
3081 struct EventLoop {
3082 	@disable this();
3083 
3084 	/// Gets a reference to an existing event loop
3085 	static EventLoop get() {
3086 		return EventLoop(0, null);
3087 	}
3088 
3089 	__gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
3090 
3091 	/// Construct an application-global event loop for yourself
3092 	/// See_Also: [SimpleWindow.setEventHandlers]
3093 	this(long pulseTimeout, void delegate() handlePulse) {
3094 		synchronized(monitor) {
3095 			if(impl is null) {
3096 				claimGuiThread();
3097 				version(sdpy_thread_checks) assert(thisIsGuiThread);
3098 				impl = new EventLoopImpl(pulseTimeout, handlePulse);
3099 			} else {
3100 				if(pulseTimeout) {
3101 					impl.pulseTimeout = pulseTimeout;
3102 					impl.handlePulse = handlePulse;
3103 				}
3104 			}
3105 			impl.refcount++;
3106 		}
3107 	}
3108 
3109 	~this() {
3110 		if(impl is null)
3111 			return;
3112 		impl.refcount--;
3113 		if(impl.refcount == 0) {
3114 			impl.dispose();
3115 			if(thisIsGuiThread)
3116 				guiThreadFinalize();
3117 		}
3118 
3119 	}
3120 
3121 	this(this) {
3122 		if(impl is null)
3123 			return;
3124 		impl.refcount++;
3125 	}
3126 
3127 	/// Runs the event loop until the whileCondition, if present, returns false
3128 	int run(bool delegate() whileCondition = null) {
3129 		assert(impl !is null);
3130 		impl.notExited = true;
3131 		return impl.run(whileCondition);
3132 	}
3133 
3134 	/// Exits the event loop
3135 	void exit() {
3136 		assert(impl !is null);
3137 		impl.notExited = false;
3138 	}
3139 
3140 	version(linux)
3141 	ref void delegate(int) signalHandler() {
3142 		assert(impl !is null);
3143 		return impl.signalHandler;
3144 	}
3145 
3146 	__gshared static EventLoopImpl* impl;
3147 }
3148 
3149 version(linux)
3150 	void delegate(int, int) globalHupHandler;
3151 
3152 version(Posix)
3153 	void makeNonBlocking(int fd) {
3154 		import fcntl = core.sys.posix.fcntl;
3155 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
3156 		if(flags == -1)
3157 			throw new Exception("fcntl get");
3158 		flags |= fcntl.O_NONBLOCK;
3159 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
3160 		if(s == -1)
3161 			throw new Exception("fcntl set");
3162 	}
3163 
3164 struct EventLoopImpl {
3165 	int refcount;
3166 
3167 	bool notExited = true;
3168 
3169 	version(linux) {
3170 		static import ep = core.sys.linux.epoll;
3171 		static import unix = core.sys.posix.unistd;
3172 		static import err = core.stdc.errno;
3173 		import core.sys.linux.timerfd;
3174 
3175 		void delegate(int) signalHandler;
3176 	}
3177 
3178 	version(X11) {
3179 		int pulseFd = -1;
3180 		version(linux) ep.epoll_event[16] events = void;
3181 	} else version(Windows) {
3182 		Timer pulser;
3183 		HANDLE[] handles;
3184 	}
3185 
3186 
3187 	/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
3188 	/// to call this, as it's not recommended to share window between threads.
3189 	void mtLock () {
3190 		version(X11) {
3191 			XLockDisplay(this.display);
3192 		}
3193 	}
3194 
3195 	version(X11)
3196 	auto display() { return XDisplayConnection.get; }
3197 
3198 	/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
3199 	/// to call this, as it's not recommended to share window between threads.
3200 	void mtUnlock () {
3201 		version(X11) {
3202 			XUnlockDisplay(this.display);
3203 		}
3204 	}
3205 
3206 	version(with_eventloop)
3207 	void initialize(long pulseTimeout) {}
3208 	else
3209 	void initialize(long pulseTimeout) {
3210 		version(Windows) {
3211 			if(pulseTimeout && handlePulse !is null)
3212 				pulser = new Timer(cast(int) pulseTimeout, handlePulse);
3213 
3214 			if (customEventH is null) {
3215 				customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null);
3216 				if (customEventH !is null) {
3217 					handles ~= customEventH;
3218 				} else {
3219 					// this is something that should not be; better be safe than sorry
3220 					throw new Exception("can't create eventfd for custom event processing");
3221 				}
3222 			}
3223 
3224 			SimpleWindow.processAllCustomEvents(); // process events added before event object creation
3225 		}
3226 
3227 		version(linux) {
3228 			prepareEventLoop();
3229 			{
3230 				auto display = XDisplayConnection.get;
3231 				// adding Xlib file
3232 				ep.epoll_event ev = void;
3233 				{ import core.stdc..string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3234 				ev.events = ep.EPOLLIN;
3235 				ev.data.fd = display.fd;
3236 				//import std.conv;
3237 				if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1)
3238 					throw new Exception("add x fd");// ~ to!string(epollFd));
3239 				displayFd = display.fd;
3240 			}
3241 
3242 			if(pulseTimeout && handlePulse !is null) {
3243 				pulseFd = timerfd_create(CLOCK_MONOTONIC, 0);
3244 				if(pulseFd == -1)
3245 					throw new Exception("pulse timer create failed");
3246 
3247 				itimerspec value;
3248 				value.it_value.tv_sec = cast(int) (pulseTimeout / 1000);
3249 				value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000;
3250 
3251 				value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000);
3252 				value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000;
3253 
3254 				if(timerfd_settime(pulseFd, 0, &value, null) == -1)
3255 					throw new Exception("couldn't make pulse timer");
3256 
3257 				ep.epoll_event ev = void;
3258 				{ import core.stdc..string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3259 				ev.events = ep.EPOLLIN;
3260 				ev.data.fd = pulseFd;
3261 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev);
3262 			}
3263 
3264 			// eventfd for custom events
3265 			if (customEventFDWrite == -1) {
3266 				customEventFDWrite = eventfd(0, 0);
3267 				customEventFDRead = customEventFDWrite;
3268 				if (customEventFDRead >= 0) {
3269 					ep.epoll_event ev = void;
3270 					{ import core.stdc..string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3271 					ev.events = ep.EPOLLIN;
3272 					ev.data.fd = customEventFDRead;
3273 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev);
3274 				} else {
3275 					// this is something that should not be; better be safe than sorry
3276 					throw new Exception("can't create eventfd for custom event processing");
3277 				}
3278 			}
3279 
3280 			if (customSignalFD == -1) {
3281 				import core.sys.linux.sys.signalfd;
3282 
3283 				sigset_t sigset;
3284 				auto err = sigemptyset(&sigset);
3285 				assert(!err);
3286 				err = sigaddset(&sigset, SIGINT);
3287 				assert(!err);
3288 				err = sigaddset(&sigset, SIGHUP);
3289 				assert(!err);
3290 				err = sigprocmask(SIG_BLOCK, &sigset, null);
3291 				assert(!err);
3292 
3293 				customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
3294 				assert(customSignalFD != -1);
3295 
3296 				ep.epoll_event ev = void;
3297 				{ import core.stdc..string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3298 				ev.events = ep.EPOLLIN;
3299 				ev.data.fd = customSignalFD;
3300 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
3301 			}
3302 		} else version(Posix) {
3303 			prepareEventLoop();
3304 			if (customEventFDRead == -1) {
3305 				int[2] bfr;
3306 				import core.sys.posix.unistd;
3307 				auto ret = pipe(bfr);
3308 				if(ret == -1) throw new Exception("pipe");
3309 				customEventFDRead = bfr[0];
3310 				customEventFDWrite = bfr[1];
3311 			}
3312 
3313 		}
3314 
3315 		SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
3316 
3317 		version(linux) {
3318 			this.mtLock();
3319 			scope(exit) this.mtUnlock();
3320 			XPending(display); // no, really
3321 		}
3322 
3323 		disposed = false;
3324 	}
3325 
3326 	bool disposed = true;
3327 	version(X11)
3328 		int displayFd = -1;
3329 
3330 	version(with_eventloop)
3331 	void dispose() {}
3332 	else
3333 	void dispose() {
3334 		disposed = true;
3335 		version(X11) {
3336 			if(pulseFd != -1) {
3337 				import unix = core.sys.posix.unistd;
3338 				unix.close(pulseFd);
3339 				pulseFd = -1;
3340 			}
3341 
3342 				version(linux)
3343 				if(displayFd != -1) {
3344 					// clean up xlib fd when we exit, in case we come back later e.g. X disconnect and reconnect with new FD, don't want to still keep the old one around
3345 					ep.epoll_event ev = void;
3346 					{ import core.stdc..string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
3347 					ev.events = ep.EPOLLIN;
3348 					ev.data.fd = displayFd;
3349 					//import std.conv;
3350 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev);
3351 					displayFd = -1;
3352 				}
3353 
3354 		} else version(Windows) {
3355 			if(pulser !is null) {
3356 				pulser.destroy();
3357 				pulser = null;
3358 			}
3359 			if (customEventH !is null) {
3360 				CloseHandle(customEventH);
3361 				customEventH = null;
3362 			}
3363 		}
3364 	}
3365 
3366 	this(long pulseTimeout, void delegate() handlePulse) {
3367 		this.pulseTimeout = pulseTimeout;
3368 		this.handlePulse = handlePulse;
3369 		initialize(pulseTimeout);
3370 	}
3371 
3372 	private long pulseTimeout;
3373 	void delegate() handlePulse;
3374 
3375 	~this() {
3376 		dispose();
3377 	}
3378 
3379 	version(Posix)
3380 	ref int customEventFDRead() { return SimpleWindow.customEventFDRead; }
3381 	version(Posix)
3382 	ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; }
3383 	version(linux)
3384 	ref int customSignalFD() { return SimpleWindow.customSignalFD; }
3385 	version(Windows)
3386 	ref auto customEventH() { return SimpleWindow.customEventH; }
3387 
3388 	version(with_eventloop) {
3389 		int loopHelper(bool delegate() whileCondition) {
3390 			// FIXME: whileCondition
3391 			import arsd.eventloop;
3392 			loop();
3393 			return 0;
3394 		}
3395 	} else
3396 	int loopHelper(bool delegate() whileCondition) {
3397 		version(X11) {
3398 			bool done = false;
3399 
3400 			XFlush(display);
3401 			insideXEventLoop = true;
3402 			scope(exit) insideXEventLoop = false;
3403 
3404 			version(linux) {
3405 				while(!done && (whileCondition is null || whileCondition() == true) && notExited) {
3406 					bool forceXPending = false;
3407 					auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
3408 					// eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic
3409 					{
3410 						this.mtLock();
3411 						scope(exit) this.mtUnlock();
3412 						if (XEventsQueued(this.display, QueueMode.QueuedAlready)) { forceXPending = true; if (wto > 10 || wto <= 0) wto = 10; } // so libX event loop will be able to do it's work
3413 					}
3414 					//{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); }
3415 					auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto));
3416 					if(nfds == -1) {
3417 						if(err.errno == err.EINTR) {
3418 							//if(forceXPending) goto xpending;
3419 							continue; // interrupted by signal, just try again
3420 						}
3421 						throw new Exception("epoll wait failure");
3422 					}
3423 
3424 					SimpleWindow.processAllCustomEvents(); // anyway
3425 					//version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
3426 					foreach(idx; 0 .. nfds) {
3427 						if(done) break;
3428 						auto fd = events[idx].data.fd;
3429 						assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
3430 						auto flags = events[idx].events;
3431 						if(flags & ep.EPOLLIN) {
3432 							if (fd == customSignalFD) {
3433 								version(linux) {
3434 									import core.sys.linux.sys.signalfd;
3435 									import core.sys.posix.unistd : read;
3436 									signalfd_siginfo info;
3437 									read(customSignalFD, &info, info.sizeof);
3438 
3439 									auto sig = info.ssi_signo;
3440 
3441 									if(EventLoop.get.signalHandler !is null) {
3442 										EventLoop.get.signalHandler()(sig);
3443 									} else {
3444 										EventLoop.get.exit();
3445 									}
3446 								}
3447 							} else if(fd == display.fd) {
3448 								version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); }
3449 								this.mtLock();
3450 								scope(exit) this.mtUnlock();
3451 								while(!done && XPending(display)) {
3452 									done = doXNextEvent(this.display);
3453 								}
3454 								forceXPending = false;
3455 							} else if(fd == pulseFd) {
3456 								long expirationCount;
3457 								// if we go over the count, I ignore it because i don't want the pulse to go off more often and eat tons of cpu time...
3458 
3459 								handlePulse();
3460 
3461 								// read just to clear the buffer so poll doesn't trigger again
3462 								// BTW I read AFTER the pulse because if the pulse handler takes
3463 								// a lot of time to execute, we don't want the app to get stuck
3464 								// in a loop of timer hits without a chance to do anything else
3465 								//
3466 								// IOW handlePulse happens at most once per pulse interval.
3467 								unix.read(pulseFd, &expirationCount, expirationCount.sizeof);
3468 								forceXPending = true; // some events might have been added while the pulse was going off and xlib already read it from the fd (like as a result of a draw done in the timer handler). if so we need to flush that separately to ensure it is not delayed
3469 							} else if (fd == customEventFDRead) {
3470 								// we have some custom events; process 'em
3471 								import core.sys.posix.unistd : read;
3472 								ulong n;
3473 								read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
3474 								//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
3475 								//SimpleWindow.processAllCustomEvents();
3476 							} else {
3477 								// some other timer
3478 								version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); }
3479 
3480 								if(Timer* t = fd in Timer.mapping)
3481 									(*t).trigger();
3482 
3483 								if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
3484 									(*pfr).ready(flags);
3485 
3486 								// or i might add support for other FDs too
3487 								// but for now it is just timer
3488 								// (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff.
3489 							}
3490 						}
3491 						if(flags & ep.EPOLLHUP) {
3492 							if(PosixFdReader* pfr = fd in PosixFdReader.mapping)
3493 								(*pfr).hup(flags);
3494 							if(globalHupHandler)
3495 								globalHupHandler(fd, flags);
3496 						}
3497 						/+
3498 						} else {
3499 							// not interested in OUT, we are just reading here.
3500 							//
3501 							// error or hup might also be reported
3502 							// but it shouldn't here since we are only
3503 							// using a few types of FD and Xlib will report
3504 							// if it dies.
3505 							// so instead of thoughtfully handling it, I'll
3506 							// just throw. for now at least
3507 
3508 							throw new Exception("epoll did something else");
3509 						}
3510 						+/
3511 					}
3512 					// if we won't call `XPending()` here, libX may delay some internal event delivery.
3513 					// i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled!
3514 					xpending:
3515 					if (!done && forceXPending) {
3516 						this.mtLock();
3517 						scope(exit) this.mtUnlock();
3518 						//{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); }
3519 						while(!done && XPending(display)) {
3520 							done = doXNextEvent(this.display);
3521 						}
3522 					}
3523 				}
3524 			} else {
3525 				// Generic fallback: yes to simple pulse support,
3526 				// but NO timer support!
3527 
3528 				// FIXME: we could probably support the POSIX timer_create
3529 				// signal-based option, but I'm in no rush to write it since
3530 				// I prefer the fd-based functions.
3531 				while (!done && (whileCondition is null || whileCondition() == true) && notExited) {
3532 
3533 					import core.sys.posix.poll;
3534 
3535 					pollfd[] pfds;
3536 					pollfd[32] pfdsBuffer;
3537 					auto len = PosixFdReader.mapping.length + 2;
3538 					// FIXME: i should just reuse the buffer
3539 					if(len < pfdsBuffer.length)
3540 						pfds = pfdsBuffer[0 .. len];
3541 					else
3542 						pfds = new pollfd[](len);
3543 
3544 					pfds[0].fd = display.fd;
3545 					pfds[0].events = POLLIN;
3546 					pfds[0].revents = 0;
3547 
3548 					int slot = 1;
3549 
3550 					if(customEventFDRead != -1) {
3551 						pfds[slot].fd = customEventFDRead;
3552 						pfds[slot].events = POLLIN;
3553 						pfds[slot].revents = 0;
3554 
3555 						slot++;
3556 					}
3557 
3558 					foreach(fd, obj; PosixFdReader.mapping) {
3559 						if(!obj.enabled) continue;
3560 						pfds[slot].fd = fd;
3561 						pfds[slot].events = POLLIN;
3562 						pfds[slot].revents = 0;
3563 
3564 						slot++;
3565 					}
3566 
3567 					auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1);
3568 					if(ret == -1) throw new Exception("poll");
3569 
3570 					if(ret == 0) {
3571 						// FIXME it may not necessarily time out if events keep coming
3572 						if(handlePulse !is null)
3573 							handlePulse();
3574 					} else {
3575 						foreach(s; 0 .. slot) {
3576 							if(pfds[s].revents == 0) continue;
3577 
3578 							if(pfds[s].fd == display.fd) {
3579 								while(!done && XPending(display)) {
3580 									this.mtLock();
3581 									scope(exit) this.mtUnlock();
3582 									done = doXNextEvent(this.display);
3583 								}
3584 							} else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) {
3585 
3586 								import core.sys.posix.unistd : read;
3587 								ulong n;
3588 								read(customEventFDRead, &n, n.sizeof);
3589 								SimpleWindow.processAllCustomEvents();
3590 							} else {
3591 								auto obj = PosixFdReader.mapping[pfds[s].fd];
3592 								if(pfds[s].revents & POLLNVAL) {
3593 									obj.dispose();
3594 								} else {
3595 									obj.ready(pfds[s].revents);
3596 								}
3597 							}
3598 
3599 							ret--;
3600 							if(ret == 0) break;
3601 						}
3602 					}
3603 				}
3604 			}
3605 		}
3606 		
3607 		version(Windows) {
3608 			int ret = -1;
3609 			MSG message;
3610 			while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) {
3611 				eventLoopRound++;
3612 				auto wto = SimpleWindow.eventAllQueueTimeoutMSecs();
3613 				auto waitResult = MsgWaitForMultipleObjectsEx(
3614 					cast(int) handles.length, handles.ptr,
3615 					(wto == 0 ? INFINITE : wto), /* timeout */
3616 					0x04FF, /* QS_ALLINPUT */
3617 					0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */);
3618 
3619 				SimpleWindow.processAllCustomEvents(); // anyway
3620 				enum WAIT_OBJECT_0 = 0;
3621 				if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) {
3622 					auto h = handles[waitResult - WAIT_OBJECT_0];
3623 					if(auto e = h in WindowsHandleReader.mapping) {
3624 						(*e).ready();
3625 					}
3626 				} else if(waitResult == handles.length + WAIT_OBJECT_0) {
3627 					// message ready
3628 					while(PeekMessage(&message, null, 0, 0, PM_NOREMOVE)) { // need to peek since sometimes MsgWaitForMultipleObjectsEx returns even though GetMessage can block. tbh i don't fully understand it but the docs say it is foreground activation
3629 						ret = GetMessage(&message, null, 0, 0);
3630 						if(ret == -1)
3631 							throw new Exception("GetMessage failed");
3632 						TranslateMessage(&message);
3633 						DispatchMessage(&message);
3634 
3635 						if(ret == 0) // WM_QUIT
3636 							break;
3637 					}
3638 				} else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) {
3639 					SleepEx(0, true); // I call this to give it a chance to do stuff like async io
3640 				} else if(waitResult == 258L /* WAIT_TIMEOUT */) {
3641 					// timeout, should never happen since we aren't using it
3642 				} else if(waitResult == 0xFFFFFFFF) {
3643 						// failed
3644 						throw new Exception("MsgWaitForMultipleObjectsEx failed");
3645 				} else {
3646 					// idk....
3647 				}
3648 			}
3649 
3650 			// return message.wParam;
3651 			return 0;
3652 		} else {
3653 			return 0;
3654 		}
3655 	}
3656 
3657 	int run(bool delegate() whileCondition = null) {
3658 		if(disposed)
3659 			initialize(this.pulseTimeout);
3660 
3661 		version(X11) {
3662 			try {
3663 				return loopHelper(whileCondition);
3664 			} catch(XDisconnectException e) {
3665 				if(e.userRequested) {
3666 					foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping)
3667 						item.discardConnectionState();
3668 					XCloseDisplay(XDisplayConnection.display);
3669 				}
3670 
3671 				XDisplayConnection.display = null;
3672 
3673 				this.dispose();
3674 
3675 				throw e;
3676 			}
3677 		} else {
3678 			return loopHelper(whileCondition);
3679 		}
3680 	}
3681 }
3682 
3683 
3684 /++
3685 	Provides an icon on the system notification area (also known as the system tray).
3686 
3687 
3688 	If a notification area is not available with the NotificationIcon object is created,
3689 	it will silently succeed and simply attempt to create one when an area becomes available.
3690 
3691 
3692 	NotificationAreaIcon on Windows assumes you are on Windows Vista or later.
3693 	If this is wrong, pass -version=WindowsXP to dmd when compiling and it will
3694 	use the older version.
3695 +/
3696 version(OSXCocoa) {} else // NotYetImplementedException
3697 class NotificationAreaIcon : CapableOfHandlingNativeEvent {
3698 
3699 	version(X11) {
3700 		void recreateAfterDisconnect() {
3701 			stateDiscarded = false;
3702 			clippixmap = None;
3703 			throw new Exception("NOT IMPLEMENTED");
3704 		}
3705 
3706 		bool stateDiscarded;
3707 		void discardConnectionState() {
3708 			stateDiscarded = true;
3709 		}
3710 	}
3711 
3712 
3713 	version(X11) {
3714 		Image img;
3715 
3716 		NativeEventHandler getNativeEventHandler() {
3717 			return delegate int(XEvent e) {
3718 				switch(e.type) {
3719 					case EventType.Expose:
3720 					//case EventType.VisibilityNotify:
3721 						redraw();
3722 					break;
3723 					case EventType.ClientMessage:
3724 						version(sddddd) {
3725 						import std.stdio;
3726 						writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get));
3727 						writeln("\t", e.xclient.format);
3728 						writeln("\t", e.xclient.data.l);
3729 						}
3730 					break;
3731 					case EventType.ButtonPress:
3732 						auto event = e.xbutton;
3733 						if (onClick !is null || onClickEx !is null) {
3734 							MouseButton mb = cast(MouseButton)0;
3735 							switch (event.button) {
3736 								case 1: mb = MouseButton.left; break; // left
3737 								case 2: mb = MouseButton.middle; break; // middle
3738 								case 3: mb = MouseButton.right; break; // right
3739 								case 4: mb = MouseButton.wheelUp; break; // scroll up
3740 								case 5: mb = MouseButton.wheelDown; break; // scroll down
3741 								case 6: break; // idk
3742 								case 7: break; // idk
3743 								case 8: mb = MouseButton.backButton; break;
3744 								case 9: mb = MouseButton.forwardButton; break;
3745 								default:
3746 							}
3747 							if (mb) {
3748 								try { onClick()(mb); } catch (Exception) {}
3749 								if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {}
3750 							}
3751 						}
3752 					break;
3753 					case EventType.EnterNotify:
3754 						if (onEnter !is null) {
3755 							onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state);
3756 						}
3757 						break;
3758 					case EventType.LeaveNotify:
3759 						if (onLeave !is null) try { onLeave(); } catch (Exception) {}
3760 						break;
3761 					case EventType.DestroyNotify:
3762 						active = false;
3763 						CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle);
3764 					break;
3765 					case EventType.ConfigureNotify:
3766 						auto event = e.xconfigure;
3767 						this.width = event.width;
3768 						this.height = event.height;
3769 						//import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y);
3770 						redraw();
3771 					break;
3772 					default: return 1;
3773 				}
3774 				return 1;
3775 			};
3776 		}
3777 
3778 		/* private */ void hideBalloon() {
3779 			balloon.close();
3780 			version(with_timer)
3781 				timer.destroy();
3782 			balloon = null;
3783 			version(with_timer)
3784 				timer = null;
3785 		}
3786 
3787 		void redraw() {
3788 			if (!active) return;
3789 
3790 			auto display = XDisplayConnection.get;
3791 			auto gc = DefaultGC(display, DefaultScreen(display));
3792 			XClearWindow(display, nativeHandle);
3793 
3794 			XSetClipMask(display, gc, clippixmap);
3795 
3796 			XSetForeground(display, gc,
3797 				cast(uint) 0 << 16 |
3798 				cast(uint) 0 << 8 |
3799 				cast(uint) 0);
3800 			XFillRectangle(display, nativeHandle, gc, 0, 0, width, height);
3801 
3802 			if (img is null) {
3803 				XSetForeground(display, gc,
3804 					cast(uint) 0 << 16 |
3805 					cast(uint) 127 << 8 |
3806 					cast(uint) 0);
3807 				XFillArc(display, nativeHandle,
3808 					gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64);
3809 			} else {
3810 				int dx = 0;
3811 				int dy = 0;
3812 				if(width > img.width)
3813 					dx = (width - img.width) / 2;
3814 				if(height > img.height)
3815 					dy = (height - img.height) / 2;
3816 				XSetClipOrigin(display, gc, dx, dy);
3817 
3818 				if (img.usingXshm)
3819 					XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false);
3820 				else
3821 					XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height);
3822 			}
3823 			XSetClipMask(display, gc, None);
3824 			flushGui();
3825 		}
3826 
3827 		static Window getTrayOwner() {
3828 			auto display = XDisplayConnection.get;
3829 			auto i = cast(int) DefaultScreen(display);
3830 			if(i < 10 && i >= 0) {
3831 				static Atom atom;
3832 				if(atom == None)
3833 					atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false);
3834 				return XGetSelectionOwner(display, atom);
3835 			}
3836 			return None;
3837 		}
3838 
3839 		static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) {
3840 			auto to = getTrayOwner();
3841 			auto display = XDisplayConnection.get;
3842 			XEvent ev;
3843 			ev.xclient.type = EventType.ClientMessage;
3844 			ev.xclient.window = to;
3845 			ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display);
3846 			ev.xclient.format = 32;
3847 			ev.xclient.data.l[0] = CurrentTime;
3848 			ev.xclient.data.l[1] = message;
3849 			ev.xclient.data.l[2] = d1;
3850 			ev.xclient.data.l[3] = d2;
3851 			ev.xclient.data.l[4] = d3;
3852 
3853 			XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev);
3854 		}
3855 
3856 		private static NotificationAreaIcon[] activeIcons;
3857 
3858 		// FIXME: possible leak with this stuff, should be able to clear it and stuff.
3859 		private void newManager() {
3860 			close();
3861 			createXWin();
3862 
3863 			if(this.clippixmap)
3864 				XFreePixmap(XDisplayConnection.get, clippixmap);
3865 			if(this.originalMemoryImage)
3866 				this.icon = this.originalMemoryImage;
3867 			else if(this.img)
3868 				this.icon = this.img;
3869 		}
3870 
3871 		private void createXWin () {
3872 			// create window
3873 			auto display = XDisplayConnection.get;
3874 
3875 			// to check for MANAGER on root window to catch new/changed tray owners
3876 			XDisplayConnection.addRootInput(EventMask.StructureNotifyMask);
3877 			// so if a thing does appear, we can handle it
3878 			foreach(ai; activeIcons)
3879 				if(ai is this)
3880 					goto alreadythere;
3881 			activeIcons ~= this;
3882 			alreadythere:
3883 
3884 			// and check for an existing tray
3885 			auto trayOwner = getTrayOwner();
3886 			if(trayOwner == None)
3887 				return;
3888 				//throw new Exception("No notification area found");
3889 
3890 			Visual* v = cast(Visual*) CopyFromParent;
3891 			/+
3892 			auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display));
3893 			if(visualProp !is null) {
3894 				c_ulong[] info = cast(c_ulong[]) visualProp;
3895 				if(info.length == 1) {
3896 					auto vid = info[0];
3897 					int returned;
3898 					XVisualInfo t;
3899 					t.visualid = vid;
3900 					auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned);
3901 					if(got !is null) {
3902 						if(returned == 1) {
3903 							v = got.visual;
3904 							import std.stdio;
3905 							writeln("using special visual ", *got);
3906 						}
3907 						XFree(got);
3908 					}
3909 				}
3910 			}
3911 			+/
3912 
3913 			auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null);
3914 			assert(nativeWindow);
3915 
3916 			XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */);
3917 
3918 			nativeHandle = nativeWindow;
3919 
3920 			///+
3921 			arch_ulong[2] info;
3922 			info[0] = 0;
3923 			info[1] = 1;
3924 
3925 			string title = this.name is null ? "simpledisplay.d program" : this.name;
3926 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
3927 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
3928 			XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
3929 
3930 			XChangeProperty(
3931 				display,
3932 				nativeWindow,
3933 				GetAtom!("_XEMBED_INFO", true)(display),
3934 				GetAtom!("_XEMBED_INFO", true)(display),
3935 				32 /* bits */,
3936 				0 /*PropModeReplace*/,
3937 				info.ptr,
3938 				2);
3939 
3940 			import core.sys.posix.unistd;
3941 			arch_ulong pid = getpid();
3942 
3943 			XChangeProperty(
3944 				display,
3945 				nativeWindow,
3946 				GetAtom!("_NET_WM_PID", true)(display),
3947 				XA_CARDINAL,
3948 				32 /* bits */,
3949 				0 /*PropModeReplace*/,
3950 				&pid,
3951 				1);
3952 
3953 			updateNetWmIcon();
3954 
3955 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
3956 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
3957 				XClassHint klass;
3958 				XWMHints wh;
3959 				XSizeHints size;
3960 				klass.res_name = sdpyWindowClassStr;
3961 				klass.res_class = sdpyWindowClassStr;
3962 				XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass);
3963 			}
3964 
3965 				// believe it or not, THIS is what xfce needed for the 9999 issue
3966 				XSizeHints sh;
3967 					c_long spr;
3968 					XGetWMNormalHints(display, nativeWindow, &sh, &spr);
3969 					sh.flags |= PMaxSize | PMinSize;
3970 				// FIXME maybe nicer resizing
3971 				sh.min_width = 16;
3972 				sh.min_height = 16;
3973 				sh.max_width = 16;
3974 				sh.max_height = 16;
3975 				XSetWMNormalHints(display, nativeWindow, &sh);
3976 
3977 
3978 			//+/
3979 
3980 
3981 			XSelectInput(display, nativeWindow,
3982 				EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask |
3983 				EventMask.EnterWindowMask | EventMask.LeaveWindowMask);
3984 
3985 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0);
3986 			CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
3987 			active = true;
3988 		}
3989 
3990 		void updateNetWmIcon() {
3991 			if(img is null) return;
3992 			auto display = XDisplayConnection.get;
3993 			// FIXME: ensure this is correct
3994 			arch_ulong[] buffer;
3995 			auto imgMi = img.toTrueColorImage;
3996 			buffer ~= imgMi.width;
3997 			buffer ~= imgMi.height;
3998 			foreach(c; imgMi.imageData.colors) {
3999 				arch_ulong b;
4000 				b |= c.a << 24;
4001 				b |= c.r << 16;
4002 				b |= c.g << 8;
4003 				b |= c.b;
4004 				buffer ~= b;
4005 			}
4006 
4007 			XChangeProperty(
4008 				display,
4009 				nativeHandle,
4010 				GetAtom!"_NET_WM_ICON"(display),
4011 				GetAtom!"CARDINAL"(display),
4012 				32 /* bits */,
4013 				0 /*PropModeReplace*/,
4014 				buffer.ptr,
4015 				cast(int) buffer.length);
4016 		}
4017 
4018 
4019 
4020 		private SimpleWindow balloon;
4021 		version(with_timer)
4022 		private Timer timer;
4023 
4024 		private Window nativeHandle;
4025 		private Pixmap clippixmap = None;
4026 		private int width = 16;
4027 		private int height = 16;
4028 		private bool active = false;
4029 
4030 		void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only.
4031 		void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only.
4032 		void delegate () onLeave; /// X11 only.
4033 
4034 		@property bool closed () const pure nothrow @safe @nogc { return !active; } ///
4035 
4036 		/// X11 only. Get global window coordinates and size. This can be used to show various notifications.
4037 		void getWindowRect (out int x, out int y, out int width, out int height) {
4038 			if (!active) { width = 1; height = 1; return; } // 1: just in case
4039 			Window dummyw;
4040 			auto dpy = XDisplayConnection.get;
4041 			//XWindowAttributes xwa;
4042 			//XGetWindowAttributes(dpy, nativeHandle, &xwa);
4043 			//XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw);
4044 			XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
4045 			width = this.width;
4046 			height = this.height;
4047 		}
4048 	}
4049 
4050 	/+
4051 		What I actually want from this:
4052 
4053 		* set / change: icon, tooltip
4054 		* handle: mouse click, right click
4055 		* show: notification bubble.
4056 	+/
4057 
4058 	version(Windows) {
4059 		WindowsIcon win32Icon;
4060 		HWND hwnd;
4061 
4062 		NOTIFYICONDATAW data;
4063 
4064 		NativeEventHandler getNativeEventHandler() {
4065 			return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
4066 				if(msg == WM_USER) {
4067 					auto event = LOWORD(lParam);
4068 					auto iconId = HIWORD(lParam);
4069 					//auto x = GET_X_LPARAM(wParam);
4070 					//auto y = GET_Y_LPARAM(wParam);
4071 					switch(event) {
4072 						case WM_LBUTTONDOWN:
4073 							onClick()(MouseButton.left);
4074 						break;
4075 						case WM_RBUTTONDOWN:
4076 							onClick()(MouseButton.right);
4077 						break;
4078 						case WM_MBUTTONDOWN:
4079 							onClick()(MouseButton.middle);
4080 						break;
4081 						case WM_MOUSEMOVE:
4082 							// sent, we could use it.
4083 						break;
4084 						case WM_MOUSEWHEEL:
4085 							// NOT SENT
4086 						break;
4087 						//case NIN_KEYSELECT:
4088 						//case NIN_SELECT:
4089 						//break;
4090 						default: {}
4091 					}
4092 				}
4093 				return 0;
4094 			};
4095 		}
4096 
4097 		enum NIF_SHOWTIP = 0x00000080;
4098 
4099 		private static struct NOTIFYICONDATAW {
4100 			DWORD cbSize;
4101 			HWND  hWnd;
4102 			UINT  uID;
4103 			UINT  uFlags;
4104 			UINT  uCallbackMessage;
4105 			HICON hIcon;
4106 			WCHAR[128] szTip;
4107 			DWORD dwState;
4108 			DWORD dwStateMask;
4109 			WCHAR[256] szInfo;
4110 			union {
4111 				UINT uTimeout;
4112 				UINT uVersion;
4113 			}
4114 			WCHAR[64] szInfoTitle;
4115 			DWORD dwInfoFlags;
4116 			GUID  guidItem;
4117 			HICON hBalloonIcon;
4118 		}
4119 
4120 	}
4121 
4122 	/++
4123 		Note that on Windows, only left, right, and middle buttons are sent.
4124 		Mouse wheel buttons are NOT set, so don't rely on those events if your
4125 		program is meant to be used on Windows too.
4126 	+/
4127 	this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) {
4128 		// The canonical constructor for Windows needs the MemoryImage, so it is here,
4129 		// but on X, we need an Image, so its canonical ctor is there. They should
4130 		// forward to each other though.
4131 		version(X11) {
4132 			this.name = name;
4133 			this.onClick = onClick;
4134 			createXWin();
4135 			this.icon = icon;
4136 		} else version(Windows) {
4137 			this.onClick = onClick;
4138 			this.win32Icon = new WindowsIcon(icon);
4139 
4140 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
4141 
4142 			static bool registered = false;
4143 			if(!registered) {
4144 				WNDCLASSEX wc;
4145 				wc.cbSize = wc.sizeof;
4146 				wc.hInstance = hInstance;
4147 				wc.lpfnWndProc = &WndProc;
4148 				wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr;
4149 				if(!RegisterClassExW(&wc))
4150 					throw new WindowsApiException("RegisterClass");
4151 				registered = true;
4152 			}
4153 
4154 			this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null);
4155 			if(hwnd is null)
4156 				throw new Exception("CreateWindow");
4157 
4158 			data.cbSize = data.sizeof;
4159 			data.hWnd = hwnd;
4160 			data.uID = cast(uint) cast(void*) this;
4161 			data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */;
4162 				// NIF_INFO means show balloon
4163 			data.uCallbackMessage = WM_USER;
4164 			data.hIcon = this.win32Icon.hIcon;
4165 			data.szTip = ""; // FIXME
4166 			data.dwState = 0; // NIS_HIDDEN; // windows vista
4167 			data.dwStateMask = NIS_HIDDEN; // windows vista
4168 
4169 			data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up
4170 
4171 
4172 			Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data);
4173 
4174 			CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this;
4175 		} else version(OSXCocoa) {
4176 			throw new NotYetImplementedException();
4177 		} else static assert(0);
4178 	}
4179 
4180 	/// ditto
4181 	this(string name, Image icon, void delegate(MouseButton button) onClick) {
4182 		version(X11) {
4183 			this.onClick = onClick;
4184 			this.name = name;
4185 			createXWin();
4186 			this.icon = icon;
4187 		} else version(Windows) {
4188 			this(name, icon is null ? null : icon.toTrueColorImage(), onClick);
4189 		} else version(OSXCocoa) {
4190 			throw new NotYetImplementedException();
4191 		} else static assert(0);
4192 	}
4193 
4194 	version(X11) {
4195 		/++
4196 			X-specific extension (for now at least)
4197 		+/
4198 		this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
4199 			this.onClickEx = onClickEx;
4200 			createXWin();
4201 			if (icon !is null) this.icon = icon;
4202 		}
4203 
4204 		/// ditto
4205 		this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) {
4206 			this.onClickEx = onClickEx;
4207 			createXWin();
4208 			this.icon = icon;
4209 		}
4210 	}
4211 
4212 	private void delegate (MouseButton button) onClick_;
4213 
4214 	///
4215 	@property final void delegate(MouseButton) onClick() {
4216 		if(onClick_ is null)
4217 			onClick_ = delegate void(MouseButton) {};
4218 		return onClick_;
4219 	}
4220 
4221 	/// ditto
4222 	@property final void onClick(void delegate(MouseButton) handler) {
4223 		// I made this a property setter so we can wrap smaller arg
4224 		// delegates and just forward all to onClickEx or something.
4225 		onClick_ = handler;
4226 	}
4227 
4228 
4229 	string name_;
4230 	@property void name(string n) {
4231 		name_ = n;
4232 	}
4233 
4234 	@property string name() {
4235 		return name_;
4236 	}
4237 
4238 	private MemoryImage originalMemoryImage;
4239 
4240 	///
4241 	@property void icon(MemoryImage i) {
4242 		version(X11) {
4243 			this.originalMemoryImage = i;
4244 			if (!active) return;
4245 			if (i !is null) {
4246 				this.img = Image.fromMemoryImage(i);
4247 				this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle);
4248 				//import std.stdio; writeln("using pixmap ", clippixmap);
4249 				updateNetWmIcon();
4250 				redraw();
4251 			} else {
4252 				if (this.img !is null) {
4253 					this.img = null;
4254 					redraw();
4255 				}
4256 			}
4257 		} else version(Windows) {
4258 			this.win32Icon = new WindowsIcon(i);
4259 
4260 			data.uFlags = NIF_ICON;
4261 			data.hIcon = this.win32Icon.hIcon;
4262 
4263 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4264 		} else version(OSXCocoa) {
4265 			throw new NotYetImplementedException();
4266 		} else static assert(0);
4267 	}
4268 
4269 	/// ditto
4270 	@property void icon (Image i) {
4271 		version(X11) {
4272 			if (!active) return;
4273 			if (i !is img) {
4274 				originalMemoryImage = null;
4275 				img = i;
4276 				redraw();
4277 			}
4278 		} else version(Windows) {
4279 			this.icon(i is null ? null : i.toTrueColorImage());
4280 		} else version(OSXCocoa) {
4281 			throw new NotYetImplementedException();
4282 		} else static assert(0);
4283 	}
4284 
4285 	/++
4286 		Shows a balloon notification. You can only show one balloon at a time, if you call
4287 		it twice while one is already up, the first balloon will be replaced.
4288 		
4289 		
4290 		The user is free to block notifications and they will automatically disappear after
4291 		a timeout period.
4292 
4293 		Params:
4294 			title = Title of the notification. Must be 40 chars or less or the OS may truncate it.
4295 			message = The message to pop up. Must be 220 chars or less or the OS may truncate it.
4296 			icon = the icon to display with the notification. If null, it uses your existing icon.
4297 			onclick = delegate called if the user clicks the balloon. (not yet implemented)
4298 			timeout = your suggested timeout period. The operating system is free to ignore your suggestion.
4299 	+/
4300 	void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) {
4301 		bool useCustom = true;
4302 		version(libnotify) {
4303 			if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop
4304 			try {
4305 				if(!active) return;
4306 
4307 				if(libnotify is null) {
4308 					libnotify = new C_DynamicLibrary("libnotify.so");
4309 					libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr);
4310 				}
4311 
4312 				auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */);
4313 
4314 				libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout);
4315 
4316 				if(onclick) {
4317 					libnotify_action_delegates[libnotify_action_delegates_count] = onclick;
4318 					libnotify.call!("notify_notification_add_action", void, void*, const char*, const char*, typeof(&libnotify_action_callback_sdpy), void*, void*)()(n, "DEFAULT".ptr, "Go".ptr, &libnotify_action_callback_sdpy, cast(void*) libnotify_action_delegates_count, null);
4319 					libnotify_action_delegates_count++;
4320 				}
4321 
4322 				// FIXME icon
4323 
4324 				// set hint image-data
4325 				// set default action for onclick
4326 
4327 				void* error;
4328 				libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error);
4329 
4330 				useCustom = false;
4331 			} catch(Exception e) {
4332 
4333 			}
4334 		}
4335 		
4336 		version(X11) {
4337 		if(useCustom) {
4338 			if(!active) return;
4339 			if(balloon) {
4340 				hideBalloon();
4341 			}
4342 			// I know there are two specs for this, but one is never
4343 			// implemented by any window manager I have ever seen, and
4344 			// the other is a bloated mess and too complicated for simpledisplay...
4345 			// so doing my own little window instead.
4346 			balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/);
4347 
4348 			int x, y, width, height;
4349 			getWindowRect(x, y, width, height);
4350 
4351 			int bx = x - balloon.width;
4352 			int by = y - balloon.height;
4353 			if(bx < 0)
4354 				bx = x + width + balloon.width;
4355 			if(by < 0)
4356 				by = y + height;
4357 
4358 			// just in case, make sure it is actually on scren
4359 			if(bx < 0)
4360 				bx = 0;
4361 			if(by < 0)
4362 				by = 0;
4363 
4364 			balloon.move(bx, by);
4365 			auto painter = balloon.draw();
4366 			painter.fillColor = Color(220, 220, 220);
4367 			painter.outlineColor = Color.black;
4368 			painter.drawRectangle(Point(0, 0), balloon.width, balloon.height);
4369 			auto iconWidth = icon is null ? 0 : icon.width;
4370 			if(icon)
4371 				painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon));
4372 			iconWidth += 6; // margin around the icon
4373 
4374 			// draw a close button
4375 			painter.outlineColor = Color(44, 44, 44);
4376 			painter.fillColor = Color(255, 255, 255);
4377 			painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13);
4378 			painter.pen = Pen(Color.black, 3);
4379 			painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14));
4380 			painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13));
4381 			painter.pen = Pen(Color.black, 1);
4382 			painter.fillColor = Color(220, 220, 220);
4383 
4384 			// Draw the title and message
4385 			painter.drawText(Point(4 + iconWidth, 4), title);
4386 			painter.drawLine(
4387 				Point(4 + iconWidth, 4 + painter.fontHeight + 1),
4388 				Point(balloon.width - 4, 4 + painter.fontHeight + 1),
4389 			);
4390 			painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message);
4391 
4392 			balloon.setEventHandlers(
4393 				(MouseEvent ev) {
4394 					if(ev.type == MouseEventType.buttonPressed) {
4395 						if(ev.x > balloon.width - 16 && ev.y < 16)
4396 							hideBalloon();
4397 						else if(onclick)
4398 							onclick();
4399 					}
4400 				}
4401 			);
4402 			balloon.show();
4403 
4404 			version(with_timer)
4405 			timer = new Timer(timeout, &hideBalloon);
4406 			else {} // FIXME
4407 		}
4408 		} else version(Windows) {
4409 			enum NIF_INFO = 0x00000010;
4410 
4411 			data.uFlags = NIF_INFO;
4412 
4413 			// FIXME: go back to the last valid unicode code point
4414 			if(title.length > 40)
4415 				title = title[0 .. 40];
4416 			if(message.length > 220)
4417 				message = message[0 .. 220];
4418 
4419 			enum NIIF_RESPECT_QUIET_TIME = 0x00000080;
4420 			enum NIIF_LARGE_ICON  = 0x00000020;
4421 			enum NIIF_NOSOUND = 0x00000010;
4422 			enum NIIF_USER = 0x00000004;
4423 			enum NIIF_ERROR = 0x00000003;
4424 			enum NIIF_WARNING = 0x00000002;
4425 			enum NIIF_INFO = 0x00000001;
4426 			enum NIIF_NONE = 0;
4427 
4428 			WCharzBuffer t = WCharzBuffer(title);
4429 			WCharzBuffer m = WCharzBuffer(message);
4430 
4431 			t.copyInto(data.szInfoTitle);
4432 			m.copyInto(data.szInfo);
4433 			data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME;
4434 
4435 			if(icon !is null) {
4436 				auto i = new WindowsIcon(icon);
4437 				data.hBalloonIcon = i.hIcon;
4438 				data.dwInfoFlags |= NIIF_USER;
4439 			}
4440 
4441 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4442 		} else version(OSXCocoa) {
4443 			throw new NotYetImplementedException();
4444 		} else static assert(0);
4445 	}
4446 
4447 	///
4448 	//version(Windows)
4449 	void show() {
4450 		version(X11) {
4451 			if(!hidden)
4452 				return;
4453 			sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0);
4454 			hidden = false;
4455 		} else version(Windows) {
4456 			data.uFlags = NIF_STATE;
4457 			data.dwState = 0; // NIS_HIDDEN; // windows vista
4458 			data.dwStateMask = NIS_HIDDEN; // windows vista
4459 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4460 		} else version(OSXCocoa) {
4461 			throw new NotYetImplementedException();
4462 		} else static assert(0);
4463 	}
4464 
4465 	version(X11)
4466 		bool hidden = false;
4467 
4468 	///
4469 	//version(Windows)
4470 	void hide() {
4471 		version(X11) {
4472 			if(hidden)
4473 				return;
4474 			hidden = true;
4475 			XUnmapWindow(XDisplayConnection.get, nativeHandle);
4476 		} else version(Windows) {
4477 			data.uFlags = NIF_STATE;
4478 			data.dwState = NIS_HIDDEN; // windows vista
4479 			data.dwStateMask = NIS_HIDDEN; // windows vista
4480 			Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data);
4481 		} else version(OSXCocoa) {
4482 			throw new NotYetImplementedException();
4483 		} else static assert(0);
4484 	}
4485 
4486 	///
4487 	void close () {
4488 		version(X11) {
4489 			if (active) {
4490 				active = false; // event handler will set this too, but meh
4491 				XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite
4492 				XDestroyWindow(XDisplayConnection.get, nativeHandle);
4493 				flushGui();
4494 			}
4495 		} else version(Windows) {
4496 			Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data);
4497 		} else version(OSXCocoa) {
4498 			throw new NotYetImplementedException();
4499 		} else static assert(0);
4500 	}
4501 
4502 	~this() {
4503 		version(X11)
4504 			if(clippixmap != None)
4505 				XFreePixmap(XDisplayConnection.get, clippixmap);
4506 		close();
4507 	}
4508 }
4509 
4510 version(X11)
4511 /// call XFreePixmap on the return value
4512 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) {
4513 	char[] data = new char[](i.width * i.height / 8 + 2);
4514 	data[] = 0;
4515 
4516 	int bitOffset = 0;
4517 	foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases
4518 		ubyte v = c.a > 128 ? 1 : 0;
4519 		data[bitOffset / 8] |= v << (bitOffset%8);
4520 		bitOffset++;
4521 	}
4522 	auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height);
4523 	return handle;
4524 }
4525 
4526 
4527 // basic functions to make timers
4528 /**
4529 	A timer that will trigger your function on a given interval.
4530 
4531 
4532 	You create a timer with an interval and a callback. It will continue
4533 	to fire on the interval until it is destroyed.
4534 
4535 	There are currently no one-off timers (instead, just create one and
4536 	destroy it when it is triggered) nor are there pause/resume functions -
4537 	the timer must again be destroyed and recreated if you want to pause it.
4538 
4539 	auto timer = new Timer(50, { it happened!; });
4540 	timer.destroy();
4541 
4542 	Timers can only be expected to fire when the event loop is running and only
4543 	once per iteration through the event loop.
4544 
4545 	History:
4546 		Prior to December 9, 2020, a timer pulse set too high with a handler too
4547 		slow could lock up the event loop. It now guarantees other things will
4548 		get a chance to run between timer calls, even if that means not keeping up
4549 		with the requested interval.
4550 */
4551 version(with_timer) {
4552 class Timer {
4553 // FIXME: needs pause and unpause
4554 	// FIXME: I might add overloads for ones that take a count of
4555 	// how many elapsed since last time (on Windows, it will divide
4556 	// the ticks thing given, on Linux it is just available) and
4557 	// maybe one that takes an instance of the Timer itself too
4558 	/// Create a timer with a callback when it triggers.
4559 	this(int intervalInMilliseconds, void delegate() onPulse) {
4560 		assert(onPulse !is null);
4561 
4562 		this.intervalInMilliseconds = intervalInMilliseconds;
4563 		this.onPulse = onPulse;
4564 
4565 		version(Windows) {
4566 			/*
4567 			handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
4568 			if(handle == 0)
4569 				throw new Exception("SetTimer fail");
4570 			*/
4571 
4572 			// thanks to Archival 998 for the WaitableTimer blocks
4573 			handle = CreateWaitableTimer(null, false, null);
4574 			long initialTime = -intervalInMilliseconds;
4575 			if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
4576 				throw new Exception("SetWaitableTimer Failed");
4577 
4578 			mapping[handle] = this;
4579 
4580 		} else version(linux) {
4581 			static import ep = core.sys.linux.epoll;
4582 
4583 			import core.sys.linux.timerfd;
4584 
4585 			fd = timerfd_create(CLOCK_MONOTONIC, 0);
4586 			if(fd == -1)
4587 				throw new Exception("timer create failed");
4588 
4589 			mapping[fd] = this;
4590 
4591 			itimerspec value;
4592 			value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000);
4593 			value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
4594 
4595 			value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000);
4596 			value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000;
4597 
4598 			if(timerfd_settime(fd, 0, &value, null) == -1)
4599 				throw new Exception("couldn't make pulse timer");
4600 
4601 			version(with_eventloop) {
4602 				import arsd.eventloop;
4603 				addFileEventListeners(fd, &trigger, null, null);
4604 			} else {
4605 				prepareEventLoop();
4606 
4607 				ep.epoll_event ev = void;
4608 				ev.events = ep.EPOLLIN;
4609 				ev.data.fd = fd;
4610 				ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
4611 			}
4612 		} else featureNotImplemented();
4613 	}
4614 
4615 	private int intervalInMilliseconds;
4616 
4617 	/// Stop and destroy the timer object.
4618 	void destroy() {
4619 		version(Windows) {
4620 			if(handle) {
4621 				// KillTimer(null, handle);
4622 				CancelWaitableTimer(cast(void*)handle);
4623 				mapping.remove(handle);
4624 				CloseHandle(handle);
4625 				handle = null;
4626 			}
4627 		} else version(linux) {
4628 			if(fd != -1) {
4629 				import unix = core.sys.posix.unistd;
4630 				static import ep = core.sys.linux.epoll;
4631 
4632 				version(with_eventloop) {
4633 					import arsd.eventloop;
4634 					removeFileEventListeners(fd);
4635 				} else {
4636 					ep.epoll_event ev = void;
4637 					ev.events = ep.EPOLLIN;
4638 					ev.data.fd = fd;
4639 
4640 					ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
4641 				}
4642 				unix.close(fd);
4643 				mapping.remove(fd);
4644 				fd = -1;
4645 			}
4646 		} else featureNotImplemented();
4647 	}
4648 
4649 	~this() {
4650 		destroy();
4651 	}
4652 
4653 
4654 	void changeTime(int intervalInMilliseconds)
4655 	{
4656 		this.intervalInMilliseconds = intervalInMilliseconds;
4657 		version(Windows)
4658 		{
4659 			if(handle)
4660 			{
4661 				//handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback);
4662 				long initialTime = -intervalInMilliseconds;
4663 				if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false))
4664 					throw new Exception("couldn't change pulse timer");
4665 			}
4666 		}
4667 	}
4668 
4669 
4670 	private:
4671 
4672 	void delegate() onPulse;
4673 
4674 	int lastEventLoopRoundTriggered;
4675 
4676 	void trigger() {
4677 		version(linux) {
4678 			import unix = core.sys.posix.unistd;
4679 			long val;
4680 			unix.read(fd, &val, val.sizeof); // gotta clear the pipe
4681 		} else version(Windows) {
4682 			if(this.lastEventLoopRoundTriggered == eventLoopRound)
4683 				return; // never try to actually run faster than the event loop
4684 			lastEventLoopRoundTriggered = eventLoopRound;
4685 		} else featureNotImplemented();
4686 
4687 		onPulse();
4688 	}
4689 
4690 	version(Windows)
4691 	void rearm() {
4692 
4693 	}
4694 
4695 	version(Windows)
4696 		extern(Windows)
4697 		//static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow {
4698 		static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow {
4699 			if(Timer* t = timer in mapping) {
4700 				try
4701 				(*t).trigger();
4702 				catch(Exception e) { sdpy_abort(e); assert(0); }
4703 			}
4704 		}
4705 
4706 	version(Windows) {
4707 		//UINT_PTR handle;
4708 		//static Timer[UINT_PTR] mapping;
4709 		HANDLE handle;
4710 		__gshared Timer[HANDLE] mapping;
4711 	} else version(linux) {
4712 		int fd = -1;
4713 		__gshared Timer[int] mapping;
4714 	} else static assert(0, "timer not supported");
4715 }
4716 }
4717 
4718 version(Windows)
4719 private int eventLoopRound;
4720 
4721 version(Windows)
4722 /// Lets you add HANDLEs to the event loop. Not meant to be used for async I/O per se, but for other handles (it can only handle a few handles at a time.) Only works on certain types of handles! see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-msgwaitformultipleobjectsex
4723 class WindowsHandleReader {
4724 	///
4725 	this(void delegate() onReady, HANDLE handle) {
4726 		this.onReady = onReady;
4727 		this.handle = handle;
4728 
4729 		mapping[handle] = this;
4730 
4731 		enable();
4732 	}
4733 
4734 	///
4735 	void enable() {
4736 		auto el = EventLoop.get().impl;
4737 		el.handles ~= handle;
4738 	}
4739 
4740 	///
4741 	void disable() {
4742 		auto el = EventLoop.get().impl;
4743 		for(int i = 0; i < el.handles.length; i++) {
4744 			if(el.handles[i] is handle) {
4745 				el.handles[i] = el.handles[$-1];
4746 				el.handles = el.handles[0 .. $-1];
4747 				return;
4748 			}
4749 		}
4750 	}
4751 
4752 	void dispose() {
4753 		disable();
4754 		if(handle)
4755 			mapping.remove(handle);
4756 		handle = null;
4757 	}
4758 
4759 	void ready() {
4760 		if(onReady)
4761 			onReady();
4762 	}
4763 
4764 	HANDLE handle;
4765 	void delegate() onReady;
4766 
4767 	__gshared WindowsHandleReader[HANDLE] mapping;
4768 }
4769 
4770 version(Posix)
4771 /// Lets you add files to the event loop for reading. Use at your own risk.
4772 class PosixFdReader {
4773 	///
4774 	this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) {
4775 		this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites);
4776 	}
4777 
4778 	///
4779 	this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
4780 		this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites);
4781 	}
4782 
4783 	///
4784 	this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) {
4785 		this.onReady = onReady;
4786 		this.fd = fd;
4787 		this.captureWrites = captureWrites;
4788 		this.captureReads = captureReads;
4789 
4790 		mapping[fd] = this;
4791 
4792 		version(with_eventloop) {
4793 			import arsd.eventloop;
4794 			addFileEventListeners(fd, &readyel);
4795 		} else {
4796 			enable();
4797 		}
4798 	}
4799 
4800 	bool captureReads;
4801 	bool captureWrites;
4802 
4803 	version(with_eventloop) {} else
4804 	///
4805 	void enable() {
4806 		prepareEventLoop();
4807 
4808 		enabled = true;
4809 
4810 		version(linux) {
4811 			static import ep = core.sys.linux.epoll;
4812 			ep.epoll_event ev = void;
4813 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
4814 			//import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites);
4815 			ev.data.fd = fd;
4816 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev);
4817 		} else {
4818 
4819 		}
4820 	}
4821 
4822 	version(with_eventloop) {} else
4823 	///
4824 	void disable() {
4825 		prepareEventLoop();
4826 
4827 		enabled = false;
4828 
4829 		version(linux) {
4830 			static import ep = core.sys.linux.epoll;
4831 			ep.epoll_event ev = void;
4832 			ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0);
4833 			//import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites);
4834 			ev.data.fd = fd;
4835 			ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev);
4836 		}
4837 	}
4838 
4839 	version(with_eventloop) {} else
4840 	///
4841 	void dispose() {
4842 		if(enabled)
4843 			disable();
4844 		if(fd != -1)
4845 			mapping.remove(fd);
4846 		fd = -1;
4847 	}
4848 
4849 	void delegate(int, bool, bool) onReady;
4850 
4851 	version(with_eventloop)
4852 	void readyel() {
4853 		onReady(fd, true, true);
4854 	}
4855 
4856 	void ready(uint flags) {
4857 		version(linux) {
4858 			static import ep = core.sys.linux.epoll;
4859 			onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false);
4860 		} else {
4861 			import core.sys.posix.poll;
4862 			onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false);
4863 		}
4864 	}
4865 
4866 	void hup(uint flags) {
4867 		if(onHup)
4868 			onHup();
4869 	}
4870 
4871 	void delegate() onHup;
4872 
4873 	int fd = -1;
4874 	private bool enabled;
4875 	__gshared PosixFdReader[int] mapping;
4876 }
4877 
4878 // basic functions to access the clipboard
4879 /+
4880 
4881 
4882 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx
4883 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx
4884 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
4885 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx
4886 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx
4887 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx
4888 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx
4889 
4890 +/
4891 
4892 /++
4893 	this does a delegate because it is actually an async call on X...
4894 	the receiver may never be called if the clipboard is empty or unavailable
4895 	gets plain text from the clipboard
4896 +/
4897 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) {
4898 	version(Windows) {
4899 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
4900 		if(OpenClipboard(hwndOwner) == 0)
4901 			throw new Exception("OpenClipboard");
4902 		scope(exit)
4903 			CloseClipboard();
4904 		// see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat
4905 		if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
4906 
4907 			if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
4908 				scope(exit)
4909 					GlobalUnlock(dataHandle);
4910 
4911 				// FIXME: CR/LF conversions
4912 				// FIXME: I might not have to copy it now that the receiver is in char[] instead of string
4913 				int len = 0;
4914 				auto d = data;
4915 				while(*d) {
4916 					d++;
4917 					len++;
4918 				}
4919 				string s;
4920 				s.reserve(len);
4921 				foreach(dchar ch; data[0 .. len]) {
4922 					s ~= ch;
4923 				}
4924 				receiver(s);
4925 			}
4926 		}
4927 	} else version(X11) {
4928 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
4929 	} else version(OSXCocoa) {
4930 		throw new NotYetImplementedException();
4931 	} else static assert(0);
4932 }
4933 
4934 // FIXME: a clipboard listener might be cool btw
4935 
4936 /++
4937 	this does a delegate because it is actually an async call on X...
4938 	the receiver may never be called if the clipboard is empty or unavailable
4939 	gets image from the clipboard
4940 
4941 	templated because it introduces an optional dependency on arsd.bmp
4942 +/
4943 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) {
4944 	version(Windows) {
4945 		HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null;
4946 		if(OpenClipboard(hwndOwner) == 0)
4947 			throw new Exception("OpenClipboard");
4948 		scope(exit)
4949 			CloseClipboard();
4950 		if(auto dataHandle = GetClipboardData(CF_DIBV5)) {
4951 			if(auto data = cast(ubyte*) GlobalLock(dataHandle)) {
4952 				scope(exit)
4953 					GlobalUnlock(dataHandle);
4954 
4955 				auto len = GlobalSize(dataHandle);
4956 
4957 				import arsd.bmp;
4958 				auto img = readBmp(data[0 .. len], false);
4959 				receiver(img);
4960 			}
4961 		}
4962 	} else version(X11) {
4963 		getX11Selection!"CLIPBOARD"(clipboardOwner, receiver);
4964 	} else version(OSXCocoa) {
4965 		throw new NotYetImplementedException();
4966 	} else static assert(0);
4967 }
4968 
4969 version(Windows)
4970 struct WCharzBuffer {
4971 	wchar[256] staticBuffer;
4972 	wchar[] buffer;
4973 
4974 	size_t length() {
4975 		return buffer.length;
4976 	}
4977 
4978 	wchar* ptr() {
4979 		return buffer.ptr;
4980 	}
4981 
4982 	wchar[] slice() {
4983 		return buffer;
4984 	}
4985 
4986 	void copyInto(R)(ref R r) {
4987 		static if(is(R == wchar[N], size_t N)) {
4988 			r[0 .. this.length] = slice[];
4989 			r[this.length] = 0;
4990 		} else static assert(0, "can only copy into wchar[n], not " ~ R.stringof);
4991 	}
4992 
4993 	/++
4994 		conversionFlags = [WindowsStringConversionFlags]
4995 	+/
4996 	this(in char[] data, int conversionFlags = 0) {
4997 		conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name
4998 		auto sz = sizeOfConvertedWstring(data, conversionFlags);
4999 		if(sz > staticBuffer.length)
5000 			buffer = new wchar[](sz);
5001 		else
5002 			buffer = staticBuffer[];
5003 
5004 		buffer = makeWindowsString(data, buffer, conversionFlags);
5005 	}
5006 }
5007 
5008 version(Windows)
5009 int sizeOfConvertedWstring(in char[] s, int conversionFlags) {
5010 	int size = 0;
5011 
5012 	if(conversionFlags & WindowsStringConversionFlags.convertNewLines) {
5013 		// need to convert line endings, which means the length will get bigger.
5014 
5015 		// BTW I betcha this could be faster with some simd stuff.
5016 		char last;
5017 		foreach(char ch; s) {
5018 			if(ch == 10 && last != 13)
5019 				size++; // will add a 13 before it...
5020 			size++;
5021 			last = ch;
5022 		}
5023 	} else {
5024 		// no conversion necessary, just estimate based on length
5025 		/*
5026 			I don't think there's any string with a longer length
5027 			in code units when encoded in UTF-16 than it has in UTF-8.
5028 			This will probably over allocate, but that's OK.
5029 		*/
5030 		size = cast(int) s.length;
5031 	}
5032 
5033 	if(conversionFlags & WindowsStringConversionFlags.zeroTerminate)
5034 		size++;
5035 
5036 	return size;
5037 }
5038 
5039 version(Windows)
5040 enum WindowsStringConversionFlags : int {
5041 	zeroTerminate = 1,
5042 	convertNewLines = 2,
5043 }
5044 
5045 version(Windows)
5046 class WindowsApiException : Exception {
5047 	char[256] buffer;
5048 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
5049 		assert(msg.length < 100);
5050 
5051 		auto error = GetLastError();
5052 		buffer[0 .. msg.length] = msg;
5053 		buffer[msg.length] = ' ';
5054 
5055 		int pos = cast(int) msg.length + 1;
5056 
5057 		if(error == 0)
5058 			buffer[pos++] = '0';
5059 		else {
5060 
5061 			auto ec = error;
5062 			auto init = pos;
5063 			while(ec) {
5064 				buffer[pos++] = (ec % 10) + '0';
5065 				ec /= 10;
5066 			}
5067 
5068 			buffer[pos++] = ' ';
5069 
5070 			size_t size = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, null, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), &(buffer[pos]), cast(DWORD) buffer.length - pos, null);
5071 
5072 			pos += size;
5073 		}
5074 
5075 
5076 		super(cast(string) buffer[0 .. pos], file, line, next);
5077 	}
5078 }
5079 
5080 class ErrnoApiException : Exception {
5081 	char[256] buffer;
5082 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
5083 		assert(msg.length < 100);
5084 
5085 		import core.stdc.errno;
5086 		auto error = errno;
5087 		buffer[0 .. msg.length] = msg;
5088 		buffer[msg.length] = ' ';
5089 
5090 		int pos = cast(int) msg.length + 1;
5091 
5092 		if(error == 0)
5093 			buffer[pos++] = '0';
5094 		else {
5095 			auto init = pos;
5096 			while(error) {
5097 				buffer[pos++] = (error % 10) + '0';
5098 				error /= 10;
5099 			}
5100 			for(int i = 0; i < (pos - init) / 2; i++) {
5101 				char c = buffer[i + init];
5102 				buffer[i + init] = buffer[pos - (i + init) - 1];
5103 				buffer[pos - (i + init) - 1] = c;
5104 			}
5105 		}
5106 
5107 
5108 		super(cast(string) buffer[0 .. pos], file, line, next);
5109 	}
5110 
5111 }
5112 
5113 version(Windows)
5114 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) {
5115 	if(str.length == 0)
5116 		return null;
5117 
5118 	int pos = 0;
5119 	dchar last;
5120 	foreach(dchar c; str) {
5121 		if(c <= 0xFFFF) {
5122 			if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13)
5123 				buffer[pos++] = 13;
5124 			buffer[pos++] = cast(wchar) c;
5125 		} else if(c <= 0x10FFFF) {
5126 			buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
5127 			buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
5128 		}
5129 
5130 		last = c;
5131 	}
5132 
5133 	if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) {
5134 		buffer[pos] = 0;
5135 	}
5136 
5137 	return buffer[0 .. pos];
5138 }
5139 
5140 version(Windows)
5141 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) {
5142 	if(str.length == 0)
5143 		return null;
5144 
5145 	auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null);
5146 	if(got == 0) {
5147 		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
5148 			throw new Exception("not enough buffer");
5149 		else
5150 			throw new Exception("conversion"); // FIXME: GetLastError
5151 	}
5152 	return buffer[0 .. got];
5153 }
5154 
5155 version(Windows)
5156 string makeUtf8StringFromWindowsString(in wchar[] str) {
5157 	char[] buffer;
5158 	auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null);
5159 	buffer.length = got;
5160 
5161 	// it is unique because we just allocated it above!
5162 	return cast(string) makeUtf8StringFromWindowsString(str, buffer);
5163 }
5164 
5165 version(Windows)
5166 string makeUtf8StringFromWindowsString(wchar* str) {
5167 	char[] buffer;
5168 	auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null);
5169 	buffer.length = got;
5170 
5171 	got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null);
5172 	if(got == 0) {
5173 		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
5174 			throw new Exception("not enough buffer");
5175 		else
5176 			throw new Exception("conversion"); // FIXME: GetLastError
5177 	}
5178 	return cast(string) buffer[0 .. got];
5179 }
5180 
5181 int findIndexOfZero(in wchar[] str) {
5182 	foreach(idx, wchar ch; str)
5183 		if(ch == 0)
5184 			return cast(int) idx;
5185 	return cast(int) str.length;
5186 }
5187 int findIndexOfZero(in char[] str) {
5188 	foreach(idx, char ch; str)
5189 		if(ch == 0)
5190 			return cast(int) idx;
5191 	return cast(int) str.length;
5192 }
5193 
5194 /// copies some text to the clipboard
5195 void setClipboardText(SimpleWindow clipboardOwner, string text) {
5196 	assert(clipboardOwner !is null);
5197 	version(Windows) {
5198 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
5199 			throw new Exception("OpenClipboard");
5200 		scope(exit)
5201 			CloseClipboard();
5202 		EmptyClipboard();
5203 		auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
5204 		auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars
5205 		if(handle is null) throw new Exception("GlobalAlloc");
5206 		if(auto data = cast(wchar*) GlobalLock(handle)) {
5207 			auto slice = data[0 .. sz];
5208 			scope(failure)
5209 				GlobalUnlock(handle);
5210 
5211 			auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
5212 
5213 			GlobalUnlock(handle);
5214 			SetClipboardData(CF_UNICODETEXT, handle);
5215 		}
5216 	} else version(X11) {
5217 		setX11Selection!"CLIPBOARD"(clipboardOwner, text);
5218 	} else version(OSXCocoa) {
5219 		throw new NotYetImplementedException();
5220 	} else static assert(0);
5221 }
5222 
5223 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) {
5224 	assert(clipboardOwner !is null);
5225 	version(Windows) {
5226 		if(OpenClipboard(clipboardOwner.impl.hwnd) == 0)
5227 			throw new Exception("OpenClipboard");
5228 		scope(exit)
5229 			CloseClipboard();
5230 		EmptyClipboard();
5231 
5232 
5233 		import arsd.bmp;
5234 		ubyte[] mdata;
5235 		mdata.reserve(img.width * img.height);
5236 		void sink(ubyte b) {
5237 			mdata ~= b;
5238 		}
5239 		writeBmpIndirect(img, &sink, false);
5240 
5241 		auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length);
5242 		if(handle is null) throw new Exception("GlobalAlloc");
5243 		if(auto data = cast(ubyte*) GlobalLock(handle)) {
5244 			auto slice = data[0 .. mdata.length];
5245 			scope(failure)
5246 				GlobalUnlock(handle);
5247 
5248 			slice[] = mdata[];
5249 
5250 			GlobalUnlock(handle);
5251 			SetClipboardData(CF_DIB, handle);
5252 		}
5253 	} else version(X11) {
5254 		static class X11SetSelectionHandler_Image : X11SetSelectionHandler {
5255 			mixin X11SetSelectionHandler_Basics;
5256 			private const(ubyte)[] mdata;
5257 			private const(ubyte)[] mdata_original;
5258 			this(MemoryImage img) {
5259 				import arsd.bmp;
5260 
5261 				mdata.reserve(img.width * img.height);
5262 				void sink(ubyte b) {
5263 					mdata ~= b;
5264 				}
5265 				writeBmpIndirect(img, &sink, true);
5266 
5267 				mdata_original = mdata;
5268 			}
5269 
5270 			Atom[] availableFormats() {
5271 				auto display = XDisplayConnection.get;
5272 				return [
5273 					GetAtom!"image/bmp"(display),
5274 					GetAtom!"TARGETS"(display)
5275 				];
5276 			}
5277 
5278 			ubyte[] getData(Atom format, return scope ubyte[] data) {
5279 				if(mdata.length < data.length) {
5280 					data[0 .. mdata.length] = mdata[];
5281 					auto ret = data[0 .. mdata.length];
5282 					mdata = mdata[$..$];
5283 					return ret;
5284 				} else {
5285 					data[] = mdata[0 .. data.length];
5286 					mdata = mdata[data.length .. $];
5287 					return data[];
5288 				}
5289 			}
5290 
5291 			void done() {
5292 				mdata = mdata_original;
5293 			}
5294 		}
5295 
5296 		setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img));
5297 	} else version(OSXCocoa) {
5298 		throw new NotYetImplementedException();
5299 	} else static assert(0);
5300 }
5301 
5302 
5303 version(X11) {
5304 	// and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11)
5305 
5306 	private Atom*[] interredAtoms; // for discardAndRecreate
5307 
5308 	// FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all.
5309 	/// Platform specific for X11
5310 	/// History: On February 21, 2021, I changed the default value of `create` to be true.
5311 	@property Atom GetAtom(string name, bool create = true)(Display* display) {
5312 		static Atom a;
5313 		if(!a) {
5314 			a = XInternAtom(display, name, !create);
5315 			interredAtoms ~= &a;
5316 		}
5317 		if(a == None)
5318 			throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false"));
5319 		return a;
5320 	}
5321 
5322 	/// Platform specific for X11 - gets atom names as a string
5323 	string getAtomName(Atom atom, Display* display) {
5324 		auto got = XGetAtomName(display, atom);
5325 		scope(exit) XFree(got);
5326 		import core.stdc..string;
5327 		string s = got[0 .. strlen(got)].idup;
5328 		return s;
5329 	}
5330 
5331 	/// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later
5332 	void setPrimarySelection(SimpleWindow window, string text) {
5333 		setX11Selection!"PRIMARY"(window, text);
5334 	}
5335 
5336 	/// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later
5337 	void setSecondarySelection(SimpleWindow window, string text) {
5338 		setX11Selection!"SECONDARY"(window, text);
5339 	}
5340 
5341 	interface X11SetSelectionHandler {
5342 		// should include TARGETS right now
5343 		Atom[] availableFormats();
5344 		// Return the slice of data you filled, empty slice if done.
5345 		// this is to support the incremental thing
5346 		ubyte[] getData(Atom format, return scope ubyte[] data);
5347 
5348 		void done();
5349 
5350 		void handleRequest(XEvent);
5351 
5352 		bool matchesIncr(Window, Atom);
5353 		void sendMoreIncr(XPropertyEvent*);
5354 	}
5355 
5356 	mixin template X11SetSelectionHandler_Basics() {
5357 		Window incrWindow;
5358 		Atom incrAtom;
5359 		Atom selectionAtom;
5360 		Atom formatAtom;
5361 		ubyte[] toSend;
5362 		bool matchesIncr(Window w, Atom a) {
5363 			return incrAtom && incrAtom == a && w == incrWindow;
5364 		}
5365 		void sendMoreIncr(XPropertyEvent* event) {
5366 			auto display = XDisplayConnection.get;
5367 
5368 			XChangeProperty (display,
5369 				incrWindow,
5370 				incrAtom,
5371 				formatAtom,
5372 				8 /* bits */, PropModeReplace,
5373 				toSend.ptr, cast(int) toSend.length);
5374 
5375 			if(toSend.length != 0) {
5376 				toSend = this.getData(formatAtom, toSend[]);
5377 			} else {
5378 				this.done();
5379 				incrWindow = None;
5380 				incrAtom = None;
5381 				selectionAtom = None;
5382 				formatAtom = None;
5383 				toSend = null;
5384 			}
5385 		}
5386 		void handleRequest(XEvent ev) {
5387 
5388 			auto display = XDisplayConnection.get;
5389 
5390 			XSelectionRequestEvent* event = &ev.xselectionrequest;
5391 			XSelectionEvent selectionEvent;
5392 			selectionEvent.type = EventType.SelectionNotify;
5393 			selectionEvent.display = event.display;
5394 			selectionEvent.requestor = event.requestor;
5395 			selectionEvent.selection = event.selection;
5396 			selectionEvent.time = event.time;
5397 			selectionEvent.target = event.target;
5398 
5399 			bool supportedType() {
5400 				foreach(t; this.availableFormats())
5401 					if(t == event.target)
5402 						return true;
5403 				return false;
5404 			}
5405 
5406 			if(event.property == None) {
5407 				selectionEvent.property = event.target;
5408 
5409 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
5410 				XFlush(display);
5411 			} if(event.target == GetAtom!"TARGETS"(display)) {
5412 				/* respond with the supported types */
5413 				auto tlist = this.availableFormats();
5414 				XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length);
5415 				selectionEvent.property = event.property;
5416 
5417 				XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
5418 				XFlush(display);
5419 			} else if(supportedType()) {
5420 				auto buffer = new ubyte[](1024 * 64);
5421 				auto toSend = this.getData(event.target, buffer[]);
5422 
5423 				if(toSend.length < 32 * 1024) {
5424 					// small enough to send directly...
5425 					selectionEvent.property = event.property;
5426 					XChangeProperty (display,
5427 						selectionEvent.requestor,
5428 						selectionEvent.property,
5429 						event.target,
5430 						8 /* bits */, 0 /* PropModeReplace */,
5431 						toSend.ptr, cast(int) toSend.length);
5432 
5433 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
5434 					XFlush(display);
5435 				} else {
5436 					// large, let's send incrementally
5437 					arch_ulong l = toSend.length;
5438 
5439 					// if I wanted other events from this window don't want to clear that out....
5440 					XWindowAttributes xwa;
5441 					XGetWindowAttributes(display, selectionEvent.requestor, &xwa);
5442 
5443 					XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask));
5444 
5445 					incrWindow = event.requestor;
5446 					incrAtom = event.property;
5447 					formatAtom = event.target;
5448 					selectionAtom = event.selection;
5449 					this.toSend = toSend;
5450 
5451 					selectionEvent.property = event.property;
5452 					XChangeProperty (display,
5453 						selectionEvent.requestor,
5454 						selectionEvent.property,
5455 						GetAtom!"INCR"(display),
5456 						32 /* bits */, PropModeReplace,
5457 						&l, 1);
5458 
5459 					XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent);
5460 					XFlush(display);
5461 				}
5462 				//if(after)
5463 					//after();
5464 			} else {
5465 				debug(sdpy_clip) {
5466 					import std.stdio; writeln("Unsupported data ", getAtomName(event.target, display));
5467 				}
5468 				selectionEvent.property = None; // I don't know how to handle this type...
5469 				XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent);
5470 				XFlush(display);
5471 			}
5472 		}
5473 	}
5474 
5475 	class X11SetSelectionHandler_Text : X11SetSelectionHandler {
5476 		mixin X11SetSelectionHandler_Basics;
5477 		private const(ubyte)[] text;
5478 		private const(ubyte)[] text_original;
5479 		this(string text) {
5480 			this.text = cast(const ubyte[]) text;
5481 			this.text_original = this.text;
5482 		}
5483 		Atom[] availableFormats() {
5484 			auto display = XDisplayConnection.get;
5485 			return [
5486 				GetAtom!"UTF8_STRING"(display),
5487 				GetAtom!"text/plain"(display),
5488 				XA_STRING,
5489 				GetAtom!"TARGETS"(display)
5490 			];
5491 		}
5492 
5493 		ubyte[] getData(Atom format, return scope ubyte[] data) {
5494 			if(text.length < data.length) {
5495 				data[0 .. text.length] = text[];
5496 				return data[0 .. text.length];
5497 			} else {
5498 				data[] = text[0 .. data.length];
5499 				text = text[data.length .. $];
5500 				return data[];
5501 			}
5502 		}
5503 
5504 		void done() {
5505 			text = text_original;
5506 		}
5507 	}
5508 
5509 	/// The `after` delegate is called after a client requests the UTF8_STRING thing. it is a mega-hack right now! (note to self july 2020... why did i do that?!)
5510 	void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
5511 		setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after);
5512 	}
5513 
5514 	void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) {
5515 		assert(window !is null);
5516 
5517 		auto display = XDisplayConnection.get();
5518 		static if (atomName == "PRIMARY") Atom a = XA_PRIMARY;
5519 		else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY;
5520 		else Atom a = GetAtom!atomName(display);
5521 
5522 		XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */);
5523 
5524 		window.impl.setSelectionHandlers[a] = data;
5525 	}
5526 
5527 	///
5528 	void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) {
5529 		getX11Selection!"PRIMARY"(window, handler);
5530 	}
5531 
5532 	// added July 28, 2020
5533 	// undocumented as experimental tho
5534 	interface X11GetSelectionHandler {
5535 		void handleData(Atom target, in ubyte[] data);
5536 		Atom findBestFormat(Atom[] answer);
5537 
5538 		void prepareIncremental(Window, Atom);
5539 		bool matchesIncr(Window, Atom);
5540 		void handleIncrData(Atom, in ubyte[] data);
5541 	}
5542 
5543 	mixin template X11GetSelectionHandler_Basics() {
5544 		Window incrWindow;
5545 		Atom incrAtom;
5546 
5547 		void prepareIncremental(Window w, Atom a) {
5548 			incrWindow = w;
5549 			incrAtom = a;
5550 		}
5551 		bool matchesIncr(Window w, Atom a) {
5552 			return incrWindow == w && incrAtom == a;
5553 		}
5554 
5555 		Atom incrFormatAtom;
5556 		ubyte[] incrData;
5557 		void handleIncrData(Atom format, in ubyte[] data) {
5558 			incrFormatAtom = format;
5559 
5560 			if(data.length)
5561 				incrData ~= data;
5562 			else
5563 				handleData(incrFormatAtom, incrData);
5564 
5565 		}
5566 	}
5567 
5568 	///
5569 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) {
5570 		assert(window !is null);
5571 
5572 		auto display = XDisplayConnection.get();
5573 		auto atom = GetAtom!atomName(display);
5574 
5575 		static class X11GetSelectionHandler_Text : X11GetSelectionHandler {
5576 			this(void delegate(in char[]) handler) {
5577 				this.handler = handler;
5578 			}
5579 
5580 			mixin X11GetSelectionHandler_Basics;
5581 
5582 			void delegate(in char[]) handler;
5583 
5584 			void handleData(Atom target, in ubyte[] data) {
5585 				if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
5586 					handler(cast(const char[]) data);
5587 			}
5588 
5589 			Atom findBestFormat(Atom[] answer) {
5590 				Atom best = None;
5591 				foreach(option; answer) {
5592 					if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
5593 						best = option;
5594 						break;
5595 					} else if(option == XA_STRING) {
5596 						best = option;
5597 					} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
5598 						best = option;
5599 					}
5600 				}
5601 				return best;
5602 			}
5603 		}
5604 
5605 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler);
5606 
5607 		auto target = GetAtom!"TARGETS"(display);
5608 
5609 		// SDD_DATA is "simpledisplay.d data"
5610 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp);
5611 	}
5612 
5613 	/// Gets the image on the clipboard, if there is one. Added July 2020.
5614 	void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) {
5615 		assert(window !is null);
5616 
5617 		auto display = XDisplayConnection.get();
5618 		auto atom = GetAtom!atomName(display);
5619 
5620 		static class X11GetSelectionHandler_Image : X11GetSelectionHandler {
5621 			this(void delegate(MemoryImage) handler) {
5622 				this.handler = handler;
5623 			}
5624 
5625 			mixin X11GetSelectionHandler_Basics;
5626 
5627 			void delegate(MemoryImage) handler;
5628 
5629 			void handleData(Atom target, in ubyte[] data) {
5630 				if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) {
5631 					import arsd.bmp;
5632 					handler(readBmp(data));
5633 				}
5634 			}
5635 
5636 			Atom findBestFormat(Atom[] answer) {
5637 				Atom best = None;
5638 				foreach(option; answer) {
5639 					if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) {
5640 						best = option;
5641 					}
5642 				}
5643 				return best;
5644 			}
5645 
5646 		}
5647 
5648 
5649 		window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler);
5650 
5651 		auto target = GetAtom!"TARGETS"(display);
5652 
5653 		// SDD_DATA is "simpledisplay.d data"
5654 		XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/);
5655 	}
5656 
5657 
5658 	///
5659 	void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) {
5660 		Atom actualType;
5661 		int actualFormat;
5662 		arch_ulong actualItems;
5663 		arch_ulong bytesRemaining;
5664 		void* data;
5665 
5666 		auto display = XDisplayConnection.get();
5667 		if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) {
5668 			if(actualFormat == 0)
5669 				return null;
5670 			else {
5671 				int byteLength;
5672 				if(actualFormat == 32) {
5673 					// 32 means it is a C long... which is variable length
5674 					actualFormat = cast(int) arch_long.sizeof * 8;
5675 				}
5676 
5677 				// then it is just a bit count
5678 				byteLength = cast(int) (actualItems * actualFormat / 8);
5679 
5680 				auto d = new ubyte[](byteLength);
5681 				d[] = cast(ubyte[]) data[0 .. byteLength];
5682 				XFree(data);
5683 				return d;
5684 			}
5685 		}
5686 		return null;
5687 	}
5688 
5689 	/* defined in the systray spec */
5690 	enum SYSTEM_TRAY_REQUEST_DOCK   = 0;
5691 	enum SYSTEM_TRAY_BEGIN_MESSAGE  = 1;
5692 	enum SYSTEM_TRAY_CANCEL_MESSAGE = 2;
5693 
5694 
5695 	/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
5696 	 * instead of delegates, you can subclass this, and override `doHandle()` method. */
5697 	public class GlobalHotkey {
5698 		KeyEvent key;
5699 		void delegate () handler;
5700 
5701 		void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
5702 
5703 		/// Create from initialzed KeyEvent object
5704 		this (KeyEvent akey, void delegate () ahandler=null) {
5705 			if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
5706 			key = akey;
5707 			handler = ahandler;
5708 		}
5709 
5710 		/// Create from emacs-like key name ("C-M-Y", etc.)
5711 		this (const(char)[] akey, void delegate () ahandler=null) {
5712 			key = KeyEvent.parse(akey);
5713 			if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
5714 			handler = ahandler;
5715 		}
5716 
5717 	}
5718 
5719 	private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
5720 		//conwriteln("failed to grab key");
5721 		GlobalHotkeyManager.ghfailed = true;
5722 		return 0;
5723 	}
5724 
5725 	private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc {
5726 		import core.stdc.stdio;
5727 		char[265] buffer;
5728 		XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length);
5729 		printf("ERROR: %s\n", buffer.ptr);
5730 		return 0;
5731 	}
5732 
5733 	/++
5734 		Global hotkey manager. It contains static methods to manage global hotkeys.
5735 
5736 		---
5737 		 try {
5738 			GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
5739 		} catch (Exception e) {
5740 			conwriteln("ERROR registering hotkey!");
5741 		}
5742 		---
5743 
5744 		The key strings are based on Emacs. In practical terms,
5745 		`M` means `alt` and `H` means the Windows logo key. `C`
5746 		is `ctrl`.
5747 
5748 		$(WARNING
5749 			This is X-specific right now. If you are on
5750 			Windows, try [registerHotKey] instead.
5751 
5752 			We will probably merge these into a single
5753 			interface later.
5754 		)
5755 	+/
5756 	public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
5757 		version(X11) {
5758 			void recreateAfterDisconnect() {
5759 				throw new Exception("NOT IMPLEMENTED");
5760 			}
5761 			void discardConnectionState() {
5762 				throw new Exception("NOT IMPLEMENTED");
5763 			}
5764 		}
5765 
5766 		private static immutable uint[8] masklist = [ 0,
5767 			KeyOrButtonMask.LockMask,
5768 			KeyOrButtonMask.Mod2Mask,
5769 			KeyOrButtonMask.Mod3Mask,
5770 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
5771 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
5772 			KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
5773 			KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
5774 		];
5775 		private __gshared GlobalHotkeyManager ghmanager;
5776 		private __gshared bool ghfailed = false;
5777 
5778 		private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
5779 			if (modmask == 0) return false;
5780 			if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
5781 			if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
5782 			return true;
5783 		}
5784 
5785 		private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
5786 			modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
5787 			modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
5788 			return modmask;
5789 		}
5790 
5791 		private static uint keyEvent2KeyCode() (in auto ref KeyEvent ke) {
5792 			uint keycode = cast(uint)ke.key;
5793 			auto dpy = XDisplayConnection.get;
5794 			return XKeysymToKeycode(dpy, keycode);
5795 		}
5796 
5797 		private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
5798 
5799 		private __gshared GlobalHotkey[ulong] globalHotkeyList;
5800 
5801 		NativeEventHandler getNativeEventHandler () {
5802 			return delegate int (XEvent e) {
5803 				if (e.type != EventType.KeyPress) return 1;
5804 				auto kev = cast(const(XKeyEvent)*)&e;
5805 				auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
5806 				if (auto ghkp = hash in globalHotkeyList) {
5807 					try {
5808 						ghkp.doHandle();
5809 					} catch (Exception e) {
5810 						import core.stdc.stdio : stderr, fprintf;
5811 						stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
5812 					}
5813 				}
5814 				return 1;
5815 			};
5816 		}
5817 
5818 		private this () {
5819 			auto dpy = XDisplayConnection.get;
5820 			auto root = RootWindow(dpy, DefaultScreen(dpy));
5821 			CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
5822 			XDisplayConnection.addRootInput(EventMask.KeyPressMask);
5823 		}
5824 
5825 		/// Register new global hotkey with initialized `GlobalHotkey` object.
5826 		/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
5827 		static void register (GlobalHotkey gh) {
5828 			if (gh is null) return;
5829 			if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
5830 
5831 			auto dpy = XDisplayConnection.get;
5832 			immutable keycode = keyEvent2KeyCode(gh.key);
5833 
5834 			auto hash = keyCode2Hash(keycode, gh.key.modifierState);
5835 			if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
5836 			if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
5837 			XSync(dpy, 0/*False*/);
5838 
5839 			Window root = RootWindow(dpy, DefaultScreen(dpy));
5840 			XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
5841 			ghfailed = false;
5842 			foreach (immutable uint ormask; masklist[]) {
5843 				XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
5844 			}
5845 			XSync(dpy, 0/*False*/);
5846 			XSetErrorHandler(savedErrorHandler);
5847 
5848 			if (ghfailed) {
5849 				savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
5850 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
5851 				XSync(dpy, 0/*False*/);
5852 				XSetErrorHandler(savedErrorHandler);
5853 				throw new Exception("cannot register global hotkey");
5854 			}
5855 
5856 			globalHotkeyList[hash] = gh;
5857 		}
5858 
5859 		/// Ditto
5860 		static void register (const(char)[] akey, void delegate () ahandler) {
5861 			register(new GlobalHotkey(akey, ahandler));
5862 		}
5863 
5864 		private static void removeByHash (ulong hash) {
5865 			if (auto ghp = hash in globalHotkeyList) {
5866 				auto dpy = XDisplayConnection.get;
5867 				immutable keycode = keyEvent2KeyCode(ghp.key);
5868 				Window root = RootWindow(dpy, DefaultScreen(dpy));
5869 				XSync(dpy, 0/*False*/);
5870 				XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
5871 				foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
5872 				XSync(dpy, 0/*False*/);
5873 				XSetErrorHandler(savedErrorHandler);
5874 				globalHotkeyList.remove(hash);
5875 			}
5876 		}
5877 
5878 		/// Register new global hotkey with previously used `GlobalHotkey` object.
5879 		/// It is safe to unregister unknown or invalid hotkey.
5880 		static void unregister (GlobalHotkey gh) {
5881 			//TODO: add second AA for faster search? prolly doesn't worth it.
5882 			if (gh is null) return;
5883 			foreach (const ref kv; globalHotkeyList.byKeyValue) {
5884 				if (kv.value is gh) {
5885 					removeByHash(kv.key);
5886 					return;
5887 				}
5888 			}
5889 		}
5890 
5891 		/// Ditto.
5892 		static void unregister (const(char)[] key) {
5893 			auto kev = KeyEvent.parse(key);
5894 			immutable keycode = keyEvent2KeyCode(kev);
5895 			removeByHash(keyCode2Hash(keycode, kev.modifierState));
5896 		}
5897 	}
5898 }
5899 
5900 version(Windows) {
5901 	/// Platform-specific for Windows. Sends a string as key press and release events to the actively focused window (not necessarily your application)
5902 	void sendSyntheticInput(wstring s) {
5903 		INPUT[] inputs;
5904 		inputs.reserve(s.length * 2);
5905 
5906 		foreach(wchar c; s) {
5907 			INPUT input;
5908 			input.type = INPUT_KEYBOARD;
5909 			input.ki.wScan = c;
5910 			input.ki.dwFlags = KEYEVENTF_UNICODE;
5911 			inputs ~= input;
5912 
5913 			input.ki.dwFlags |= KEYEVENTF_KEYUP;
5914 			inputs ~= input;
5915 		}
5916 
5917 		if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
5918 			throw new Exception("SendInput failed");
5919 		}
5920 	}
5921 
5922 
5923 	// global hotkey helper function
5924 
5925 	/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID.
5926 	int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
5927 		__gshared int hotkeyId = 0;
5928 		int id = ++hotkeyId;
5929 		if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk))
5930 			throw new Exception("RegisterHotKey failed");
5931 
5932 		__gshared void delegate()[WPARAM][HWND] handlers;
5933 
5934 		handlers[window.impl.hwnd][id] = handler;
5935 
5936 		int delegate(HWND, UINT, WPARAM, LPARAM) oldHandler;
5937 
5938 		auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5939 			switch(msg) {
5940 				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx
5941 				case WM_HOTKEY:
5942 					if(auto list = hwnd in handlers) {
5943 						if(auto h = wParam in *list) {
5944 							(*h)();
5945 							return 0;
5946 						}
5947 					}
5948 				goto default;
5949 				default:
5950 			}
5951 			if(oldHandler)
5952 				return oldHandler(hwnd, msg, wParam, lParam);
5953 			return 1; // pass it on
5954 		};
5955 
5956 		if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) {
5957 			oldHandler = window.handleNativeEvent;
5958 			window.handleNativeEvent = nativeEventHandler;
5959 		}
5960 
5961 		return id;
5962 	}
5963 
5964 	/// Platform-specific for Windows. Unregisters a key. The id is the value returned by registerHotKey.
5965 	void unregisterHotKey(SimpleWindow window, int id) {
5966 		if(!UnregisterHotKey(window.impl.hwnd, id))
5967 			throw new Exception("UnregisterHotKey");
5968 	}
5969 }
5970 
5971 version (X11) {
5972 	pragma(lib, "dl");
5973 	import core.sys.posix.dlfcn;
5974 
5975 	/++
5976 		Allows for sending synthetic input to the X server via the Xtst
5977 		extension.
5978 
5979 		Please remember user input is meant to be user - don't use this
5980 		if you have some other alternative!
5981 
5982 		If you need this on Windows btw, the top-level [sendSyntheticInput] shows
5983 		the Win32 api to start it, but I only did basics there, PR welcome if you like,
5984 		it is an easy enough function to use.
5985 
5986 		History: Added May 17, 2020.
5987 	+/
5988 	struct SyntheticInput {
5989 		@disable this();
5990 
5991 		private void* lib;
5992 		private int* refcount;
5993 
5994 		private extern(C) {
5995 			void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
5996 			void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
5997 		}
5998 
5999 		/// The dummy param must be 0.
6000 		this(int dummy) {
6001 			lib = dlopen("libXtst.so", RTLD_NOW);
6002 			if(lib is null)
6003 				throw new Exception("cannot load xtest lib extension");
6004 			scope(failure)
6005 				dlclose(lib);
6006 
6007 			XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent");
6008 			XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent");
6009 
6010 			if(XTestFakeKeyEvent is null)
6011 				throw new Exception("No XTestFakeKeyEvent");
6012 			if(XTestFakeButtonEvent is null)
6013 				throw new Exception("No XTestFakeButtonEvent");
6014 
6015 			refcount = new int;
6016 			*refcount = 1;
6017 		}
6018 
6019 		this(this) {
6020 			if(refcount)
6021 				*refcount += 1;
6022 		}
6023 
6024 		~this() {
6025 			if(refcount) {
6026 				*refcount -= 1;
6027 				if(*refcount == 0)
6028 					// I commented this because if I close the lib before
6029 					// XCloseDisplay, it is liable to segfault... so just
6030 					// gonna keep it loaded if it is loaded, no big deal
6031 					// anyway.
6032 					{} // dlclose(lib);
6033 			}
6034 		}
6035 
6036 		/// This ONLY works with basic ascii!
6037 		void sendSyntheticInput(string s) {
6038 			int delay = 0;
6039 			foreach(ch; s) {
6040 				pressKey(cast(Key) ch, true, delay);
6041 				pressKey(cast(Key) ch, false, delay);
6042 				delay += 5;
6043 			}
6044 		}
6045 
6046 		///
6047 		void pressKey(Key key, bool pressed, int delay = 0) {
6048 			XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
6049 		}
6050 
6051 		///
6052 		void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
6053 			int btn;
6054 
6055 			switch(button) {
6056 				case MouseButton.left: btn = 1; break;
6057 				case MouseButton.middle: btn = 2; break;
6058 				case MouseButton.right: btn = 3; break;
6059 				case MouseButton.wheelUp: btn = 4; break;
6060 				case MouseButton.wheelDown: btn = 5; break;
6061 				case MouseButton.backButton: btn = 8; break;
6062 				case MouseButton.forwardButton: btn = 9; break;
6063 				default:
6064 			}
6065 
6066 			assert(btn);
6067 
6068 			XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
6069 		}
6070 
6071 		///
6072 		static void moveMouseArrowBy(int dx, int dy) {
6073 			auto disp = XDisplayConnection.get();
6074 			XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
6075 			XFlush(disp);
6076 		}
6077 
6078 		///
6079 		static void moveMouseArrowTo(int x, int y) {
6080 			auto disp = XDisplayConnection.get();
6081 			auto root = RootWindow(disp, DefaultScreen(disp));
6082 			XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
6083 			XFlush(disp);
6084 		}
6085 	}
6086 }
6087 
6088 
6089 
6090 /++
6091 	[ScreenPainter] operations can use different operations to combine the color with the color on screen.
6092 
6093 	See_Also:
6094 	$(LIST
6095 		*[ScreenPainter]
6096 		*[ScreenPainter.rasterOp]
6097 	)
6098 +/
6099 enum RasterOp {
6100 	normal, /// Replaces the pixel.
6101 	xor, /// Uses bitwise xor to draw.
6102 }
6103 
6104 // being phobos-free keeps the size WAY down
6105 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
6106 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; }
6107 package(arsd) const(wchar)* toWStringz(string s) {
6108 	wstring r;
6109 	foreach(dchar c; s)
6110 		r ~= c;
6111 	r ~= '\0';
6112 	return r.ptr;
6113 }
6114 private string[] split(in void[] a, char c) {
6115 		string[] ret;
6116 		size_t previous = 0;
6117 		foreach(i, char ch; cast(ubyte[]) a) {
6118 			if(ch == c) {
6119 				ret ~= cast(string) a[previous .. i];
6120 				previous = i + 1;
6121 			}
6122 		}
6123 		if(previous != a.length)
6124 			ret ~= cast(string) a[previous .. $];
6125 		return ret;
6126 	}
6127 
6128 version(without_opengl) {
6129 	enum OpenGlOptions {
6130 		no,
6131 	}
6132 } else {
6133 	/++
6134 		Determines if you want an OpenGL context created on the new window.
6135 
6136 
6137 		See more: [#topics-3d|in the 3d topic].
6138 
6139 		---
6140 		import arsd.simpledisplay;
6141 		void main() {
6142 			auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes);
6143 
6144 			// Set up the matrix
6145 			window.setAsCurrentOpenGlContext(); // make this window active
6146 
6147 			// This is called on each frame, we will draw our scene
6148 			window.redrawOpenGlScene = delegate() {
6149 
6150 			};
6151 
6152 			window.eventLoop(0);
6153 		}
6154 		---
6155 	+/
6156 	enum OpenGlOptions {
6157 		no, /// No OpenGL context is created
6158 		yes, /// Yes, create an OpenGL context
6159 	}
6160 
6161 	version(X11) {
6162 		static if (!SdpyIsUsingIVGLBinds) {
6163 
6164 
6165 			struct __GLXFBConfigRec {}
6166 			alias GLXFBConfig = __GLXFBConfigRec*;
6167 
6168 			//pragma(lib, "GL");
6169 			//pragma(lib, "GLU");
6170 			interface GLX {
6171 			extern(C) nothrow @nogc {
6172 				 XVisualInfo* glXChooseVisual(Display *dpy, int screen,
6173 						const int *attrib_list);
6174 
6175 				 void glXCopyContext(Display *dpy, GLXContext src,
6176 						GLXContext dst, arch_ulong mask);
6177 
6178 				 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis,
6179 						GLXContext share_list, Bool direct);
6180 
6181 				 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
6182 						Pixmap pixmap);
6183 
6184 				 void glXDestroyContext(Display *dpy, GLXContext ctx);
6185 
6186 				 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix);
6187 
6188 				 int glXGetConfig(Display *dpy, XVisualInfo *vis,
6189 						int attrib, int *value);
6190 
6191 				 GLXContext glXGetCurrentContext();
6192 
6193 				 GLXDrawable glXGetCurrentDrawable();
6194 
6195 				 Bool glXIsDirect(Display *dpy, GLXContext ctx);
6196 
6197 				 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable,
6198 						GLXContext ctx);
6199 
6200 				 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);
6201 
6202 				 Bool glXQueryVersion(Display *dpy, int *major, int *minor);
6203 
6204 				 void glXSwapBuffers(Display *dpy, GLXDrawable drawable);
6205 
6206 				 void glXUseXFont(Font font, int first, int count, int list_base);
6207 
6208 				 void glXWaitGL();
6209 
6210 				 void glXWaitX();
6211 
6212 
6213 				GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*);
6214 				int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*);
6215 				XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig);
6216 
6217 				char* glXQueryExtensionsString (Display*, int);
6218 				void* glXGetProcAddress (const(char)*);
6219 
6220 			}
6221 			}
6222 
6223 			version(OSX)
6224 			mixin DynamicLoad!(GLX, "GL", 0, true) glx;
6225 			else
6226 			mixin DynamicLoad!(GLX, "GLX", 0, true) glx;
6227 			shared static this() {
6228 				glx.loadDynamicLibrary();
6229 			}
6230 
6231 			alias glbindGetProcAddress = glXGetProcAddress;
6232 		}
6233 	} else version(Windows) {
6234 		/* it is done below by interface GL */
6235 	} else
6236 		static assert(0, "OpenGL not supported on your system yet. Try -version=X11 if you have X Windows available, or -version=without_opengl to go without.");
6237 }
6238 
6239 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.")
6240 alias Resizablity = Resizability;
6241 
6242 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...
6243 enum Resizability {
6244 	fixedSize, /// the window cannot be resized
6245 	allowResizing, /// the window can be resized. The buffer (if there is one) will automatically adjust size, but not stretch the contents. the windowResized delegate will be called so you can respond to the new size yourself.
6246 	automaticallyScaleIfPossible, /// if possible, your drawing buffer will remain the same size and simply be automatically scaled to the new window size. If this is impossible, it will not allow the user to resize the window at all. Note: window.width and window.height WILL be adjusted, which might throw you off if you draw based on them, so keep track of your expected width and height separately. That way, when it is scaled, things won't be thrown off.
6247 
6248 	// FIXME: automaticallyScaleIfPossible should adjust the OpenGL viewport on resize events
6249 }
6250 
6251 
6252 /++
6253 	Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.
6254 +/
6255 enum TextAlignment : uint {
6256 	Left = 0, ///
6257 	Center = 1, ///
6258 	Right = 2, ///
6259 
6260 	VerticalTop = 0, ///
6261 	VerticalCenter = 4, ///
6262 	VerticalBottom = 8, ///
6263 }
6264 
6265 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
6266 alias Rectangle = arsd.color.Rectangle;
6267 
6268 
6269 /++
6270 	Keyboard press and release events
6271 +/
6272 struct KeyEvent {
6273 	/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key]
6274 	Key key;
6275 	ubyte hardwareCode; /// A platform and hardware specific code for the key
6276 	bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent...
6277 
6278 	dchar character; ///
6279 
6280 	uint modifierState; /// see enum [ModifierState]. They are bitwise combined together.
6281 
6282 	SimpleWindow window; /// associated Window
6283 
6284 	// convert key event to simplified string representation a-la emacs
6285 	const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted {
6286 		uint dpos = 0;
6287 		void put (const(char)[] s...) nothrow @trusted {
6288 			static if (growdest) {
6289 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; }
6290 			} else {
6291 				foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch;
6292 			}
6293 		}
6294 
6295 		void putMod (ModifierState mod, Key key, string text) nothrow @trusted {
6296 			if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text);
6297 		}
6298 
6299 		if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null;
6300 
6301 		// put modifiers
6302 		// releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it
6303 		putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+");
6304 		putMod(ModifierState.alt, Key.Alt, "Alt+");
6305 		putMod(ModifierState.windows, Key.Shift, "Windows+");
6306 		putMod(ModifierState.shift, Key.Shift, "Shift+");
6307 
6308 		if (this.key) {
6309 			foreach (string kn; __traits(allMembers, Key)) {
6310 				if (this.key == __traits(getMember, Key, kn)) {
6311 					// HACK!
6312 					static if (kn == "N0") put("0");
6313 					else static if (kn == "N1") put("1");
6314 					else static if (kn == "N2") put("2");
6315 					else static if (kn == "N3") put("3");
6316 					else static if (kn == "N4") put("4");
6317 					else static if (kn == "N5") put("5");
6318 					else static if (kn == "N6") put("6");
6319 					else static if (kn == "N7") put("7");
6320 					else static if (kn == "N8") put("8");
6321 					else static if (kn == "N9") put("9");
6322 					else put(kn);
6323 					return dest[0..dpos];
6324 				}
6325 			}
6326 			put("Unknown");
6327 		} else {
6328 			if (dpos && dest[dpos-1] == '+') --dpos;
6329 		}
6330 		return dest[0..dpos];
6331 	}
6332 
6333 	string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here
6334 
6335 	/** Parse string into key name with modifiers. It accepts things like:
6336 	 *
6337 	 * C-H-1 -- emacs style (ctrl, and windows, and 1)
6338 	 *
6339 	 * Ctrl+Win+1 -- windows style
6340 	 *
6341 	 * Ctrl-Win-1 -- '-' is a valid delimiter too
6342 	 *
6343 	 * Ctrl Win 1 -- and space
6344 	 *
6345 	 * and even "Win + 1 + Ctrl".
6346 	 */
6347 	static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc {
6348 		auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set
6349 
6350 		// remove trailing spaces
6351 		while (name.length && name[$-1] <= ' ') name = name[0..$-1];
6352 
6353 		// tokens delimited by blank, '+', or '-'
6354 		// null on eol
6355 		const(char)[] getToken () nothrow @trusted @nogc {
6356 			// remove leading spaces and delimiters
6357 			while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$];
6358 			if (name.length == 0) return null; // oops, no more tokens
6359 			// get token
6360 			size_t epos = 0;
6361 			while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos;
6362 			assert(epos > 0 && epos <= name.length);
6363 			auto res = name[0..epos];
6364 			name = name[epos..$];
6365 			return res;
6366 		}
6367 
6368 		static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
6369 			if (s0.length != s1.length) return false;
6370 			foreach (immutable ci, char c0; s0) {
6371 				if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
6372 				char c1 = s1[ci];
6373 				if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
6374 				if (c0 != c1) return false;
6375 			}
6376 			return true;
6377 		}
6378 
6379 		if (ignoreModsOut !is null) *ignoreModsOut = false;
6380 		if (updown !is null) *updown = -1;
6381 		KeyEvent res;
6382 		res.key = cast(Key)0; // just in case
6383 		const(char)[] tk, tkn; // last token
6384 		bool allowEmascStyle = true;
6385 		bool ignoreModifiers = false;
6386 		tokenloop: for (;;) {
6387 			tk = tkn;
6388 			tkn = getToken();
6389 			//k8: yay, i took "Bloody Mess" trait from Fallout!
6390 			if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; }
6391 			if (tkn.length == 0 && tk.length == 0) break; // no more tokens
6392 			if (allowEmascStyle && tkn.length != 0) {
6393 				if (tk.length == 1) {
6394 					char mdc = tk[0];
6395 					if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper()
6396 					if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; }
6397 					if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; }
6398 					if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; }
6399 					if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; }
6400 					if (mdc == '*') { ignoreModifiers = true; continue tokenloop; }
6401 					if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; }
6402 					if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; }
6403 				}
6404 			}
6405 			allowEmascStyle = false;
6406 			if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; }
6407 			if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; }
6408 			if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; }
6409 			if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; }
6410 			if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; }
6411 			if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; }
6412 			if (tk == "*") { ignoreModifiers = true; continue tokenloop; }
6413 			if (tk.length == 0) continue;
6414 			// try key name
6415 			if (res.key == 0) {
6416 				// little hack
6417 				if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') {
6418 					final switch (tk[0]) {
6419 						case '0': tk = "N0"; break;
6420 						case '1': tk = "N1"; break;
6421 						case '2': tk = "N2"; break;
6422 						case '3': tk = "N3"; break;
6423 						case '4': tk = "N4"; break;
6424 						case '5': tk = "N5"; break;
6425 						case '6': tk = "N6"; break;
6426 						case '7': tk = "N7"; break;
6427 						case '8': tk = "N8"; break;
6428 						case '9': tk = "N9"; break;
6429 					}
6430 				}
6431 				foreach (string kn; __traits(allMembers, Key)) {
6432 					if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; }
6433 				}
6434 			}
6435 			// unknown or duplicate key name, get out of here
6436 			break;
6437 		}
6438 		if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers;
6439 		return res; // something
6440 	}
6441 
6442 	bool opEquals() (const(char)[] name) const nothrow @trusted @nogc {
6443 		enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows);
6444 		void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) {
6445 			if (kk == k) { mask |= mst; kk = cast(Key)0; }
6446 		}
6447 		bool ignoreMods;
6448 		int updown;
6449 		auto ke = KeyEvent.parse(name, &ignoreMods, &updown);
6450 		if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false;
6451 		if (this.key != ke.key) {
6452 			// things like "ctrl+alt" are complicated
6453 			uint tkm = this.modifierState&modmask;
6454 			uint kkm = ke.modifierState&modmask;
6455 			Key tk = this.key;
6456 			// ke
6457 			doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl);
6458 			doModKey(kkm, ke.key, Key.Alt, ModifierState.alt);
6459 			doModKey(kkm, ke.key, Key.Windows, ModifierState.windows);
6460 			doModKey(kkm, ke.key, Key.Shift, ModifierState.shift);
6461 			// this
6462 			doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl);
6463 			doModKey(tkm, tk, Key.Alt, ModifierState.alt);
6464 			doModKey(tkm, tk, Key.Windows, ModifierState.windows);
6465 			doModKey(tkm, tk, Key.Shift, ModifierState.shift);
6466 			return (tk == ke.key && tkm == kkm);
6467 		}
6468 		return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask)));
6469 	}
6470 }
6471 
6472 /// sets the application name.
6473 @property string ApplicationName(string name) {
6474 	return _applicationName = name;
6475 }
6476 
6477 string _applicationName;
6478 
6479 /// ditto
6480 @property string ApplicationName() {
6481 	if(_applicationName is null) {
6482 		import core.runtime;
6483 		return Runtime.args[0];
6484 	}
6485 	return _applicationName;
6486 }
6487 
6488 
6489 /// Type of a [MouseEvent]
6490 enum MouseEventType : int {
6491 	motion = 0, /// The mouse moved inside the window
6492 	buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
6493 	buttonReleased = 2, /// A mouse button was released
6494 }
6495 
6496 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
6497 /++
6498 	Listen for this on your event listeners if you are interested in mouse action.
6499 
6500 	Note that [button] is used on mouse press and release events. If you are curious about which button is being held in during motion, use [modifierState] and check the bitmask for [ModifierState.leftButtonDown], etc.
6501 
6502 	Examples:
6503 
6504 	This will draw boxes on the window with the mouse as you hold the left button.
6505 	---
6506 	import arsd.simpledisplay;
6507 
6508 	void main() {
6509 		auto window = new SimpleWindow();
6510 
6511 		window.eventLoop(0,
6512 			(MouseEvent ev) {
6513 				if(ev.modifierState & ModifierState.leftButtonDown) {
6514 					auto painter = window.draw();
6515 					painter.fillColor = Color.red;
6516 					painter.outlineColor = Color.black;
6517 					painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16);
6518 				}
6519 			}
6520 		);
6521 	}
6522 	---
6523 +/
6524 struct MouseEvent {
6525 	MouseEventType type; /// movement, press, release, double click. See [MouseEventType]
6526 
6527 	int x; /// Current X position of the cursor when the event fired, relative to the upper-left corner of the window, reported in pixels. (0, 0) is the upper left, (window.width - 1, window.height - 1) is the lower right corner of the window.
6528 	int y; /// Current Y position of the cursor when the event fired.
6529 
6530 	int dx; /// Change in X position since last report
6531 	int dy; /// Change in Y position since last report
6532 
6533 	MouseButton button; /// See [MouseButton]
6534 	int modifierState; /// See [ModifierState]
6535 
6536 	version(X11)
6537 		private Time timestamp;
6538 
6539 	/// Returns a linear representation of mouse button,
6540 	/// for use with static arrays. Guaranteed to be >= 0 && <= 15
6541 	///
6542 	/// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`.
6543 	@property ubyte buttonLinear() const {
6544 		import core.bitop;
6545 		if(button == 0)
6546 			return 0;
6547 		return (bsf(button) + 1) & 0b1111;
6548 	}
6549 
6550 	bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed]
6551 
6552 	SimpleWindow window; /// The window in which the event happened.
6553 
6554 	Point globalCoordinates() {
6555 		Point p;
6556 		if(window is null)
6557 			throw new Exception("wtf");
6558 		static if(UsingSimpledisplayX11) {
6559 			Window child;
6560 			XTranslateCoordinates(
6561 				XDisplayConnection.get,
6562 				window.impl.window,
6563 				RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)),
6564 				x, y, &p.x, &p.y, &child);
6565 			return p;
6566 		} else version(Windows) {
6567 			POINT[1] points;
6568 			points[0].x = x;
6569 			points[0].y = y;
6570 			MapWindowPoints(
6571 				window.impl.hwnd,
6572 				null,
6573 				points.ptr,
6574 				points.length
6575 			);
6576 			p.x = points[0].x;
6577 			p.y = points[0].y;
6578 
6579 			return p;
6580 		} else version(OSXCocoa) {
6581 			throw new NotYetImplementedException();
6582 		} else static assert(0);
6583 	}
6584 
6585 	bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); }
6586 
6587 	/**
6588 	can contain emacs-like modifier prefix
6589 	case-insensitive names:
6590 		lmbX/leftX
6591 		rmbX/rightX
6592 		mmbX/middleX
6593 		wheelX
6594 		motion (no prefix allowed)
6595 	'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down"
6596 	*/
6597 	static bool equStr() (in auto ref MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc {
6598 		if (str.length == 0) return false; // just in case
6599 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); }
6600 		enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U }
6601 		auto anchor = str;
6602 		uint mods = 0; // uint.max == any
6603 		// interesting bits in kmod
6604 		uint kmodmask =
6605 			ModifierState.shift|
6606 			ModifierState.ctrl|
6607 			ModifierState.alt|
6608 			ModifierState.windows|
6609 			ModifierState.leftButtonDown|
6610 			ModifierState.middleButtonDown|
6611 			ModifierState.rightButtonDown|
6612 			0;
6613 		uint lastButt = uint.max; // otherwise, bit 31 means "down"
6614 		bool wasButtons = false;
6615 		while (str.length) {
6616 			if (str.ptr[0] <= ' ') {
6617 				while (str.length && str.ptr[0] <= ' ') str = str[1..$];
6618 				continue;
6619 			}
6620 			// one-letter modifier?
6621 			if (str.length >= 2 && str.ptr[1] == '-') {
6622 				switch (str.ptr[0]) {
6623 					case '*': // "any" modifier (cannot be undone)
6624 						mods = mods.max;
6625 						break;
6626 					case 'C': case 'c': // emacs "ctrl"
6627 						if (mods != mods.max) mods |= ModifierState.ctrl;
6628 						break;
6629 					case 'M': case 'm': // emacs "meta"
6630 						if (mods != mods.max) mods |= ModifierState.alt;
6631 						break;
6632 					case 'S': case 's': // emacs "shift"
6633 						if (mods != mods.max) mods |= ModifierState.shift;
6634 						break;
6635 					case 'H': case 'h': // emacs "hyper" (aka winkey)
6636 						if (mods != mods.max) mods |= ModifierState.windows;
6637 						break;
6638 					default:
6639 						return false; // unknown modifier
6640 				}
6641 				str = str[2..$];
6642 				continue;
6643 			}
6644 			// word
6645 			char[16] buf = void; // locased
6646 			auto wep = 0;
6647 			while (str.length) {
6648 				immutable char ch = str.ptr[0];
6649 				if (ch <= ' ' || ch == '-') break;
6650 				str = str[1..$];
6651 				if (wep > buf.length) return false; // too long
6652 						 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
6653 				else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
6654 				else return false; // invalid char
6655 			}
6656 			if (wep == 0) return false; // just in case
6657 			uint bnum;
6658 			enum UpDown { None = -1, Up, Down, Any }
6659 			auto updown = UpDown.None; // 0: up; 1: down
6660 			switch (buf[0..wep]) {
6661 				// left button
6662 				case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb";
6663 				case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb";
6664 				case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb";
6665 				case "lmb": case "left": bnum = 0; break;
6666 				// middle button
6667 				case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb";
6668 				case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb";
6669 				case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb";
6670 				case "mmb": case "middle": bnum = 1; break;
6671 				// right button
6672 				case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb";
6673 				case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb";
6674 				case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb";
6675 				case "rmb": case "right": bnum = 2; break;
6676 				// wheel
6677 				case "wheelup": updown = UpDown.Up; goto case "wheel";
6678 				case "wheeldown": updown = UpDown.Down; goto case "wheel";
6679 				case "wheelany": updown = UpDown.Any; goto case "wheel";
6680 				case "wheel": bnum = 3; break;
6681 				// motion
6682 				case "motion": bnum = 7; break;
6683 				// unknown
6684 				default: return false;
6685 			}
6686 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
6687 			// parse possible "-up" or "-down"
6688 			if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') {
6689 				wep = 0;
6690 				foreach (immutable idx, immutable char ch; str[1..$]) {
6691 					if (ch <= ' ' || ch == '-') break;
6692 					assert(idx == wep); // for now; trick
6693 					if (wep > buf.length) { wep = 0; break; } // too long
6694 							 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower
6695 					else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch;
6696 					else { wep = 0; break; } // invalid char
6697 				}
6698 						 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up;
6699 				else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down;
6700 				else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any;
6701 				// remove parsed part
6702 				if (updown != UpDown.None) str = str[wep+1..$];
6703 			}
6704 			if (updown == UpDown.None) {
6705 				updown = UpDown.Down;
6706 			}
6707 			wasButtons = wasButtons || (bnum <= 2);
6708 			//assert(updown != UpDown.None);
6709 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); }
6710 			// if we have a previous button, it goes to modifiers (unless it is a wheel or motion)
6711 			if (lastButt != lastButt.max) {
6712 				if ((lastButt&0xff) >= 3) return false; // wheel or motion
6713 				if (mods != mods.max) {
6714 					uint butbit = 0;
6715 					final switch (lastButt&0x03) {
6716 						case 0: butbit = ModifierState.leftButtonDown; break;
6717 						case 1: butbit = ModifierState.middleButtonDown; break;
6718 						case 2: butbit = ModifierState.rightButtonDown; break;
6719 					}
6720 					     if (lastButt&Flag.Down) mods |= butbit;
6721 					else if (lastButt&Flag.Up) mods &= ~butbit;
6722 					else if (lastButt&Flag.Any) kmodmask &= ~butbit;
6723 				}
6724 			}
6725 			// remember last button
6726 			lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down);
6727 		}
6728 		// no button -- nothing to do
6729 		if (lastButt == lastButt.max) return false;
6730 		// done parsing, check if something's left
6731 		foreach (immutable char ch; str) if (ch > ' ') return false; // oops
6732 		// remove action button from mask
6733 		if ((lastButt&0xff) < 3) {
6734 			final switch (lastButt&0x03) {
6735 				case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break;
6736 				case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break;
6737 				case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break;
6738 			}
6739 		}
6740 		// special case: "Motion" means "ignore buttons"
6741 		if ((lastButt&0xff) == 7 && !wasButtons) {
6742 			debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("  *: special motion"); }
6743 			kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown);
6744 		}
6745 		uint kmod = event.modifierState&kmodmask;
6746 		debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln("  *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); }
6747 		// check modifier state
6748 		if (mods != mods.max) {
6749 			if (kmod != mods) return false;
6750 		}
6751 		// now check type
6752 		if ((lastButt&0xff) == 7) {
6753 			// motion
6754 			if (event.type != MouseEventType.motion) return false;
6755 		} else if ((lastButt&0xff) == 3) {
6756 			// wheel
6757 			if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp);
6758 			if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown);
6759 			if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp));
6760 			return false;
6761 		} else {
6762 			// buttons
6763 			if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) ||
6764 			    ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased))
6765 			{
6766 				return false;
6767 			}
6768 			// button number
6769 			switch (lastButt&0x03) {
6770 				case 0: if (event.button != MouseButton.left) return false; break;
6771 				case 1: if (event.button != MouseButton.middle) return false; break;
6772 				case 2: if (event.button != MouseButton.right) return false; break;
6773 				default: return false;
6774 			}
6775 		}
6776 		return true;
6777 	}
6778 }
6779 
6780 version(arsd_mevent_strcmp_test) unittest {
6781 	MouseEvent event;
6782 	event.type = MouseEventType.buttonPressed;
6783 	event.button = MouseButton.left;
6784 	event.modifierState = ModifierState.ctrl;
6785 	assert(event == "C-LMB");
6786 	assert(event != "C-LMBUP");
6787 	assert(event != "C-LMB-UP");
6788 	assert(event != "C-S-LMB");
6789 	assert(event == "*-LMB");
6790 	assert(event != "*-LMB-UP");
6791 
6792 	event.type = MouseEventType.buttonReleased;
6793 	assert(event != "C-LMB");
6794 	assert(event == "C-LMBUP");
6795 	assert(event == "C-LMB-UP");
6796 	assert(event != "C-S-LMB");
6797 	assert(event != "*-LMB");
6798 	assert(event == "*-LMB-UP");
6799 
6800 	event.button = MouseButton.right;
6801 	event.modifierState |= ModifierState.shift;
6802 	event.type = MouseEventType.buttonPressed;
6803 	assert(event != "C-LMB");
6804 	assert(event != "C-LMBUP");
6805 	assert(event != "C-LMB-UP");
6806 	assert(event != "C-S-LMB");
6807 	assert(event != "*-LMB");
6808 	assert(event != "*-LMB-UP");
6809 
6810 	assert(event != "C-RMB");
6811 	assert(event != "C-RMBUP");
6812 	assert(event != "C-RMB-UP");
6813 	assert(event == "C-S-RMB");
6814 	assert(event == "*-RMB");
6815 	assert(event != "*-RMB-UP");
6816 }
6817 
6818 /// This gives a few more options to drawing lines and such
6819 struct Pen {
6820 	Color color; /// the foreground color
6821 	int width = 1; /// width of the line
6822 	Style style; /// See [Style]
6823 /+
6824 // From X.h
6825 
6826 #define LineSolid		0
6827 #define LineOnOffDash		1
6828 #define LineDoubleDash		2
6829        LineDou-        The full path of the line is drawn, but the
6830        bleDash         even dashes are filled differently from the
6831                        odd dashes (see fill-style) with CapButt
6832                        style used where even and odd dashes meet.
6833 
6834 
6835 
6836 /* capStyle */
6837 
6838 #define CapNotLast		0
6839 #define CapButt			1
6840 #define CapRound		2
6841 #define CapProjecting		3
6842 
6843 /* joinStyle */
6844 
6845 #define JoinMiter		0
6846 #define JoinRound		1
6847 #define JoinBevel		2
6848 
6849 /* fillStyle */
6850 
6851 #define FillSolid		0
6852 #define FillTiled		1
6853 #define FillStippled		2
6854 #define FillOpaqueStippled	3
6855 
6856 
6857 +/
6858 	/// Style of lines drawn
6859 	enum Style {
6860 		Solid, /// a solid line
6861 		Dashed, /// a dashed line
6862 		Dotted, /// a dotted line
6863 	}
6864 }
6865 
6866 
6867 /++
6868 	Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.
6869 
6870 
6871 	On Windows, this means a device-independent bitmap. On X11, it is an XImage.
6872 
6873 	$(NOTE If you are writing platform-aware code and need to know low-level details, uou may check `if(Image.impl.xshmAvailable)` to see if MIT-SHM is used on X11 targets to draw `Image`s and `Sprite`s. Use `static if(UsingSimpledisplayX11)` to determine if you are compiling for an X11 target.)
6874 
6875 	Drawing an image to screen is not necessarily fast, but applying algorithms to draw to the image itself should be fast. An `Image` is also the first step in loading and displaying images loaded from files.
6876 
6877 	If you intend to draw an image to screen several times, you will want to convert it into a [Sprite].
6878 
6879 	$(PITFALL `Image` may represent a scarce, shared resource that persists across process termination, and should be disposed of properly. On X11, it uses the MIT-SHM extension, if available, which uses shared memory handles with the X server, which is a long-lived process that holds onto them after your program terminates if you don't free it.
6880 
6881 	It is possible for your user's system to run out of these handles over time, forcing them to clean it up with extraordinary measures - their GUI is liable to stop working!
6882 
6883 	Be sure these are cleaned up properly. simpledisplay will do its best to do the right thing, including cleaning them up in garbage collection sweeps (one of which is run at most normal program terminations) and catching some deadly signals. It will almost always do the right thing. But, this is no substitute for you managing the resource properly yourself. (And try not to segfault, as recovery from them is alway dicey!)
6884 
6885 	Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope:
6886 
6887 	---
6888 		auto image = new Image(256, 256);
6889 		scope(exit) destroy(image);
6890 	---
6891 
6892 	As long as you don't hold on to it outside the scope.
6893 
6894 	I might change it to be an owned pointer at some point in the future.
6895 
6896 	)
6897 
6898 	Drawing pixels on the image may be simple, using the `opIndexAssign` function, but
6899 	you can also often get a fair amount of speedup by getting the raw data format and
6900 	writing some custom code.
6901 
6902 	FIXME INSERT EXAMPLES HERE
6903 
6904 
6905 +/
6906 final class Image {
6907 	///
6908 	this(int width, int height, bool forcexshm=false) {
6909 		this.width = width;
6910 		this.height = height;
6911 
6912 		impl.createImage(width, height, forcexshm);
6913 	}
6914 
6915 	///
6916 	this(Size size, bool forcexshm=false) {
6917 		this(size.width, size.height, forcexshm);
6918 	}
6919 
6920 	private bool suppressDestruction;
6921 
6922 	version(X11)
6923 	this(XImage* handle) {
6924 		this.handle = handle;
6925 		this.rawData = cast(ubyte*) handle.data;
6926 		this.width = handle.width;
6927 		this.height = handle.height;
6928 		suppressDestruction = true;
6929 	}
6930 
6931 	~this() {
6932 		if(suppressDestruction) return;
6933 		impl.dispose();
6934 	}
6935 
6936 	// these numbers are used for working with rawData itself, skipping putPixel and getPixel
6937 	/// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value.
6938 	pure const @system nothrow {
6939 		/*
6940 			To use these to draw a blue rectangle with size WxH at position X,Y...
6941 
6942 			// make certain that it will fit before we proceed
6943 			enforce(X + W <= img.width && Y + H <= img.height); // you could also adjust the size to clip it, but be sure not to run off since this here will do raw pointers with no bounds checks!
6944 
6945 			// gather all the values you'll need up front. These can be kept until the image changes size if you want
6946 			// (though calculating them isn't really that expensive).
6947 			auto nextLineAdjustment = img.adjustmentForNextLine();
6948 			auto offR = img.redByteOffset();
6949 			auto offB = img.blueByteOffset();
6950 			auto offG = img.greenByteOffset();
6951 			auto bpp = img.bytesPerPixel();
6952 
6953 			auto data = img.getDataPointer();
6954 
6955 			// figure out the starting byte offset
6956 			auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X;
6957 
6958 			auto startOfLine = data + offset; // get our pointer lined up on the first pixel
6959 
6960 			// and now our drawing loop for the rectangle
6961 			foreach(y; 0 .. H) {
6962 				auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable
6963 				foreach(x; 0 .. W) {
6964 					// write our color
6965 					data[offR] = 0;
6966 					data[offG] = 0;
6967 					data[offB] = 255;
6968 
6969 					data += bpp; // moving to the next pixel is just an addition...
6970 				}
6971 				startOfLine += nextLineAdjustment;
6972 			}
6973 
6974 
6975 			As you can see, the loop itself was very simple thanks to the calculations being moved outside.
6976 
6977 			FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets
6978 			can be made into a bitmask or something so we can write them as *uint...
6979 		*/
6980 
6981 		///
6982 		int offsetForTopLeftPixel() {
6983 			version(X11) {
6984 				return 0;
6985 			} else version(Windows) {
6986 				return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1);
6987 			} else version(OSXCocoa) {
6988 				return 0 ; //throw new NotYetImplementedException();
6989 			} else static assert(0, "fill in this info for other OSes");
6990 		}
6991 
6992 		///
6993 		int offsetForPixel(int x, int y) {
6994 			version(X11) {
6995 				auto offset = (y * width + x) * 4;
6996 				return offset;
6997 			} else version(Windows) {
6998 				auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
6999 				// remember, bmps are upside down
7000 				auto offset = itemsPerLine * (height - y - 1) + x * 3;
7001 				return offset;
7002 			} else version(OSXCocoa) {
7003 				return 0 ; //throw new NotYetImplementedException();
7004 			} else static assert(0, "fill in this info for other OSes");
7005 		}
7006 
7007 		///
7008 		int adjustmentForNextLine() {
7009 			version(X11) {
7010 				return width * 4;
7011 			} else version(Windows) {
7012 				// windows bmps are upside down, so the adjustment is actually negative
7013 				return -((cast(int) width * 3 + 3) / 4) * 4;
7014 			} else version(OSXCocoa) {
7015 				return 0 ; //throw new NotYetImplementedException();
7016 			} else static assert(0, "fill in this info for other OSes");
7017 		}
7018 
7019 		/// once you have the position of a pixel, use these to get to the proper color
7020 		int redByteOffset() {
7021 			version(X11) {
7022 				return 2;
7023 			} else version(Windows) {
7024 				return 2;
7025 			} else version(OSXCocoa) {
7026 				return 0 ; //throw new NotYetImplementedException();
7027 			} else static assert(0, "fill in this info for other OSes");
7028 		}
7029 
7030 		///
7031 		int greenByteOffset() {
7032 			version(X11) {
7033 				return 1;
7034 			} else version(Windows) {
7035 				return 1;
7036 			} else version(OSXCocoa) {
7037 				return 0 ; //throw new NotYetImplementedException();
7038 			} else static assert(0, "fill in this info for other OSes");
7039 		}
7040 
7041 		///
7042 		int blueByteOffset() {
7043 			version(X11) {
7044 				return 0;
7045 			} else version(Windows) {
7046 				return 0;
7047 			} else version(OSXCocoa) {
7048 				return 0 ; //throw new NotYetImplementedException();
7049 			} else static assert(0, "fill in this info for other OSes");
7050 		}
7051 	}
7052 
7053 	///
7054 	final void putPixel(int x, int y, Color c) {
7055 		if(x < 0 || x >= width)
7056 			return;
7057 		if(y < 0 || y >= height)
7058 			return;
7059 
7060 		impl.setPixel(x, y, c);
7061 	}
7062 
7063 	///
7064 	final Color getPixel(int x, int y) {
7065 		if(x < 0 || x >= width)
7066 			return Color.transparent;
7067 		if(y < 0 || y >= height)
7068 			return Color.transparent;
7069 
7070 		version(OSXCocoa) throw new NotYetImplementedException(); else
7071 		return impl.getPixel(x, y);
7072 	}
7073 
7074 	///
7075 	final void opIndexAssign(Color c, int x, int y) {
7076 		putPixel(x, y, c);
7077 	}
7078 
7079 	///
7080 	TrueColorImage toTrueColorImage() {
7081 		auto tci = new TrueColorImage(width, height);
7082 		convertToRgbaBytes(tci.imageData.bytes);
7083 		return tci;
7084 	}
7085 
7086 	///
7087 	static Image fromMemoryImage(MemoryImage i) {
7088 		auto tci = i.getAsTrueColorImage();
7089 		auto img = new Image(tci.width, tci.height);
7090 		img.setRgbaBytes(tci.imageData.bytes);
7091 		return img;
7092 	}
7093 
7094 	/// this is here for interop with arsd.image. where can be a TrueColorImage's data member
7095 	/// if you pass in a buffer, it will put it right there. length must be width*height*4 already
7096 	/// if you pass null, it will allocate a new one.
7097 	ubyte[] getRgbaBytes(ubyte[] where = null) {
7098 		if(where is null)
7099 			where = new ubyte[this.width*this.height*4];
7100 		convertToRgbaBytes(where);
7101 		return where;
7102 	}
7103 
7104 	/// this is here for interop with arsd.image. from can be a TrueColorImage's data member
7105 	void setRgbaBytes(in ubyte[] from ) {
7106 		assert(from.length == this.width * this.height * 4);
7107 		setFromRgbaBytes(from);
7108 	}
7109 
7110 	// FIXME: make properly cross platform by getting rgba right
7111 
7112 	/// warning: this is not portable across platforms because the data format can change
7113 	ubyte* getDataPointer() {
7114 		return impl.rawData;
7115 	}
7116 
7117 	/// for use with getDataPointer
7118 	final int bytesPerLine() const pure @safe nothrow {
7119 		version(Windows)
7120 			return ((cast(int) width * 3 + 3) / 4) * 4;
7121 		else version(X11)
7122 			return 4 * width;
7123 		else version(OSXCocoa)
7124 			return 4 * width;
7125 		else static assert(0);
7126 	}
7127 
7128 	/// for use with getDataPointer
7129 	final int bytesPerPixel() const pure @safe nothrow {
7130 		version(Windows)
7131 			return 3;
7132 		else version(X11)
7133 			return 4;
7134 		else version(OSXCocoa)
7135 			return 4;
7136 		else static assert(0);
7137 	}
7138 
7139 	///
7140 	immutable int width;
7141 
7142 	///
7143 	immutable int height;
7144     //private:
7145 	mixin NativeImageImplementation!() impl;
7146 }
7147 
7148 /// A convenience function to pop up a window displaying the image.
7149 /// If you pass a win, it will draw the image in it. Otherwise, it will
7150 /// create a window with the size of the image and run its event loop, closing
7151 /// when a key is pressed.
7152 void displayImage(Image image, SimpleWindow win = null) {
7153 	if(win is null) {
7154 		win = new SimpleWindow(image);
7155 		{
7156 			auto p = win.draw;
7157 			p.drawImage(Point(0, 0), image);
7158 		}
7159 		win.eventLoop(0,
7160 			(KeyEvent ev) {
7161 				if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close();
7162 			} );
7163 	} else {
7164 		win.image = image;
7165 	}
7166 }
7167 
7168 enum FontWeight : int {
7169 	dontcare = 0,
7170 	thin = 100,
7171 	extralight = 200,
7172 	light = 300,
7173 	regular = 400,
7174 	medium = 500,
7175 	semibold = 600,
7176 	bold = 700,
7177 	extrabold = 800,
7178 	heavy = 900
7179 }
7180 
7181 /++
7182 	Represents a font loaded off the operating system or the X server.
7183 
7184 
7185 	While the api here is unified cross platform, the fonts are not necessarily
7186 	available, even across machines of the same platform, so be sure to always check
7187 	for null (using [isNull]) and have a fallback plan.
7188 
7189 	When you have a font you like, use [ScreenPainter.setFont] to load it for drawing.
7190 
7191 	Worst case, a null font will automatically fall back to the default font loaded
7192 	for your system.
7193 +/
7194 class OperatingSystemFont {
7195 	// FIXME: when the X Connection is lost, these need to be invalidated!
7196 	// that means I need to store the original stuff again to reconstruct it too.
7197 
7198 	version(X11) {
7199 		XFontStruct* font;
7200 		XFontSet fontset;
7201 
7202 		version(with_xft) {
7203 			XftFont* xftFont;
7204 			bool isXft;
7205 		}
7206 	} else version(Windows) {
7207 		HFONT font;
7208 		int width_;
7209 		int height_;
7210 	} else version(OSXCocoa) {
7211 		// FIXME
7212 	} else static assert(0);
7213 
7214 	/++
7215 		Constructs the class and immediately calls [load].
7216 	+/
7217 	this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
7218 		load(name, size, weight, italic);
7219 	}
7220 
7221 	/++
7222 		Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object.
7223 
7224 		You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you.
7225 
7226 		History:
7227 			Added January 24, 2021.
7228 	+/
7229 	this() {
7230 		// this space intentionally left blank
7231 	}
7232 
7233 	/++
7234 		Loads specifically with the Xft library - a freetype font from a fontconfig string.
7235 
7236 		History:
7237 			Added November 13, 2020.
7238 	+/
7239 	version(with_xft)
7240 	bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
7241 		unload();
7242 
7243 		if(!XftLibrary.attempted) {
7244 			XftLibrary.loadDynamicLibrary();
7245 		}
7246 
7247 		if(!XftLibrary.loadSuccessful)
7248 			return false;
7249 
7250 		auto display = XDisplayConnection.get;
7251 
7252 		char[256] nameBuffer = void;
7253 		int nbp = 0;
7254 
7255 		void add(in char[] a) {
7256 			nameBuffer[nbp .. nbp + a.length] = a[];
7257 			nbp += a.length;
7258 		}
7259 		add(name);
7260 
7261 		if(size) {
7262 			add(":size=");
7263 			add(toInternal!string(size));
7264 		}
7265 		if(weight != FontWeight.dontcare) {
7266 			add(":weight=");
7267 			add(weightToString(weight));
7268 		}
7269 		if(italic)
7270 			add(":slant=100");
7271 
7272 		nameBuffer[nbp] = 0;
7273 
7274 		this.xftFont = XftFontOpenName(
7275 			display,
7276 			DefaultScreen(display),
7277 			nameBuffer.ptr
7278 		);
7279 
7280 		this.isXft = true;
7281 
7282 		if(xftFont !is null) {
7283 			isMonospace_ = stringWidth("x") == stringWidth("M");
7284 			ascent_ = xftFont.ascent;
7285 			descent_ = xftFont.descent;
7286 		}
7287 
7288 		return !isNull();
7289 	}
7290 
7291 	// see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352
7292 
7293 	private string weightToString(FontWeight weight) {
7294 		with(FontWeight)
7295 		final switch(weight) {
7296 			case dontcare: return "*";
7297 			case thin: return "extralight";
7298 			case extralight: return "extralight";
7299 			case light: return "light";
7300 			case regular: return "regular";
7301 			case medium: return "medium";
7302 			case semibold: return "demibold";
7303 			case bold: return "bold";
7304 			case extrabold: return "demibold";
7305 			case heavy: return "black";
7306 		}
7307 	}
7308 
7309 	/++
7310 		Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance.
7311 
7312 		History:
7313 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
7314 	+/
7315 	version(X11)
7316 	bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
7317 		unload();
7318 
7319 		string xfontstr;
7320 
7321 		if(name.length > 3 && name[0 .. 3] == "-*-") {
7322 			// this is kinda a disgusting hack but if the user sends an exact
7323 			// string I'd like to honor it...
7324 			xfontstr = name;
7325 		} else {
7326 			string weightstr = weightToString(weight);
7327 			string sizestr;
7328 			if(size == 0)
7329 				sizestr = "*";
7330 			else
7331 				sizestr = toInternal!string(size);
7332 			xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0";
7333 		}
7334 
7335 		//import std.stdio; writeln(xfontstr);
7336 
7337 		auto display = XDisplayConnection.get;
7338 
7339 		font = XLoadQueryFont(display, xfontstr.ptr);
7340 		if(font is null)
7341 			return false;
7342 
7343 		char** lol;
7344 		int lol2;
7345 		char* lol3;
7346 		fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
7347 
7348 		prepareFontInfo();
7349 
7350 		return !isNull();
7351 	}
7352 
7353 	version(X11)
7354 	private void prepareFontInfo() {
7355 		if(font !is null) {
7356 			isMonospace_ = stringWidth("l") == stringWidth("M");
7357 			ascent_ = font.max_bounds.ascent;
7358 			descent_ = font.max_bounds.descent;
7359 		}
7360 	}
7361 
7362 	/++
7363 		Loads a Windows font. You probably want to use [load] instead to be more generic.
7364 
7365 		History:
7366 			Added November 13, 2020. Before then, this code was integrated in the [load] function.
7367 	+/
7368 	version(Windows)
7369 	bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) {
7370 		unload();
7371 
7372 		WCharzBuffer buffer = WCharzBuffer(name);
7373 		font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr);
7374 
7375 		prepareFontInfo(hdc);
7376 
7377 		return !isNull();
7378 	}
7379 
7380 	version(Windows)
7381 	void prepareFontInfo(HDC hdc = null) {
7382 		if(font is null)
7383 			return;
7384 
7385 		TEXTMETRIC tm;
7386 		auto dc = hdc ? hdc : GetDC(null);
7387 		auto orig = SelectObject(dc, font);
7388 		GetTextMetrics(dc, &tm);
7389 		SelectObject(dc, orig);
7390 		if(hdc is null)
7391 			ReleaseDC(null, dc);
7392 
7393 		width_ = tm.tmAveCharWidth;
7394 		height_ = tm.tmHeight;
7395 		ascent_ = tm.tmAscent;
7396 		descent_ = tm.tmDescent;
7397 		// If this bit is set the font is a variable pitch font. If this bit is clear the font is a fixed pitch font. Note very carefully that those meanings are the opposite of what the constant name implies.
7398 		isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
7399 	}
7400 
7401 
7402 	/++
7403 		`name` is a font name, but it can also be a more complicated string parsed in an OS-specific way.
7404 
7405 		On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise,
7406 		it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX].
7407 
7408 		On Windows, it forwards directly to [loadWin32].
7409 
7410 		Params:
7411 			name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences.
7412 			size = font size. This may be interpreted differently by different systems and different fonts. Size 0 means load a default, which may not exist and cause [isNull] to become true.
7413 			weight = approximate boldness, results may vary.
7414 			italic = try to get a slanted version of the given font.
7415 
7416 		History:
7417 			Xft support was added on November 13, 2020. It would only load core fonts. Xft inclusion changed font lookup and interpretation of the `size` parameter, requiring a major version bump. This caused release v9.0.
7418 	+/
7419 	bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) {
7420 		version(X11) {
7421 			version(with_xft) {
7422 				if(name.length > 5 && name[0 .. 5] == "core:") {
7423 					goto core;
7424 				}
7425 
7426 				if(loadXft(name, size, weight, italic))
7427 					return true;
7428 				// if xft fails, fallback to core to avoid breaking
7429 				// code that already depended on this.
7430 			}
7431 
7432 			core:
7433 
7434 			if(name.length > 5 && name[0 .. 5] == "core:") {
7435 				name = name[5 .. $];
7436 			}
7437 
7438 			return loadCoreX(name, size, weight, italic);
7439 		} else version(Windows) {
7440 			return loadWin32(name, size, weight, italic);
7441 		} else version(OSXCocoa) {
7442 			// FIXME
7443 			return false;
7444 		} else static assert(0);
7445 	}
7446 
7447 	///
7448 	void unload() {
7449 		if(isNull())
7450 			return;
7451 
7452 		version(X11) {
7453 			auto display = XDisplayConnection.display;
7454 
7455 			if(display is null)
7456 				return;
7457 
7458 			version(with_xft) {
7459 				if(isXft) {
7460 					if(xftFont)
7461 						XftFontClose(display, xftFont);
7462 					isXft = false;
7463 					xftFont = null;
7464 					return;
7465 				}
7466 			}
7467 
7468 			if(font)
7469 				XFreeFont(display, font);
7470 			if(fontset)
7471 				XFreeFontSet(display, fontset);
7472 
7473 			font = null;
7474 			fontset = null;
7475 		} else version(Windows) {
7476 			DeleteObject(font);
7477 			font = null;
7478 		} else version(OSXCocoa) {
7479 			// FIXME
7480 		} else static assert(0);
7481 	}
7482 
7483 	private bool isMonospace_;
7484 
7485 	/++
7486 		History:
7487 			Added January 16, 2021
7488 	+/
7489 	bool isMonospace() {
7490 		return isMonospace_;
7491 	}
7492 
7493 	/++
7494 		Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character.
7495 
7496 		History:
7497 			Added March 26, 2020
7498 			Documented January 16, 2021
7499 	+/
7500 	int averageWidth() {
7501 		version(X11) {
7502 			return stringWidth("x");
7503 		} else version(Windows)
7504 			return width_;
7505 		else assert(0);
7506 	}
7507 
7508 	/++
7509 		Returns the width of the string as drawn on the specified window, or the default screen if the window is null.
7510 
7511 		History:
7512 			Added January 16, 2021
7513 	+/
7514 	int stringWidth(scope const(char)[] s, SimpleWindow window = null) {
7515 	// FIXME: what about tab?
7516 		if(isNull)
7517 			return 0;
7518 
7519 		version(X11) {
7520 			version(with_xft)
7521 				if(isXft && xftFont !is null) {
7522 					//return xftFont.max_advance_width;
7523 					XGlyphInfo extents;
7524 					XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents);
7525 					//import std.stdio; writeln(extents);
7526 					return extents.xOff;
7527 				}
7528 			if(font is null)
7529 				return 0;
7530 			else if(fontset) {
7531 				XRectangle rect;
7532 				Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect);
7533 
7534 				return rect.width;
7535 			} else {
7536 				return XTextWidth(font, s.ptr, cast(int) s.length);
7537 			}
7538 		} else version(Windows) {
7539 			WCharzBuffer buffer = WCharzBuffer(s);
7540 
7541 			return stringWidth(buffer.slice, window);
7542 		}
7543 		else assert(0);
7544 	}
7545 
7546 	version(Windows)
7547 	/// ditto
7548 	int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) {
7549 		if(isNull)
7550 			return 0;
7551 		version(Windows) {
7552 			SIZE size;
7553 
7554 			prepareContext(window);
7555 			scope(exit) releaseContext();
7556 
7557 			GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size);
7558 
7559 			return size.cx;
7560 		} else {
7561 			// std.conv can do this easily but it is slow to import and i don't think it is worth it
7562 			static assert(0, "not implemented yet");
7563 			//return stringWidth(s, window);
7564 		}
7565 	}
7566 
7567 	private {
7568 		int prepRefcount;
7569 
7570 		version(Windows) {
7571 			HDC dc;
7572 			HANDLE orig;
7573 			HWND hwnd;
7574 		}
7575 	}
7576 	/++
7577 		[stringWidth] can be slow. This helps speed it up if you are doing a lot of calculations. Just prepareContext when you start this work and releaseContext when you are done. Important to release before too long though as it can be a scarce system resource.
7578 
7579 		History:
7580 			Added January 23, 2021
7581 	+/
7582 	void prepareContext(SimpleWindow window = null) {
7583 		prepRefcount++;
7584 		if(prepRefcount == 1) {
7585 			version(Windows) {
7586 				hwnd = window is null ? null : window.impl.hwnd;
7587 				dc = GetDC(hwnd);
7588 				orig = SelectObject(dc, font);
7589 			}
7590 		}
7591 	}
7592 	/// ditto
7593 	void releaseContext() {
7594 		prepRefcount--;
7595 		if(prepRefcount == 0) {
7596 			version(Windows) {
7597 				SelectObject(dc, orig);
7598 				ReleaseDC(hwnd, dc);
7599 				hwnd = null;
7600 				dc = null;
7601 				orig = null;
7602 			}
7603 		}
7604 	}
7605 
7606 	/+
7607 		FIXME: I think I need advance and kerning pair
7608 
7609 		int advance(dchar from, dchar to) { } // use dchar.init for first item in string
7610 	+/
7611 
7612 	/++
7613 		Returns the height of the font.
7614 
7615 		History:
7616 			Added March 26, 2020
7617 			Documented January 16, 2021
7618 	+/
7619 	int height() {
7620 		version(X11) {
7621 			version(with_xft)
7622 				if(isXft && xftFont !is null) {
7623 					return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel
7624 				}
7625 			if(font is null)
7626 				return 0;
7627 			return font.max_bounds.ascent + font.max_bounds.descent;
7628 		} else version(Windows)
7629 			return height_;
7630 		else assert(0);
7631 	}
7632 
7633 	private int ascent_;
7634 	private int descent_;
7635 
7636 	/++
7637 		Max ascent above the baseline.
7638 
7639 		History:
7640 			Added January 22, 2021
7641 	+/
7642 	int ascent() {
7643 		return ascent_;
7644 	}
7645 
7646 	/++
7647 		Max descent below the baseline.
7648 
7649 		History:
7650 			Added January 22, 2021
7651 	+/
7652 	int descent() {
7653 		return descent_;
7654 	}
7655 
7656 	/++
7657 		Loads the default font used by [ScreenPainter] if none others are loaded.
7658 
7659 		Returns:
7660 			This method mutates the `this` object, but then returns `this` for
7661 			easy chaining like:
7662 
7663 			---
7664 			auto font = foo.isNull ? foo : foo.loadDefault
7665 			---
7666 
7667 		History:
7668 			Added previously, but left unimplemented until January 24, 2021.
7669 	+/
7670 	OperatingSystemFont loadDefault() {
7671 		unload();
7672 
7673 		version(X11) {
7674 			// another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
7675 			// but meh since sdpy does its own thing, this should be ok too
7676 
7677 			ScreenPainterImplementation.ensureDefaultFontLoaded();
7678 			this.font = ScreenPainterImplementation.defaultfont;
7679 			this.fontset = ScreenPainterImplementation.defaultfontset;
7680 
7681 			prepareFontInfo();
7682 		} else version(Windows) {
7683 			ScreenPainterImplementation.ensureDefaultFontLoaded();
7684 			this.font = ScreenPainterImplementation.defaultGuiFont;
7685 
7686 			prepareFontInfo();
7687 		} else throw new NotYetImplementedException();
7688 
7689 		return this;
7690 	}
7691 
7692 	///
7693 	bool isNull() {
7694 		version(OSXCocoa) throw new NotYetImplementedException(); else {
7695 			version(with_xft)
7696 				if(isXft)
7697 					return xftFont is null;
7698 			return font is null;
7699 		}
7700 	}
7701 
7702 	/* Metrics */
7703 	/+
7704 		GetABCWidth
7705 		GetKerningPairs
7706 
7707 		if I do it right, I can size it all here, and match
7708 		what happens when I draw the full string with the OS functions.
7709 
7710 		subclasses might do the same thing while getting the glyphs on images
7711 	struct GlyphInfo {
7712 		int glyph;
7713 
7714 		size_t stringIdxStart;
7715 		size_t stringIdxEnd;
7716 
7717 		Rectangle boundingBox;
7718 	}
7719 	GlyphInfo[] getCharBoxes() {
7720 		// XftTextExtentsUtf8
7721 		return null;
7722 
7723 	}
7724 	+/
7725 
7726 	~this() {
7727 		unload();
7728 	}
7729 }
7730 
7731 /**
7732 	The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
7733 	than constructing it directly. Then, it is reference counted so you can pass it
7734 	at around and when the last ref goes out of scope, the buffered drawing activities
7735 	are all carried out.
7736 
7737 
7738 	Most functions use the outlineColor instead of taking a color themselves.
7739 	ScreenPainter is reference counted and draws its buffer to the screen when its
7740 	final reference goes out of scope.
7741 */
7742 struct ScreenPainter {
7743 	CapableOfBeingDrawnUpon window;
7744 	this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle) {
7745 		this.window = window;
7746 		if(window.closed)
7747 			return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
7748 		currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
7749 		if(window.activeScreenPainter !is null) {
7750 			impl = window.activeScreenPainter;
7751 			if(impl.referenceCount == 0) {
7752 				impl.window = window;
7753 				impl.create(handle);
7754 			}
7755 			impl.referenceCount++;
7756 		//	writeln("refcount ++ ", impl.referenceCount);
7757 		} else {
7758 			impl = new ScreenPainterImplementation;
7759 			impl.window = window;
7760 			impl.create(handle);
7761 			impl.referenceCount = 1;
7762 			window.activeScreenPainter = impl;
7763 		//	writeln("constructed");
7764 		}
7765 
7766 		copyActiveOriginals();
7767 	}
7768 
7769 	private Pen originalPen;
7770 	private Color originalFillColor;
7771 	private arsd.color.Rectangle originalClipRectangle;
7772 	void copyActiveOriginals() {
7773 		if(impl is null) return;
7774 		originalPen = impl._activePen;
7775 		originalFillColor = impl._fillColor;
7776 		originalClipRectangle = impl._clipRectangle;
7777 	}
7778 
7779 	~this() {
7780 		if(impl is null) return;
7781 		impl.referenceCount--;
7782 		//writeln("refcount -- ", impl.referenceCount);
7783 		if(impl.referenceCount == 0) {
7784 			//writeln("destructed");
7785 			impl.dispose();
7786 			*window.activeScreenPainter = ScreenPainterImplementation.init;
7787 			//import std.stdio; writeln("paint finished");
7788 		} else {
7789 			// there is still an active reference, reset stuff so the
7790 			// next user doesn't get weirdness via the reference
7791 			this.rasterOp = RasterOp.normal;
7792 			pen = originalPen;
7793 			fillColor = originalFillColor;
7794 			impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height);
7795 		}
7796 	}
7797 
7798 	this(this) {
7799 		if(impl is null) return;
7800 		impl.referenceCount++;
7801 		//writeln("refcount ++ ", impl.referenceCount);
7802 
7803 		copyActiveOriginals();
7804 	}
7805 
7806 	private int _originX;
7807 	private int _originY;
7808 	@property int originX() { return _originX; }
7809 	@property int originY() { return _originY; }
7810 	@property int originX(int a) {
7811 		//currentClipRectangle.left += a - _originX;
7812 		//currentClipRectangle.right += a - _originX;
7813 		_originX = a;
7814 		return _originX;
7815 	}
7816 	@property int originY(int a) {
7817 		//currentClipRectangle.top += a - _originY;
7818 		//currentClipRectangle.bottom += a - _originY;
7819 		_originY = a;
7820 		return _originY;
7821 	}
7822 	arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations
7823 	private void transform(ref Point p) {
7824 		if(impl is null) return;
7825 		p.x += _originX;
7826 		p.y += _originY;
7827 	}
7828 
7829 	// this needs to be checked BEFORE the originX/Y transformation
7830 	private bool isClipped(Point p) {
7831 		return !currentClipRectangle.contains(p);
7832 	}
7833 	private bool isClipped(Point p, int width, int height) {
7834 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1)));
7835 	}
7836 	private bool isClipped(Point p, Size s) {
7837 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1)));
7838 	}
7839 	private bool isClipped(Point p, Point p2) {
7840 		// need to ensure the end points are actually included inside, so the +1 does that
7841 		return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1)));
7842 	}
7843 
7844 
7845 	/// Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
7846 	void setClipRectangle(Point pt, int width, int height) {
7847 		if(impl is null) return;
7848 		if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
7849 			return; // no need to do anything
7850 		currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
7851 		transform(pt);
7852 
7853 		impl.setClipRectangle(pt.x, pt.y, width, height);
7854 	}
7855 
7856 	/// ditto
7857 	void setClipRectangle(arsd.color.Rectangle rect) {
7858 		if(impl is null) return;
7859 		setClipRectangle(rect.upperLeft, rect.width, rect.height);
7860 	}
7861 
7862 	///
7863 	void setFont(OperatingSystemFont font) {
7864 		if(impl is null) return;
7865 		impl.setFont(font);
7866 	}
7867 
7868 	///
7869 	int fontHeight() {
7870 		if(impl is null) return 0;
7871 		return impl.fontHeight();
7872 	}
7873 
7874 	private Pen activePen;
7875 
7876 	///
7877 	@property void pen(Pen p) {
7878 		if(impl is null) return;
7879 		activePen = p;
7880 		impl.pen(p);
7881 	}
7882 
7883 	///
7884 	@scriptable
7885 	@property void outlineColor(Color c) {
7886 		if(impl is null) return;
7887 		if(activePen.color == c)
7888 			return;
7889 		activePen.color = c;
7890 		impl.pen(activePen);
7891 	}
7892 
7893 	///
7894 	@scriptable
7895 	@property void fillColor(Color c) {
7896 		if(impl is null) return;
7897 		impl.fillColor(c);
7898 	}
7899 
7900 	///
7901 	@property void rasterOp(RasterOp op) {
7902 		if(impl is null) return;
7903 		impl.rasterOp(op);
7904 	}
7905 
7906 
7907 	void updateDisplay() {
7908 		// FIXME this should do what the dtor does
7909 	}
7910 
7911 	/// Scrolls the contents in the bounding rectangle by dx, dy. Positive dx means scroll left (make space available at the right), positive dy means scroll up (make space available at the bottom)
7912 	void scrollArea(Point upperLeft, int width, int height, int dx, int dy) {
7913 		if(impl is null) return;
7914 		if(isClipped(upperLeft, width, height)) return;
7915 		transform(upperLeft);
7916 		version(Windows) {
7917 			// http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx
7918 			RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height);
7919 			RECT clip = scroll;
7920 			RECT uncovered;
7921 			HRGN hrgn;
7922 			if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered))
7923 				throw new Exception("ScrollDC");
7924 
7925 		} else version(X11) {
7926 			// FIXME: clip stuff outside this rectangle
7927 			XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy);
7928 		} else version(OSXCocoa) {
7929 			throw new NotYetImplementedException();
7930 		} else static assert(0);
7931 	}
7932 
7933 	///
7934 	void clear(Color color = Color.white()) {
7935 		if(impl is null) return;
7936 		fillColor = color;
7937 		outlineColor = color;
7938 		drawRectangle(Point(0, 0), window.width, window.height);
7939 	}
7940 
7941 	/++
7942 		Draws a pixmap (represented by the [Sprite] class) on the drawable.
7943 
7944 		Params:
7945 			upperLeft = point on the window where the upper left corner of the image will be drawn
7946 			imageUpperLeft = point on the image to start the slice to draw
7947 			sliceSize = size of the slice of the image to draw on the window. If width or height is 0, it uses the entire width or height of the image.
7948 		History:
7949 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
7950 	+/
7951 	version(OSXCocoa) {} else // NotYetImplementedException
7952 	void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
7953 		if(impl is null) return;
7954 		if(isClipped(upperLeft, s.width, s.height)) return;
7955 		transform(upperLeft);
7956 		impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height);
7957 	}
7958 
7959 	///
7960 	void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) {
7961 		if(impl is null) return;
7962 		//if(isClipped(upperLeft, w, h)) return; // FIXME
7963 		transform(upperLeft);
7964 		if(w == 0 || w > i.width)
7965 			w = i.width;
7966 		if(h == 0 || h > i.height)
7967 			h = i.height;
7968 		if(upperLeftOfImage.x < 0)
7969 			upperLeftOfImage.x = 0;
7970 		if(upperLeftOfImage.y < 0)
7971 			upperLeftOfImage.y = 0;
7972 
7973 		impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h);
7974 	}
7975 
7976 	///
7977 	Size textSize(in char[] text) {
7978 		if(impl is null) return Size(0, 0);
7979 		return impl.textSize(text);
7980 	}
7981 
7982 	/++
7983 		Draws a string in the window with the set font (see [setFont] to change it).
7984 
7985 		Params:
7986 			upperLeft = the upper left point of the bounding box of the text
7987 			text = the string to draw
7988 			lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound.
7989 			alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags
7990 	+/
7991 	@scriptable
7992 	void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) {
7993 		if(impl is null) return;
7994 		if(lowerRight.x != 0 || lowerRight.y != 0) {
7995 			if(isClipped(upperLeft, lowerRight)) return;
7996 			transform(lowerRight);
7997 		} else {
7998 			if(isClipped(upperLeft, textSize(text))) return;
7999 		}
8000 		transform(upperLeft);
8001 		impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment);
8002 	}
8003 
8004 	/++
8005 		Draws text using a custom font.
8006 
8007 		This is still MAJOR work in progress.
8008 
8009 		Creating a [DrawableFont] can be tricky and require additional dependencies.
8010 	+/
8011 	void drawText(DrawableFont font, Point upperLeft, in char[] text) {
8012 		if(impl is null) return;
8013 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
8014 		transform(upperLeft);
8015 		font.drawString(this, upperLeft, text);
8016 	}
8017 
8018 	version(Windows)
8019 	void drawText(Point upperLeft, scope const(wchar)[] text) {
8020 		if(impl is null) return;
8021 		if(isClipped(upperLeft, Point(int.max, int.max))) return;
8022 		transform(upperLeft);
8023 
8024 		if(text.length && text[$-1] == '\n')
8025 			text = text[0 .. $-1]; // tailing newlines are weird on windows...
8026 
8027 		TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length);
8028 	}
8029 
8030 	static struct TextDrawingContext {
8031 		Point boundingBoxUpperLeft;
8032 		Point boundingBoxLowerRight;
8033 
8034 		Point currentLocation;
8035 
8036 		Point lastDrewUpperLeft;
8037 		Point lastDrewLowerRight;
8038 
8039 		// how do i do right aligned rich text?
8040 		// i kinda want to do a pre-made drawing then right align
8041 		// draw the whole block.
8042 		//
8043 		// That's exactly the diff: inline vs block stuff.
8044 
8045 		// I need to get coordinates of an inline section out too,
8046 		// not just a bounding box, but a series of bounding boxes
8047 		// should be ok. Consider what's needed to detect a click
8048 		// on a link in the middle of a paragraph breaking a line.
8049 		//
8050 		// Generally, we should be able to get the rectangles of
8051 		// any portion we draw.
8052 		//
8053 		// It also needs to tell what text is left if it overflows
8054 		// out of the box, so we can do stuff like float images around
8055 		// it. It should not attempt to draw a letter that would be
8056 		// clipped.
8057 		//
8058 		// I might also turn off word wrap stuff.
8059 	}
8060 
8061 	void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) {
8062 		if(impl is null) return;
8063 		// FIXME
8064 	}
8065 
8066 	/// Drawing an individual pixel is slow. Avoid it if possible.
8067 	void drawPixel(Point where) {
8068 		if(impl is null) return;
8069 		if(isClipped(where)) return;
8070 		transform(where);
8071 		impl.drawPixel(where.x, where.y);
8072 	}
8073 
8074 
8075 	/// Draws a pen using the current pen / outlineColor
8076 	@scriptable
8077 	void drawLine(Point starting, Point ending) {
8078 		if(impl is null) return;
8079 		if(isClipped(starting, ending)) return;
8080 		transform(starting);
8081 		transform(ending);
8082 		impl.drawLine(starting.x, starting.y, ending.x, ending.y);
8083 	}
8084 
8085 	/// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides
8086 	/// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor
8087 	/// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn.
8088 	@scriptable
8089 	void drawRectangle(Point upperLeft, int width, int height) {
8090 		if(impl is null) return;
8091 		if(isClipped(upperLeft, width, height)) return;
8092 		transform(upperLeft);
8093 		impl.drawRectangle(upperLeft.x, upperLeft.y, width, height);
8094 	}
8095 
8096 	/// ditto
8097 	void drawRectangle(Point upperLeft, Size size) {
8098 		if(impl is null) return;
8099 		if(isClipped(upperLeft, size.width, size.height)) return;
8100 		transform(upperLeft);
8101 		impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height);
8102 	}
8103 
8104 	/// ditto
8105 	void drawRectangle(Point upperLeft, Point lowerRightInclusive) {
8106 		if(impl is null) return;
8107 		if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return;
8108 		transform(upperLeft);
8109 		transform(lowerRightInclusive);
8110 		impl.drawRectangle(upperLeft.x, upperLeft.y,
8111 			lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1);
8112 	}
8113 
8114 	/// Arguments are the points of the bounding rectangle
8115 	void drawEllipse(Point upperLeft, Point lowerRight) {
8116 		if(impl is null) return;
8117 		if(isClipped(upperLeft, lowerRight)) return;
8118 		transform(upperLeft);
8119 		transform(lowerRight);
8120 		impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
8121 	}
8122 
8123 	/++
8124 		start and finish are units of degrees * 64
8125 	+/
8126 	void drawArc(Point upperLeft, int width, int height, int start, int finish) {
8127 		if(impl is null) return;
8128 		// FIXME: not actually implemented
8129 		if(isClipped(upperLeft, width, height)) return;
8130 		transform(upperLeft);
8131 		impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish);
8132 	}
8133 
8134 	//this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius
8135 	void drawCircle(Point upperLeft, int diameter) {
8136 		drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter));
8137 	}
8138 
8139 	/// .
8140 	void drawPolygon(Point[] vertexes) {
8141 		if(impl is null) return;
8142 		assert(vertexes.length);
8143 		int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min;
8144 		foreach(ref vertex; vertexes) {
8145 			if(vertex.x < minX)
8146 				minX = vertex.x;
8147 			if(vertex.y < minY)
8148 				minY = vertex.y;
8149 			if(vertex.x > maxX)
8150 				maxX = vertex.x;
8151 			if(vertex.y > maxY)
8152 				maxY = vertex.y;
8153 			transform(vertex);
8154 		}
8155 		if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return;
8156 		impl.drawPolygon(vertexes);
8157 	}
8158 
8159 	/// ditto
8160 	void drawPolygon(Point[] vertexes...) {
8161 		if(impl is null) return;
8162 		drawPolygon(vertexes);
8163 	}
8164 
8165 
8166 	// and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls.
8167 
8168 	//mixin NativeScreenPainterImplementation!() impl;
8169 
8170 
8171 	// HACK: if I mixin the impl directly, it won't let me override the copy
8172 	// constructor! The linker complains about there being multiple definitions.
8173 	// I'll make the best of it and reference count it though.
8174 	ScreenPainterImplementation* impl;
8175 }
8176 
8177 	// HACK: I need a pointer to the implementation so it's separate
8178 	struct ScreenPainterImplementation {
8179 		CapableOfBeingDrawnUpon window;
8180 		int referenceCount;
8181 		mixin NativeScreenPainterImplementation!();
8182 	}
8183 
8184 // FIXME: i haven't actually tested the sprite class on MS Windows
8185 
8186 /**
8187 	Sprites are optimized for fast drawing on the screen, but slow for direct pixel
8188 	access. They are best for drawing a relatively unchanging image repeatedly on the screen.
8189 
8190 
8191 	On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap,
8192 	though I'm not sure that's ideal and the implementation might change.
8193 
8194 	You create one by giving a window and an image. It optimizes for that window,
8195 	and copies the image into it to use as the initial picture. Creating a sprite
8196 	can be quite slow (especially over a network connection) so you should do it
8197 	as little as possible and just hold on to your sprite handles after making them.
8198 	simpledisplay does try to do its best though, using the XSHM extension if available,
8199 	but you should still write your code as if it will always be slow.
8200 
8201 	Then you can use `sprite.drawAt(painter, point);` to draw it, which should be
8202 	a fast operation - much faster than drawing the Image itself every time.
8203 
8204 	`Sprite` represents a scarce resource which should be freed when you
8205 	are done with it. Use the `dispose` method to do this. Do not use a `Sprite`
8206 	after it has been disposed. If you are unsure about this, don't take chances,
8207 	just let the garbage collector do it for you. But ideally, you can manage its
8208 	lifetime more efficiently.
8209 
8210 	$(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not
8211 	support alpha blending in its drawing at this time. That might change in the
8212 	future, but if you need alpha blending right now, use OpenGL instead. See
8213 	`gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.)
8214 
8215 	FIXME: you are supposed to be able to draw on these similarly to on windows.
8216 	ScreenPainter needs to be refactored to allow that though. So until that is
8217 	done, consider a `Sprite` to have const contents.
8218 */
8219 version(OSXCocoa) {} else // NotYetImplementedException
8220 class Sprite : CapableOfBeingDrawnUpon {
8221 
8222 	///
8223 	ScreenPainter draw() {
8224 		return ScreenPainter(this, handle);
8225 	}
8226 
8227 	/++
8228 		Copies the sprite's current state into a [TrueColorImage].
8229 
8230 		Be warned: this can be a very slow operation
8231 
8232 		History:
8233 			Actually implemented on March 14, 2021
8234 	+/
8235 	TrueColorImage takeScreenshot() {
8236 		return trueColorImageFromNativeHandle(handle, width, height);
8237 	}
8238 
8239 	void delegate() paintingFinishedDg() { return null; }
8240 	bool closed() { return false; }
8241 	ScreenPainterImplementation* activeScreenPainter_;
8242 	protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; }
8243 	protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; }
8244 
8245 	version(Windows)
8246 		private ubyte* rawData;
8247 	// FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them...
8248 
8249 	this(SimpleWindow win, int width, int height) {
8250 		this._width = width;
8251 		this._height = height;
8252 
8253 		version(X11) {
8254 			auto display = XDisplayConnection.get();
8255 			handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
8256 		} else version(Windows) {
8257 			BITMAPINFO infoheader;
8258 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
8259 			infoheader.bmiHeader.biWidth = width;
8260 			infoheader.bmiHeader.biHeight = height;
8261 			infoheader.bmiHeader.biPlanes = 1;
8262 			infoheader.bmiHeader.biBitCount = 24;
8263 			infoheader.bmiHeader.biCompression = BI_RGB;
8264 
8265 			// FIXME: this should prolly be a device dependent bitmap...
8266 			handle = CreateDIBSection(
8267 				null,
8268 				&infoheader,
8269 				DIB_RGB_COLORS,
8270 				cast(void**) &rawData,
8271 				null,
8272 				0);
8273 
8274 			if(handle is null)
8275 				throw new Exception("couldn't create pixmap");
8276 		}
8277 	}
8278 
8279 	/// Makes a sprite based on the image with the initial contents from the Image
8280 	this(SimpleWindow win, Image i) {
8281 		this(win, i.width, i.height);
8282 
8283 		version(X11) {
8284 			auto display = XDisplayConnection.get();
8285 			if(i.usingXshm)
8286 				XShmPutImage(display, cast(Drawable) handle, DefaultGC(display, DefaultScreen(display)), i.handle, 0, 0, 0, 0, i.width, i.height, false);
8287 			else
8288 				XPutImage(display, cast(Drawable) handle, DefaultGC(display, DefaultScreen(display)), i.handle, 0, 0, 0, 0, i.width, i.height);
8289 		} else version(Windows) {
8290 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
8291 			auto arrLength = itemsPerLine * height;
8292 			rawData[0..arrLength] = i.rawData[0..arrLength];
8293 		} else version(OSXCocoa) {
8294 			// FIXME: I have no idea if this is even any good
8295 			ubyte* rawData;
8296 
8297 			auto colorSpace = CGColorSpaceCreateDeviceRGB();
8298 			context = CGBitmapContextCreate(null, width, height, 8, 4*width,
8299 				colorSpace,
8300 				kCGImageAlphaPremultipliedLast
8301 				|kCGBitmapByteOrder32Big);
8302 			CGColorSpaceRelease(colorSpace);
8303 			rawData = CGBitmapContextGetData(context);
8304 
8305 			auto rdl = (width * height * 4);
8306 			rawData[0 .. rdl] = i.rawData[0 .. rdl];
8307 		} else static assert(0);
8308 	}
8309 
8310 	/++
8311 		Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn.
8312 
8313 		Params:
8314 			where = point on the window where the upper left corner of the image will be drawn
8315 			imageUpperLeft = point on the image to start the slice to draw
8316 			sliceSize = size of the slice of the image to draw on the window. If width or height is 0, it uses the entire width or height of the image.
8317 		History:
8318 			The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0)
8319 	+/
8320 	void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) {
8321 		painter.drawPixmap(this, where, imageUpperLeft, sliceSize);
8322 	}
8323 
8324 
8325 	/// Call this when you're ready to get rid of it
8326 	void dispose() {
8327 		version(X11) {
8328 			if(handle)
8329 				XFreePixmap(XDisplayConnection.get(), handle);
8330 			handle = None;
8331 		} else version(Windows) {
8332 			if(handle)
8333 				DeleteObject(handle);
8334 			handle = null;
8335 		} else version(OSXCocoa) {
8336 			if(context)
8337 				CGContextRelease(context);
8338 			context = null;
8339 		} else static assert(0);
8340 
8341 	}
8342 
8343 	~this() {
8344 		dispose();
8345 	}
8346 
8347 	///
8348 	final @property int width() { return _width; }
8349 
8350 	///
8351 	final @property int height() { return _height; }
8352 
8353 	///
8354 	static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img) {
8355 		return new Sprite(win, Image.fromMemoryImage(img));
8356 	}
8357 
8358 	private:
8359 
8360 	int _width;
8361 	int _height;
8362 	version(X11)
8363 		Pixmap handle;
8364 	else version(Windows)
8365 		HBITMAP handle;
8366 	else version(OSXCocoa)
8367 		CGContextRef context;
8368 	else static assert(0);
8369 }
8370 
8371 /++
8372 	NOT IMPLEMENTED
8373 
8374 	A display-stored image optimized for relatively quick drawing, like
8375 	[Sprite], but this one supports alpha channel blending and does NOT
8376 	support direct drawing upon it with a [ScreenPainter].
8377 
8378 	You can think of it as an [arsd.game.OpenGlTexture] for usage with a
8379 	plain [ScreenPainter]... sort of.
8380 
8381 	On X11, it requires the Xrender extension and library. This is available
8382 	almost everywhere though.
8383 
8384 	History:
8385 		Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED
8386 +/
8387 version(none)
8388 class AlphaSprite {
8389 	/++
8390 		Copies the given image into it.
8391 	+/
8392 	this(MemoryImage img) {
8393 
8394 		if(!XRenderLibrary.loadAttempted) {
8395 			XRenderLibrary.loadDynamicLibrary();
8396 
8397 			// FIXME: this needs to be reconstructed when the X server changes
8398 			repopulateX();
8399 		}
8400 		if(!XRenderLibrary.loadSuccessful)
8401 			throw new Exception("XRender library load failure");
8402 
8403 		// I probably need to put the alpha mask in a separate Picture
8404 		// ugh
8405 		// maybe the Sprite itself can have an alpha bitmask anyway
8406 
8407 
8408 		auto display = XDisplayConnection.get();
8409 		pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
8410 
8411 
8412 		XRenderPictureAttributes attrs;
8413 
8414 		handle = XRenderCreatePicture(
8415 			XDisplayConnection.get,
8416 			pixmap,
8417 			RGBA,
8418 			0,
8419 			&attrs
8420 		);
8421 
8422 	}
8423 
8424 	// maybe i'll use the create gradient functions too with static factories..
8425 
8426 	void drawAt(ScreenPainter painter, Point where) {
8427 		//painter.drawPixmap(this, where);
8428 
8429 		XRenderPictureAttributes attrs;
8430 
8431 		auto pic = XRenderCreatePicture(
8432 			XDisplayConnection.get,
8433 			painter.impl.d,
8434 			RGB,
8435 			0,
8436 			&attrs
8437 		);
8438 
8439 		XRenderComposite(
8440 			XDisplayConnection.get,
8441 			3, // PictOpOver
8442 			handle,
8443 			None,
8444 			pic,
8445 			0, // src
8446 			0,
8447 			0, // mask
8448 			0,
8449 			10, // dest
8450 			10,
8451 			100, // width
8452 			100
8453 		);
8454 
8455 		/+
8456 		XRenderFreePicture(
8457 			XDisplayConnection.get,
8458 			pic
8459 		);
8460 
8461 		XRenderFreePicture(
8462 			XDisplayConnection.get,
8463 			fill
8464 		);
8465 		+/
8466 		// on Windows you can stretch but Xrender still can't :(
8467 	}
8468 
8469 	static XRenderPictFormat* RGB;
8470 	static XRenderPictFormat* RGBA;
8471 	static void repopulateX() {
8472 		auto display = XDisplayConnection.get;
8473 		RGB  = XRenderFindStandardFormat(display, PictStandardRGB24);
8474 		RGBA = XRenderFindStandardFormat(display, PictStandardARGB24);
8475 	}
8476 
8477 	XPixmap pixmap;
8478 	Picture handle;
8479 }
8480 
8481 ///
8482 interface CapableOfBeingDrawnUpon {
8483 	///
8484 	ScreenPainter draw();
8485 	///
8486 	int width();
8487 	///
8488 	int height();
8489 	protected ScreenPainterImplementation* activeScreenPainter();
8490 	protected void activeScreenPainter(ScreenPainterImplementation*);
8491 	bool closed();
8492 
8493 	void delegate() paintingFinishedDg();
8494 
8495 	/// Be warned: this can be a very slow operation
8496 	TrueColorImage takeScreenshot();
8497 }
8498 
8499 /// Flushes any pending gui buffers. Necessary if you are using with_eventloop with X - flush after you create your windows but before you call loop()
8500 void flushGui() {
8501 	version(X11) {
8502 		auto dpy = XDisplayConnection.get();
8503 		XLockDisplay(dpy);
8504 		scope(exit) XUnlockDisplay(dpy);
8505 		XFlush(dpy);
8506 	}
8507 }
8508 
8509 /++
8510 	Runs the given code in the GUI thread when its event loop
8511 	is available, blocking until it completes. This allows you
8512 	to create and manipulate windows from another thread without
8513 	invoking undefined behavior.
8514 
8515 	If this is the gui thread, it runs the code immediately.
8516 
8517 	If no gui thread exists yet, the current thread is assumed
8518 	to be it. Attempting to create windows or run the event loop
8519 	in any other thread will cause an assertion failure.
8520 
8521 
8522 	$(TIP
8523 		Did you know you can use UFCS on delegate literals?
8524 
8525 		() {
8526 			// code here
8527 		}.runInGuiThread;
8528 	)
8529 
8530 	Returns:
8531 		`true` if the function was called, `false` if it was not.
8532 		The function may not be called because the gui thread had
8533 		already terminated by the time you called this.
8534 
8535 	History:
8536 		Added April 10, 2020 (v7.2.0)
8537 
8538 		Return value added and implementation tweaked to avoid locking
8539 		at program termination on February 24, 2021 (v9.2.1).
8540 +/
8541 bool runInGuiThread(scope void delegate() dg) @trusted {
8542 	claimGuiThread();
8543 
8544 	if(thisIsGuiThread) {
8545 		dg();
8546 		return true;
8547 	}
8548 
8549 	if(guiThreadTerminating)
8550 		return false;
8551 
8552 	import core.sync.semaphore;
8553 	static Semaphore sc;
8554 	if(sc is null)
8555 		sc = new Semaphore();
8556 
8557 	static RunQueueMember* rqm;
8558 	if(rqm is null)
8559 		rqm = new RunQueueMember;
8560 	rqm.dg = cast(typeof(rqm.dg)) dg;
8561 	rqm.signal = sc;
8562 	rqm.thrown = null;
8563 
8564 	synchronized(runInGuiThreadLock) {
8565 		runInGuiThreadQueue ~= rqm;
8566 	}
8567 
8568 	if(!SimpleWindow.eventWakeUp())
8569 		throw new Error("runInGuiThread impossible; eventWakeUp failed");
8570 
8571 	rqm.signal.wait();
8572 	auto t = rqm.thrown;
8573 
8574 	if(t)
8575 		throw t;
8576 
8577 	return true;
8578 }
8579 
8580 private void runPendingRunInGuiThreadDelegates() {
8581 	more:
8582 	RunQueueMember* next;
8583 	synchronized(runInGuiThreadLock) {
8584 		if(runInGuiThreadQueue.length) {
8585 			next = runInGuiThreadQueue[0];
8586 			runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
8587 		} else {
8588 			next = null;
8589 		}
8590 	}
8591 
8592 	if(next) {
8593 		try {
8594 			next.dg();
8595 			next.thrown = null;
8596 		} catch(Throwable t) {
8597 			next.thrown = t;
8598 		}
8599 
8600 		next.signal.notify();
8601 
8602 		goto more;
8603 	}
8604 }
8605 
8606 private void claimGuiThread() {
8607 	import core.atomic;
8608 	if(cas(&guiThreadExists, false, true))
8609 		thisIsGuiThread = true;
8610 }
8611 
8612 private struct RunQueueMember {
8613 	void delegate() dg;
8614 	import core.sync.semaphore;
8615 	Semaphore signal;
8616 	Throwable thrown;
8617 }
8618 
8619 private __gshared RunQueueMember*[] runInGuiThreadQueue;
8620 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
8621 private bool thisIsGuiThread = false;
8622 private shared bool guiThreadExists = false;
8623 private shared bool guiThreadTerminating = false;
8624 
8625 private void guiThreadFinalize() {
8626 	assert(thisIsGuiThread);
8627 
8628 	guiThreadTerminating = true; // don't add any more from this point on
8629 	runPendingRunInGuiThreadDelegates();
8630 }
8631 
8632 /+
8633 interface IPromise {
8634 	void reportProgress(int current, int max, string message);
8635 
8636 	/+ // not formally in cuz of templates but still
8637 	IPromise Then();
8638 	IPromise Catch();
8639 	IPromise Finally();
8640 	+/
8641 }
8642 
8643 /+
8644 	auto promise = async({ ... });
8645 	promise.Then(whatever).
8646 		Then(whateverelse).
8647 		Catch((exception) { });
8648 
8649 
8650 	A promise is run inside a fiber and it looks something like:
8651 
8652 	try {
8653 		auto res = whatever();
8654 		auto res2 = whateverelse(res);
8655 	} catch(Exception e) {
8656 		{ }(e);
8657 	}
8658 
8659 	When a thing succeeds, it is passed as an arg to the next
8660 +/
8661 class Promise(T) : IPromise {
8662 	auto Then() { return null; }
8663 	auto Catch() { return null; }
8664 	auto Finally() { return null; }
8665 
8666 	// wait for it to resolve and return the value, or rethrow the error if that occurred.
8667 	// cannot be called from the gui thread, but this is caught at runtime instead of compile time.
8668 	T await();
8669 }
8670 
8671 interface Task {
8672 }
8673 
8674 interface Resolvable(T) : Task {
8675 	void run();
8676 
8677 	void resolve(T);
8678 
8679 	Resolvable!T then(void delegate(T)); // returns a new promise
8680 	Resolvable!T error(Throwable); // js catch
8681 	Resolvable!T completed(); // js finally
8682 
8683 }
8684 
8685 /++
8686 	Runs `work` in a helper thread and sends its return value back to the main gui
8687 	thread as the argument to `uponCompletion`. If `work` throws, the exception is
8688 	sent to the `uponThrown` if given, or if null, rethrown from the event loop to
8689 	kill the program.
8690 
8691 	You can call reportProgress(position, max, message) to update your parent window
8692 	on your progress.
8693 
8694 	I should also use `shared` methods. FIXME
8695 
8696 	History:
8697 		Added March 6, 2021 (dub version 9.3).
8698 +/
8699 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) {
8700 	uponCompletion(work(null));
8701 }
8702 
8703 +/
8704 
8705 /// Used internal to dispatch events to various classes.
8706 interface CapableOfHandlingNativeEvent {
8707 	NativeEventHandler getNativeEventHandler();
8708 
8709 	/*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
8710 
8711 	version(X11) {
8712 		// if this is impossible, you are allowed to just throw from it
8713 		// Note: if you call it from another object, set a flag cuz the manger will call you again
8714 		void recreateAfterDisconnect();
8715 		// discard any *connection specific* state, but keep enough that you
8716 		// can be recreated if possible. discardConnectionState() is always called immediately
8717 		// before recreateAfterDisconnect(), so you can set a flag there to decide if
8718 		// you need initialization order
8719 		void discardConnectionState();
8720 	}
8721 }
8722 
8723 version(X11)
8724 /++
8725 	State of keys on mouse events, especially motion.
8726 
8727 	Do not trust the actual integer values in this, they are platform-specific. Always use the names.
8728 +/
8729 enum ModifierState : uint {
8730 	shift = 1, ///
8731 	capsLock = 2, ///
8732 	ctrl = 4, ///
8733 	alt = 8, /// Not always available on Windows
8734 	windows = 64, /// ditto
8735 	numLock = 16, ///
8736 
8737 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
8738 	middleButtonDown = 512, /// ditto
8739 	rightButtonDown = 1024, /// ditto
8740 }
8741 else version(Windows)
8742 /// ditto
8743 enum ModifierState : uint {
8744 	shift = 4, ///
8745 	ctrl = 8, ///
8746 
8747 	// i'm not sure if the next two are available
8748 	alt = 256, /// not always available on Windows
8749 	windows = 512, /// ditto
8750 
8751 	capsLock = 1024, ///
8752 	numLock = 2048, ///
8753 
8754 	leftButtonDown = 1, /// not available on key events
8755 	middleButtonDown = 16, /// ditto
8756 	rightButtonDown = 2, /// ditto
8757 
8758 	backButtonDown = 0x20, /// not available on X
8759 	forwardButtonDown = 0x40, /// ditto
8760 }
8761 else version(OSXCocoa)
8762 // FIXME FIXME NotYetImplementedException
8763 enum ModifierState : uint {
8764 	shift = 1, ///
8765 	capsLock = 2, ///
8766 	ctrl = 4, ///
8767 	alt = 8, /// Not always available on Windows
8768 	windows = 64, /// ditto
8769 	numLock = 16, ///
8770 
8771 	leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
8772 	middleButtonDown = 512, /// ditto
8773 	rightButtonDown = 1024, /// ditto
8774 }
8775 
8776 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them
8777 enum MouseButton : int {
8778 	none = 0,
8779 	left = 1, ///
8780 	right = 2, ///
8781 	middle = 4, ///
8782 	wheelUp = 8, ///
8783 	wheelDown = 16, ///
8784 	backButton = 32, /// often found on the thumb and used for back in browsers
8785 	forwardButton = 64, /// often found on the thumb and used for forward in browsers
8786 }
8787 
8788 version(X11) {
8789 	// FIXME: match ASCII whenever we can. Most of it is already there,
8790 	// but there's a few exceptions and mismatches with Windows
8791 
8792 	/// Do not trust the numeric values as they are platform-specific. Always use the symbolic name.
8793 	enum Key {
8794 		Escape = 0xff1b, ///
8795 		F1 = 0xffbe, ///
8796 		F2 = 0xffbf, ///
8797 		F3 = 0xffc0, ///
8798 		F4 = 0xffc1, ///
8799 		F5 = 0xffc2, ///
8800 		F6 = 0xffc3, ///
8801 		F7 = 0xffc4, ///
8802 		F8 = 0xffc5, ///
8803 		F9 = 0xffc6, ///
8804 		F10 = 0xffc7, ///
8805 		F11 = 0xffc8, ///
8806 		F12 = 0xffc9, ///
8807 		PrintScreen = 0xff61, ///
8808 		ScrollLock = 0xff14, ///
8809 		Pause = 0xff13, ///
8810 		Grave = 0x60, /// The $(BACKTICK) ~ key
8811 		// number keys across the top of the keyboard
8812 		N1 = 0x31, /// Number key atop the keyboard
8813 		N2 = 0x32, ///
8814 		N3 = 0x33, ///
8815 		N4 = 0x34, ///
8816 		N5 = 0x35, ///
8817 		N6 = 0x36, ///
8818 		N7 = 0x37, ///
8819 		N8 = 0x38, ///
8820 		N9 = 0x39, ///
8821 		N0 = 0x30, ///
8822 		Dash = 0x2d, ///
8823 		Equals = 0x3d, ///
8824 		Backslash = 0x5c, /// The \ | key
8825 		Backspace = 0xff08, ///
8826 		Insert = 0xff63, ///
8827 		Home = 0xff50, ///
8828 		PageUp = 0xff55, ///
8829 		Delete = 0xffff, ///
8830 		End = 0xff57, ///
8831 		PageDown = 0xff56, ///
8832 		Up = 0xff52, ///
8833 		Down = 0xff54, ///
8834 		Left = 0xff51, ///
8835 		Right = 0xff53, ///
8836 
8837 		Tab = 0xff09, ///
8838 		Q = 0x71, ///
8839 		W = 0x77, ///
8840 		E = 0x65, ///
8841 		R = 0x72, ///
8842 		T = 0x74, ///
8843 		Y = 0x79, ///
8844 		U = 0x75, ///
8845 		I = 0x69, ///
8846 		O = 0x6f, ///
8847 		P = 0x70, ///
8848 		LeftBracket = 0x5b, /// the [ { key
8849 		RightBracket = 0x5d, /// the ] } key
8850 		CapsLock = 0xffe5, ///
8851 		A = 0x61, ///
8852 		S = 0x73, ///
8853 		D = 0x64, ///
8854 		F = 0x66, ///
8855 		G = 0x67, ///
8856 		H = 0x68, ///
8857 		J = 0x6a, ///
8858 		K = 0x6b, ///
8859 		L = 0x6c, ///
8860 		Semicolon = 0x3b, ///
8861 		Apostrophe = 0x27, ///
8862 		Enter = 0xff0d, ///
8863 		Shift = 0xffe1, ///
8864 		Z = 0x7a, ///
8865 		X = 0x78, ///
8866 		C = 0x63, ///
8867 		V = 0x76, ///
8868 		B = 0x62, ///
8869 		N = 0x6e, ///
8870 		M = 0x6d, ///
8871 		Comma = 0x2c, ///
8872 		Period = 0x2e, ///
8873 		Slash = 0x2f, /// the / ? key
8874 		Shift_r = 0xffe2, /// Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it. If it is supported though, it is the right Shift key, as opposed to the left Shift key
8875 		Ctrl = 0xffe3, ///
8876 		Windows = 0xffeb, ///
8877 		Alt = 0xffe9, ///
8878 		Space = 0x20, ///
8879 		Alt_r = 0xffea, /// ditto of shift_r
8880 		Windows_r = 0xffec, ///
8881 		Menu = 0xff67, ///
8882 		Ctrl_r = 0xffe4, ///
8883 
8884 		NumLock = 0xff7f, ///
8885 		Divide = 0xffaf, /// The / key on the number pad
8886 		Multiply = 0xffaa, /// The * key on the number pad
8887 		Minus = 0xffad, /// The - key on the number pad
8888 		Plus = 0xffab, /// The + key on the number pad
8889 		PadEnter = 0xff8d, /// Numberpad enter key
8890 		Pad1 = 0xff9c, /// Numberpad keys
8891 		Pad2 = 0xff99, ///
8892 		Pad3 = 0xff9b, ///
8893 		Pad4 = 0xff96, ///
8894 		Pad5 = 0xff9d, ///
8895 		Pad6 = 0xff98, ///
8896 		Pad7 = 0xff95, ///
8897 		Pad8 = 0xff97, ///
8898 		Pad9 = 0xff9a, ///
8899 		Pad0 = 0xff9e, ///
8900 		PadDot = 0xff9f, ///
8901 	}
8902 } else version(Windows) {
8903 	// the character here is for en-us layouts and for illustration only
8904 	// if you actually want to get characters, wait for character events
8905 	// (the argument to your event handler is simply a dchar)
8906 	// those will be converted by the OS for the right locale.
8907 
8908 	enum Key {
8909 		Escape = 0x1b,
8910 		F1 = 0x70,
8911 		F2 = 0x71,
8912 		F3 = 0x72,
8913 		F4 = 0x73,
8914 		F5 = 0x74,
8915 		F6 = 0x75,
8916 		F7 = 0x76,
8917 		F8 = 0x77,
8918 		F9 = 0x78,
8919 		F10 = 0x79,
8920 		F11 = 0x7a,
8921 		F12 = 0x7b,
8922 		PrintScreen = 0x2c,
8923 		ScrollLock = 0x91,
8924 		Pause = 0x13,
8925 		Grave = 0xc0,
8926 		// number keys across the top of the keyboard
8927 		N1 = 0x31,
8928 		N2 = 0x32,
8929 		N3 = 0x33,
8930 		N4 = 0x34,
8931 		N5 = 0x35,
8932 		N6 = 0x36,
8933 		N7 = 0x37,
8934 		N8 = 0x38,
8935 		N9 = 0x39,
8936 		N0 = 0x30,
8937 		Dash = 0xbd,
8938 		Equals = 0xbb,
8939 		Backslash = 0xdc,
8940 		Backspace = 0x08,
8941 		Insert = 0x2d,
8942 		Home = 0x24,
8943 		PageUp = 0x21,
8944 		Delete = 0x2e,
8945 		End = 0x23,
8946 		PageDown = 0x22,
8947 		Up = 0x26,
8948 		Down = 0x28,
8949 		Left = 0x25,
8950 		Right = 0x27,
8951 
8952 		Tab = 0x09,
8953 		Q = 0x51,
8954 		W = 0x57,
8955 		E = 0x45,
8956 		R = 0x52,
8957 		T = 0x54,
8958 		Y = 0x59,
8959 		U = 0x55,
8960 		I = 0x49,
8961 		O = 0x4f,
8962 		P = 0x50,
8963 		LeftBracket = 0xdb,
8964 		RightBracket = 0xdd,
8965 		CapsLock = 0x14,
8966 		A = 0x41,
8967 		S = 0x53,
8968 		D = 0x44,
8969 		F = 0x46,
8970 		G = 0x47,
8971 		H = 0x48,
8972 		J = 0x4a,
8973 		K = 0x4b,
8974 		L = 0x4c,
8975 		Semicolon = 0xba,
8976 		Apostrophe = 0xde,
8977 		Enter = 0x0d,
8978 		Shift = 0x10,
8979 		Z = 0x5a,
8980 		X = 0x58,
8981 		C = 0x43,
8982 		V = 0x56,
8983 		B = 0x42,
8984 		N = 0x4e,
8985 		M = 0x4d,
8986 		Comma = 0xbc,
8987 		Period = 0xbe,
8988 		Slash = 0xbf,
8989 		Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
8990 		Ctrl = 0x11,
8991 		Windows = 0x5b,
8992 		Alt = -5, // FIXME
8993 		Space = 0x20,
8994 		Alt_r = 0xffea, // ditto of shift_r
8995 		Windows_r = 0x5c, // ditto of shift_r
8996 		Menu = 0x5d,
8997 		Ctrl_r = 0xa3, // ditto of shift_r
8998 
8999 		NumLock = 0x90,
9000 		Divide = 0x6f,
9001 		Multiply = 0x6a,
9002 		Minus = 0x6d,
9003 		Plus = 0x6b,
9004 		PadEnter = -8, // FIXME
9005 		Pad1 = 0x61,
9006 		Pad2 = 0x62,
9007 		Pad3 = 0x63,
9008 		Pad4 = 0x64,
9009 		Pad5 = 0x65,
9010 		Pad6 = 0x66,
9011 		Pad7 = 0x67,
9012 		Pad8 = 0x68,
9013 		Pad9 = 0x69,
9014 		Pad0 = 0x60,
9015 		PadDot = 0x6e,
9016 	}
9017 
9018 	// I'm keeping this around for reference purposes
9019 	// ideally all these buttons will be listed for all platforms,
9020 	// but now now I'm just focusing on my US keyboard
9021 	version(none)
9022 	enum Key {
9023 		LBUTTON = 0x01,
9024 		RBUTTON = 0x02,
9025 		CANCEL = 0x03,
9026 		MBUTTON = 0x04,
9027 		//static if (_WIN32_WINNT > =  0x500) {
9028 		XBUTTON1 = 0x05,
9029 		XBUTTON2 = 0x06,
9030 		//}
9031 		BACK = 0x08,
9032 		TAB = 0x09,
9033 		CLEAR = 0x0C,
9034 		RETURN = 0x0D,
9035 		SHIFT = 0x10,
9036 		CONTROL = 0x11,
9037 		MENU = 0x12,
9038 		PAUSE = 0x13,
9039 		CAPITAL = 0x14,
9040 		KANA = 0x15,
9041 		HANGEUL = 0x15,
9042 		HANGUL = 0x15,
9043 		JUNJA = 0x17,
9044 		FINAL = 0x18,
9045 		HANJA = 0x19,
9046 		KANJI = 0x19,
9047 		ESCAPE = 0x1B,
9048 		CONVERT = 0x1C,
9049 		NONCONVERT = 0x1D,
9050 		ACCEPT = 0x1E,
9051 		MODECHANGE = 0x1F,
9052 		SPACE = 0x20,
9053 		PRIOR = 0x21,
9054 		NEXT = 0x22,
9055 		END = 0x23,
9056 		HOME = 0x24,
9057 		LEFT = 0x25,
9058 		UP = 0x26,
9059 		RIGHT = 0x27,
9060 		DOWN = 0x28,
9061 		SELECT = 0x29,
9062 		PRINT = 0x2A,
9063 		EXECUTE = 0x2B,
9064 		SNAPSHOT = 0x2C,
9065 		INSERT = 0x2D,
9066 		DELETE = 0x2E,
9067 		HELP = 0x2F,
9068 		LWIN = 0x5B,
9069 		RWIN = 0x5C,
9070 		APPS = 0x5D,
9071 		SLEEP = 0x5F,
9072 		NUMPAD0 = 0x60,
9073 		NUMPAD1 = 0x61,
9074 		NUMPAD2 = 0x62,
9075 		NUMPAD3 = 0x63,
9076 		NUMPAD4 = 0x64,
9077 		NUMPAD5 = 0x65,
9078 		NUMPAD6 = 0x66,
9079 		NUMPAD7 = 0x67,
9080 		NUMPAD8 = 0x68,
9081 		NUMPAD9 = 0x69,
9082 		MULTIPLY = 0x6A,
9083 		ADD = 0x6B,
9084 		SEPARATOR = 0x6C,
9085 		SUBTRACT = 0x6D,
9086 		DECIMAL = 0x6E,
9087 		DIVIDE = 0x6F,
9088 		F1 = 0x70,
9089 		F2 = 0x71,
9090 		F3 = 0x72,
9091 		F4 = 0x73,
9092 		F5 = 0x74,
9093 		F6 = 0x75,
9094 		F7 = 0x76,
9095 		F8 = 0x77,
9096 		F9 = 0x78,
9097 		F10 = 0x79,
9098 		F11 = 0x7A,
9099 		F12 = 0x7B,
9100 		F13 = 0x7C,
9101 		F14 = 0x7D,
9102 		F15 = 0x7E,
9103 		F16 = 0x7F,
9104 		F17 = 0x80,
9105 		F18 = 0x81,
9106 		F19 = 0x82,
9107 		F20 = 0x83,
9108 		F21 = 0x84,
9109 		F22 = 0x85,
9110 		F23 = 0x86,
9111 		F24 = 0x87,
9112 		NUMLOCK = 0x90,
9113 		SCROLL = 0x91,
9114 		LSHIFT = 0xA0,
9115 		RSHIFT = 0xA1,
9116 		LCONTROL = 0xA2,
9117 		RCONTROL = 0xA3,
9118 		LMENU = 0xA4,
9119 		RMENU = 0xA5,
9120 		//static if (_WIN32_WINNT > =  0x500) {
9121 		BROWSER_BACK = 0xA6,
9122 		BROWSER_FORWARD = 0xA7,
9123 		BROWSER_REFRESH = 0xA8,
9124 		BROWSER_STOP = 0xA9,
9125 		BROWSER_SEARCH = 0xAA,
9126 		BROWSER_FAVORITES = 0xAB,
9127 		BROWSER_HOME = 0xAC,
9128 		VOLUME_MUTE = 0xAD,
9129 		VOLUME_DOWN = 0xAE,
9130 		VOLUME_UP = 0xAF,
9131 		MEDIA_NEXT_TRACK = 0xB0,
9132 		MEDIA_PREV_TRACK = 0xB1,
9133 		MEDIA_STOP = 0xB2,
9134 		MEDIA_PLAY_PAUSE = 0xB3,
9135 		LAUNCH_MAIL = 0xB4,
9136 		LAUNCH_MEDIA_SELECT = 0xB5,
9137 		LAUNCH_APP1 = 0xB6,
9138 		LAUNCH_APP2 = 0xB7,
9139 		//}
9140 		OEM_1 = 0xBA,
9141 		//static if (_WIN32_WINNT > =  0x500) {
9142 		OEM_PLUS = 0xBB,
9143 		OEM_COMMA = 0xBC,
9144 		OEM_MINUS = 0xBD,
9145 		OEM_PERIOD = 0xBE,
9146 		//}
9147 		OEM_2 = 0xBF,
9148 		OEM_3 = 0xC0,
9149 		OEM_4 = 0xDB,
9150 		OEM_5 = 0xDC,
9151 		OEM_6 = 0xDD,
9152 		OEM_7 = 0xDE,
9153 		OEM_8 = 0xDF,
9154 		//static if (_WIN32_WINNT > =  0x500) {
9155 		OEM_102 = 0xE2,
9156 		//}
9157 		PROCESSKEY = 0xE5,
9158 		//static if (_WIN32_WINNT > =  0x500) {
9159 		PACKET = 0xE7,
9160 		//}
9161 		ATTN = 0xF6,
9162 		CRSEL = 0xF7,
9163 		EXSEL = 0xF8,
9164 		EREOF = 0xF9,
9165 		PLAY = 0xFA,
9166 		ZOOM = 0xFB,
9167 		NONAME = 0xFC,
9168 		PA1 = 0xFD,
9169 		OEM_CLEAR = 0xFE,
9170 	}
9171 
9172 } else version(OSXCocoa) {
9173 	// FIXME
9174 	enum Key {
9175 		Escape = 0x1b,
9176 		F1 = 0x70,
9177 		F2 = 0x71,
9178 		F3 = 0x72,
9179 		F4 = 0x73,
9180 		F5 = 0x74,
9181 		F6 = 0x75,
9182 		F7 = 0x76,
9183 		F8 = 0x77,
9184 		F9 = 0x78,
9185 		F10 = 0x79,
9186 		F11 = 0x7a,
9187 		F12 = 0x7b,
9188 		PrintScreen = 0x2c,
9189 		ScrollLock = -2, // FIXME
9190 		Pause = -3, // FIXME
9191 		Grave = 0xc0,
9192 		// number keys across the top of the keyboard
9193 		N1 = 0x31,
9194 		N2 = 0x32,
9195 		N3 = 0x33,
9196 		N4 = 0x34,
9197 		N5 = 0x35,
9198 		N6 = 0x36,
9199 		N7 = 0x37,
9200 		N8 = 0x38,
9201 		N9 = 0x39,
9202 		N0 = 0x30,
9203 		Dash = 0xbd,
9204 		Equals = 0xbb,
9205 		Backslash = 0xdc,
9206 		Backspace = 0x08,
9207 		Insert = 0x2d,
9208 		Home = 0x24,
9209 		PageUp = 0x21,
9210 		Delete = 0x2e,
9211 		End = 0x23,
9212 		PageDown = 0x22,
9213 		Up = 0x26,
9214 		Down = 0x28,
9215 		Left = 0x25,
9216 		Right = 0x27,
9217 
9218 		Tab = 0x09,
9219 		Q = 0x51,
9220 		W = 0x57,
9221 		E = 0x45,
9222 		R = 0x52,
9223 		T = 0x54,
9224 		Y = 0x59,
9225 		U = 0x55,
9226 		I = 0x49,
9227 		O = 0x4f,
9228 		P = 0x50,
9229 		LeftBracket = 0xdb,
9230 		RightBracket = 0xdd,
9231 		CapsLock = 0x14,
9232 		A = 0x41,
9233 		S = 0x53,
9234 		D = 0x44,
9235 		F = 0x46,
9236 		G = 0x47,
9237 		H = 0x48,
9238 		J = 0x4a,
9239 		K = 0x4b,
9240 		L = 0x4c,
9241 		Semicolon = 0xba,
9242 		Apostrophe = 0xde,
9243 		Enter = 0x0d,
9244 		Shift = 0x10,
9245 		Z = 0x5a,
9246 		X = 0x58,
9247 		C = 0x43,
9248 		V = 0x56,
9249 		B = 0x42,
9250 		N = 0x4e,
9251 		M = 0x4d,
9252 		Comma = 0xbc,
9253 		Period = 0xbe,
9254 		Slash = 0xbf,
9255 		Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it
9256 		Ctrl = 0x11,
9257 		Windows = 0x5b,
9258 		Alt = -5, // FIXME
9259 		Space = 0x20,
9260 		Alt_r = 0xffea, // ditto of shift_r
9261 		Windows_r = -6, // FIXME
9262 		Menu = 0x5d,
9263 		Ctrl_r = -7, // FIXME
9264 
9265 		NumLock = 0x90,
9266 		Divide = 0x6f,
9267 		Multiply = 0x6a,
9268 		Minus = 0x6d,
9269 		Plus = 0x6b,
9270 		PadEnter = -8, // FIXME
9271 		// FIXME for the rest of these:
9272 		Pad1 = 0xff9c,
9273 		Pad2 = 0xff99,
9274 		Pad3 = 0xff9b,
9275 		Pad4 = 0xff96,
9276 		Pad5 = 0xff9d,
9277 		Pad6 = 0xff98,
9278 		Pad7 = 0xff95,
9279 		Pad8 = 0xff97,
9280 		Pad9 = 0xff9a,
9281 		Pad0 = 0xff9e,
9282 		PadDot = 0xff9f,
9283 	}
9284 
9285 }
9286 
9287 /* Additional utilities */
9288 
9289 
9290 Color fromHsl(real h, real s, real l) {
9291 	return arsd.color.fromHsl([h,s,l]);
9292 }
9293 
9294 
9295 
9296 /* ********** What follows is the system-specific implementations *********/
9297 version(Windows) {
9298 
9299 
9300 	// helpers for making HICONs from MemoryImages
9301 	class WindowsIcon {
9302 		struct Win32Icon(int colorCount) {
9303 		align(1):
9304 			uint biSize;
9305 			int biWidth;
9306 			int biHeight;
9307 			ushort biPlanes;
9308 			ushort biBitCount;
9309 			uint biCompression;
9310 			uint biSizeImage;
9311 			int biXPelsPerMeter;
9312 			int biYPelsPerMeter;
9313 			uint biClrUsed;
9314 			uint biClrImportant;
9315 			RGBQUAD[colorCount] biColors;
9316 			/* Pixels:
9317 			Uint8 pixels[]
9318 			*/
9319 			/* Mask:
9320 			Uint8 mask[]
9321 			*/
9322 
9323 			ubyte[4096] data;
9324 
9325 			void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
9326 				width = mi.width;
9327 				height = mi.height;
9328 
9329 				auto indexedImage = cast(IndexedImage) mi;
9330 				if(indexedImage is null)
9331 					indexedImage = quantize(mi.getAsTrueColorImage());
9332 
9333 				assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy
9334 				assert(height %4 == 0);
9335 
9336 				int icon_plen = height*((width+3)&~3);
9337 				int icon_mlen = height*((((width+7)/8)+3)&~3);
9338 				icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount;
9339 
9340 				biSize = 40;
9341 				biWidth = width;
9342 				biHeight = height*2;
9343 				biPlanes = 1;
9344 				biBitCount = 8;
9345 				biSizeImage = icon_plen+icon_mlen;
9346 
9347 				int offset = 0;
9348 				int andOff = icon_plen * 8; // the and offset is in bits
9349 				for(int y = height - 1; y >= 0; y--) {
9350 					int off2 = y * width;
9351 					foreach(x; 0 .. width) {
9352 						const b = indexedImage.data[off2 + x];
9353 						data[offset] = b;
9354 						offset++;
9355 
9356 						const andBit = andOff % 8;
9357 						const andIdx = andOff / 8;
9358 						assert(b < indexedImage.palette.length);
9359 						// this is anded to the destination, since and 0 means erase,
9360 						// we want that to  be opaque, and 1 for transparent
9361 						auto transparent = (indexedImage.palette[b].a <= 127);
9362 						data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0);
9363 
9364 						andOff++;
9365 					}
9366 
9367 					andOff += andOff % 32;
9368 				}
9369 
9370 				foreach(idx, entry; indexedImage.palette) {
9371 					if(entry.a > 127) {
9372 						biColors[idx].rgbBlue = entry.b;
9373 						biColors[idx].rgbGreen = entry.g;
9374 						biColors[idx].rgbRed = entry.r;
9375 					} else {
9376 						biColors[idx].rgbBlue = 255;
9377 						biColors[idx].rgbGreen = 255;
9378 						biColors[idx].rgbRed = 255;
9379 					}
9380 				}
9381 
9382 				/*
9383 				data[0..icon_plen] = getFlippedUnfilteredDatastream(png);
9384 				data[icon_plen..icon_plen+icon_mlen] = getANDMask(png);
9385 				//icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0);
9386 				auto pngMap = fetchPaletteWin32(png);
9387 				biColors[0..pngMap.length] = pngMap[];
9388 				*/
9389 			}
9390 		}
9391 
9392 
9393 		Win32Icon!(256) icon_win32;
9394 
9395 
9396 		this(MemoryImage mi) {
9397 			int icon_len, width, height;
9398 
9399 			icon_win32.fromMemoryImage(mi, icon_len, width, height);
9400 
9401 			/*
9402 			PNG* png = readPnpngData);
9403 			PNGHeader pngh = getHeader(png);
9404 			void* icon_win32;
9405 			if(pngh.depth == 4) {
9406 				auto i = new Win32Icon!(16);
9407 				i.fromPNG(png, pngh, icon_len, width, height);
9408 				icon_win32 = i;
9409 			}
9410 			else if(pngh.depth == 8) {
9411 				auto i = new Win32Icon!(256);
9412 				i.fromPNG(png, pngh, icon_len, width, height);
9413 				icon_win32 = i;
9414 			} else assert(0);
9415 			*/
9416 
9417 			hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0);
9418 
9419 			if(hIcon is null) throw new Exception("CreateIconFromResourceEx");
9420 		}
9421 
9422 		~this() {
9423 			DestroyIcon(hIcon);
9424 		}
9425 
9426 		HICON hIcon;
9427 	}
9428 
9429 
9430 
9431 
9432 
9433 
9434 	alias int delegate(HWND, UINT, WPARAM, LPARAM) NativeEventHandler;
9435 	alias HWND NativeWindowHandle;
9436 
9437 	extern(Windows)
9438 	LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
9439 		try {
9440 			if(SimpleWindow.handleNativeGlobalEvent !is null) {
9441 				// it returns zero if the message is handled, so we won't do anything more there
9442 				// do I like that though?
9443 				auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam);
9444 				if(ret == 0)
9445 					return ret;
9446 			}
9447 
9448 			if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) {
9449 				if(window.getNativeEventHandler !is null) {
9450 					auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam);
9451 					if(ret == 0)
9452 						return ret;
9453 				}
9454 				if(auto w = cast(SimpleWindow) (*window))
9455 					return w.windowProcedure(hWnd, iMessage, wParam, lParam);
9456 				else
9457 					return DefWindowProc(hWnd, iMessage, wParam, lParam);
9458 			} else {
9459 				return DefWindowProc(hWnd, iMessage, wParam, lParam);
9460 			}
9461 		} catch (Exception e) {
9462 			try {
9463 				sdpy_abort(e);
9464 				return 0;
9465 			} catch(Exception e) { assert(0); }
9466 		}
9467 	}
9468 
9469 	void sdpy_abort(Throwable e) nothrow {
9470 		try
9471 			MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0);
9472 		catch(Exception e)
9473 			MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0);
9474 		ExitProcess(1);
9475 	}
9476 
9477 	mixin template NativeScreenPainterImplementation() {
9478 		HDC hdc;
9479 		HWND hwnd;
9480 		//HDC windowHdc;
9481 		HBITMAP oldBmp;
9482 
9483 		void create(NativeWindowHandle window) {
9484 			hwnd = window;
9485 
9486 			if(auto sw = cast(SimpleWindow) this.window) {
9487 				// drawing on a window, double buffer
9488 				auto windowHdc = GetDC(hwnd);
9489 
9490 				auto buffer = sw.impl.buffer;
9491 				if(buffer is null) {
9492 					hdc = windowHdc;
9493 					windowDc = true;
9494 				} else {
9495 					hdc = CreateCompatibleDC(windowHdc);
9496 
9497 					ReleaseDC(hwnd, windowHdc);
9498 
9499 					oldBmp = SelectObject(hdc, buffer);
9500 				}
9501 			} else {
9502 				// drawing on something else, draw directly
9503 				hdc = CreateCompatibleDC(null);
9504 				SelectObject(hdc, window);
9505 
9506 			}
9507 
9508 			// X doesn't draw a text background, so neither should we
9509 			SetBkMode(hdc, TRANSPARENT);
9510 
9511 			ensureDefaultFontLoaded();
9512 
9513 			if(defaultGuiFont) {
9514 				SelectObject(hdc, defaultGuiFont);
9515 				// DeleteObject(defaultGuiFont);
9516 			}
9517 		}
9518 
9519 		static HFONT defaultGuiFont;
9520 		static void ensureDefaultFontLoaded() {
9521 			static bool triedDefaultGuiFont = false;
9522 			if(!triedDefaultGuiFont) {
9523 				NONCLIENTMETRICS params;
9524 				params.cbSize = params.sizeof;
9525 				if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
9526 					defaultGuiFont = CreateFontIndirect(&params.lfMessageFont);
9527 				}
9528 				triedDefaultGuiFont = true;
9529 			}
9530 		}
9531 
9532 		void setFont(OperatingSystemFont font) {
9533 			if(font && font.font) {
9534 				if(SelectObject(hdc, font.font) == HGDI_ERROR) {
9535 					// error... how to handle tho?
9536 				}
9537 			}
9538 			else if(defaultGuiFont)
9539 				SelectObject(hdc, defaultGuiFont);
9540 		}
9541 
9542 		arsd.color.Rectangle _clipRectangle;
9543 
9544 		void setClipRectangle(int x, int y, int width, int height) {
9545 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
9546 
9547 			if(width == 0 || height == 0) {
9548 				SelectClipRgn(hdc, null);
9549 			} else {
9550 				auto region = CreateRectRgn(x, y, x + width, y + height);
9551 				SelectClipRgn(hdc, region);
9552 				DeleteObject(region);
9553 			}
9554 		}
9555 
9556 
9557 		// just because we can on Windows...
9558 		//void create(Image image);
9559 
9560 		void dispose() {
9561 			// FIXME: this.window.width/height is probably wrong
9562 			// BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY);
9563 			// ReleaseDC(hwnd, windowHdc);
9564 
9565 			// FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right
9566 			if(cast(SimpleWindow) this.window)
9567 			InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove
9568 
9569 			if(originalPen !is null)
9570 				SelectObject(hdc, originalPen);
9571 			if(currentPen !is null)
9572 				DeleteObject(currentPen);
9573 			if(originalBrush !is null)
9574 				SelectObject(hdc, originalBrush);
9575 			if(currentBrush !is null)
9576 				DeleteObject(currentBrush);
9577 
9578 			SelectObject(hdc, oldBmp);
9579 
9580 			if(windowDc)
9581 				ReleaseDC(hwnd, hdc);
9582 			else
9583 				DeleteDC(hdc);
9584 
9585 			if(window.paintingFinishedDg !is null)
9586 				window.paintingFinishedDg()();
9587 		}
9588 
9589 		bool windowDc;
9590 		HPEN originalPen;
9591 		HPEN currentPen;
9592 
9593 		Pen _activePen;
9594 
9595 		@property void pen(Pen p) {
9596 			_activePen = p;
9597 
9598 			HPEN pen;
9599 			if(p.color.a == 0) {
9600 				pen = GetStockObject(NULL_PEN);
9601 			} else {
9602 				int style = PS_SOLID;
9603 				final switch(p.style) {
9604 					case Pen.Style.Solid:
9605 						style = PS_SOLID;
9606 					break;
9607 					case Pen.Style.Dashed:
9608 						style = PS_DASH;
9609 					break;
9610 					case Pen.Style.Dotted:
9611 						style = PS_DOT;
9612 					break;
9613 				}
9614 				pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b));
9615 			}
9616 			auto orig = SelectObject(hdc, pen);
9617 			if(originalPen is null)
9618 				originalPen = orig;
9619 
9620 			if(currentPen !is null)
9621 				DeleteObject(currentPen);
9622 
9623 			currentPen = pen;
9624 
9625 			// the outline is like a foreground since it's done that way on X
9626 			SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b));
9627 
9628 		}
9629 
9630 		@property void rasterOp(RasterOp op) {
9631 			int mode;
9632 			final switch(op) {
9633 				case RasterOp.normal:
9634 					mode = R2_COPYPEN;
9635 				break;
9636 				case RasterOp.xor:
9637 					mode = R2_XORPEN;
9638 				break;
9639 			}
9640 			SetROP2(hdc, mode);
9641 		}
9642 
9643 		HBRUSH originalBrush;
9644 		HBRUSH currentBrush;
9645 		Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this??
9646 		@property void fillColor(Color c) {
9647 			if(c == _fillColor)
9648 				return;
9649 			_fillColor = c;
9650 			HBRUSH brush;
9651 			if(c.a == 0) {
9652 				brush = GetStockObject(HOLLOW_BRUSH);
9653 			} else {
9654 				brush = CreateSolidBrush(RGB(c.r, c.g, c.b));
9655 			}
9656 			auto orig = SelectObject(hdc, brush);
9657 			if(originalBrush is null)
9658 				originalBrush = orig;
9659 
9660 			if(currentBrush !is null)
9661 				DeleteObject(currentBrush);
9662 
9663 			currentBrush = brush;
9664 
9665 			// background color is NOT set because X doesn't draw text backgrounds
9666 			//   SetBkColor(hdc, RGB(255, 255, 255));
9667 		}
9668 
9669 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
9670 			BITMAP bm;
9671 
9672 			HDC hdcMem = CreateCompatibleDC(hdc);
9673 			HBITMAP hbmOld = SelectObject(hdcMem, i.handle);
9674 
9675 			GetObject(i.handle, bm.sizeof, &bm);
9676 
9677 			// or should I AlphaBlend!??!?!
9678 			BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY);
9679 
9680 			SelectObject(hdcMem, hbmOld);
9681 			DeleteDC(hdcMem);
9682 		}
9683 
9684 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
9685 			BITMAP bm;
9686 
9687 			HDC hdcMem = CreateCompatibleDC(hdc);
9688 			HBITMAP hbmOld = SelectObject(hdcMem, s.handle);
9689 
9690 			GetObject(s.handle, bm.sizeof, &bm);
9691 
9692 			// or should I AlphaBlend!??!?! note it is supposed to be premultiplied  http://www.fengyuan.com/article/alphablend.html
9693 			BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY);
9694 
9695 			SelectObject(hdcMem, hbmOld);
9696 			DeleteDC(hdcMem);
9697 		}
9698 
9699 		Size textSize(scope const(char)[] text) {
9700 			bool dummyX;
9701 			if(text.length == 0) {
9702 				text = " ";
9703 				dummyX = true;
9704 			}
9705 			RECT rect;
9706 			WCharzBuffer buffer = WCharzBuffer(text);
9707 			DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT);
9708 			return Size(dummyX ? 0 : rect.right, rect.bottom);
9709 		}
9710 
9711 		void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) {
9712 			if(text.length && text[$-1] == '\n')
9713 				text = text[0 .. $-1]; // tailing newlines are weird on windows...
9714 			if(text.length && text[$-1] == '\r')
9715 				text = text[0 .. $-1];
9716 
9717 			WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines);
9718 			if(x2 == 0 && y2 == 0)
9719 				TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length);
9720 			else {
9721 				RECT rect;
9722 				rect.left = x;
9723 				rect.top = y;
9724 				rect.right = x2;
9725 				rect.bottom = y2;
9726 
9727 				uint mode = DT_LEFT;
9728 				if(alignment & TextAlignment.Right)
9729 					mode = DT_RIGHT;
9730 				else if(alignment & TextAlignment.Center)
9731 					mode = DT_CENTER;
9732 
9733 				// FIXME: vcenter on windows only works with single line, but I want it to work in all cases
9734 				if(alignment & TextAlignment.VerticalCenter)
9735 					mode |= DT_VCENTER | DT_SINGLELINE;
9736 
9737 				DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode);
9738 			}
9739 
9740 			/*
9741 			uint mode;
9742 
9743 			if(alignment & TextAlignment.Center)
9744 				mode = TA_CENTER;
9745 
9746 			SetTextAlign(hdc, mode);
9747 			*/
9748 		}
9749 
9750 		int fontHeight() {
9751 			TEXTMETRIC metric;
9752 			if(GetTextMetricsW(hdc, &metric)) {
9753 				return metric.tmHeight;
9754 			}
9755 
9756 			return 16; // idk just guessing here, maybe we should throw
9757 		}
9758 
9759 		void drawPixel(int x, int y) {
9760 			SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b));
9761 		}
9762 
9763 		// The basic shapes, outlined
9764 
9765 		void drawLine(int x1, int y1, int x2, int y2) {
9766 			MoveToEx(hdc, x1, y1, null);
9767 			LineTo(hdc, x2, y2);
9768 		}
9769 
9770 		void drawRectangle(int x, int y, int width, int height) {
9771 			gdi.Rectangle(hdc, x, y, x + width, y + height);
9772 		}
9773 
9774 		/// Arguments are the points of the bounding rectangle
9775 		void drawEllipse(int x1, int y1, int x2, int y2) {
9776 			Ellipse(hdc, x1, y1, x2, y2);
9777 		}
9778 
9779 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
9780 			if((start % (360*64)) == (finish % (360*64)))
9781 				drawEllipse(x1, y1, x1 + width, y1 + height);
9782 			else {
9783 				import core.stdc.math;
9784 				float startAngle = start * 64 * 180 / 3.14159265;
9785 				float endAngle = finish * 64 * 180 / 3.14159265;
9786 				Arc(hdc, x1, y1, x1 + width, y1 + height,
9787 					cast(int)(cos(startAngle) * width / 2 + x1),
9788 					cast(int)(sin(startAngle) * height / 2 + y1),
9789 					cast(int)(cos(endAngle) * width / 2 + x1),
9790 					cast(int)(sin(endAngle) * height / 2 + y1),
9791 				);
9792 			}
9793 		}
9794 
9795 		void drawPolygon(Point[] vertexes) {
9796 			POINT[] points;
9797 			points.length = vertexes.length;
9798 
9799 			foreach(i, p; vertexes) {
9800 				points[i].x = p.x;
9801 				points[i].y = p.y;
9802 			}
9803 
9804 			Polygon(hdc, points.ptr, cast(int) points.length);
9805 		}
9806 	}
9807 
9808 
9809 	// Mix this into the SimpleWindow class
9810 	mixin template NativeSimpleWindowImplementation() {
9811 		int curHidden = 0; // counter
9812 		__gshared static bool[string] knownWinClasses;
9813 		static bool altPressed = false;
9814 
9815 		HANDLE oldCursor;
9816 
9817 		void hideCursor () {
9818 			if(curHidden == 0)
9819 				oldCursor = SetCursor(null);
9820 			++curHidden;
9821 		}
9822 
9823 		void showCursor () {
9824 			--curHidden;
9825 			if(curHidden == 0) {
9826 				SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement
9827 			}
9828 		}
9829 
9830 
9831 		int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max;
9832 
9833 		void setMinSize (int minwidth, int minheight) {
9834 			minWidth = minwidth;
9835 			minHeight = minheight;
9836 		}
9837 		void setMaxSize (int maxwidth, int maxheight) {
9838 			maxWidth = maxwidth;
9839 			maxHeight = maxheight;
9840 		}
9841 
9842 		// FIXME i'm not sure that Windows has this functionality
9843 		// though it is nonessential anyway.
9844 		void setResizeGranularity (int granx, int grany) {}
9845 
9846 		ScreenPainter getPainter() {
9847 			return ScreenPainter(this, hwnd);
9848 		}
9849 
9850 		HBITMAP buffer;
9851 
9852 		void setTitle(string title) {
9853 			WCharzBuffer bfr = WCharzBuffer(title);
9854 			SetWindowTextW(hwnd, bfr.ptr);
9855 		}
9856 
9857 		string getTitle() {
9858 			auto len = GetWindowTextLengthW(hwnd);
9859 			if (!len)
9860 				return null;
9861 			wchar[256] tmpBuffer;
9862 			wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] :  new wchar[len];
9863 			auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
9864 			auto str = buffer[0 .. len2];
9865 			return makeUtf8StringFromWindowsString(str);
9866 		}
9867 
9868 		void move(int x, int y) {
9869 			RECT rect;
9870 			GetWindowRect(hwnd, &rect);
9871 			// move it while maintaining the same size...
9872 			MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true);
9873 		}
9874 
9875 		void resize(int w, int h) {
9876 			RECT rect;
9877 			GetWindowRect(hwnd, &rect);
9878 
9879 			RECT client;
9880 			GetClientRect(hwnd, &client);
9881 
9882 			rect.right = rect.right - client.right + w;
9883 			rect.bottom = rect.bottom - client.bottom + h;
9884 
9885 			// same position, new size for the client rectangle
9886 			MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true);
9887 
9888 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
9889 		}
9890 
9891 		void moveResize (int x, int y, int w, int h) {
9892 			// what's given is the client rectangle, we need to adjust
9893 
9894 			RECT rect;
9895 			rect.left = x;
9896 			rect.top = y;
9897 			rect.right = w + x;
9898 			rect.bottom = h + y;
9899 			if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null))
9900 				throw new Exception("AdjustWindowRect");
9901 
9902 			MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true);
9903 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
9904 			if (windowResized !is null) windowResized(w, h);
9905 		}
9906 
9907 		version(without_opengl) {} else {
9908 			HGLRC ghRC;
9909 			HDC ghDC;
9910 		}
9911 
9912 		void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
9913 			string cnamec;
9914 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
9915 			if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) {
9916 				cnamec = "DSimpleWindow";
9917 			} else {
9918 				cnamec = sdpyWindowClass;
9919 			}
9920 
9921 			WCharzBuffer cn = WCharzBuffer(cnamec);
9922 
9923 			HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
9924 
9925 			if(cnamec !in knownWinClasses) {
9926 				WNDCLASSEX wc;
9927 
9928 				// FIXME: I might be able to use cbWndExtra to hold the pointer back
9929 				// to the object. Maybe.
9930 				wc.cbSize = wc.sizeof;
9931 				wc.cbClsExtra = 0;
9932 				wc.cbWndExtra = 0;
9933 				wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH);
9934 				wc.hCursor = LoadCursorW(null, IDC_ARROW);
9935 				wc.hIcon = LoadIcon(hInstance, null);
9936 				wc.hInstance = hInstance;
9937 				wc.lpfnWndProc = &WndProc;
9938 				wc.lpszClassName = cn.ptr;
9939 				wc.hIconSm = null;
9940 				wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
9941 				if(!RegisterClassExW(&wc))
9942 					throw new WindowsApiException("RegisterClassExW");
9943 				knownWinClasses[cnamec] = true;
9944 			}
9945 
9946 			int style;
9947 			uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files
9948 
9949 			// FIXME: windowType and customizationFlags
9950 			final switch(windowType) {
9951 				case WindowTypes.normal:
9952 					style = WS_OVERLAPPEDWINDOW;
9953 				break;
9954 				case WindowTypes.undecorated:
9955 					style = WS_POPUP | WS_SYSMENU;
9956 				break;
9957 				case WindowTypes.eventOnly:
9958 					_hidden = true;
9959 				break;
9960 				case WindowTypes.dropdownMenu:
9961 				case WindowTypes.popupMenu:
9962 				case WindowTypes.notification:
9963 					style = WS_POPUP;
9964 					flags |= WS_EX_NOACTIVATE;
9965 				break;
9966 				case WindowTypes.nestedChild:
9967 					style = WS_CHILD;
9968 				break;
9969 			}
9970 
9971 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
9972 				flags |= WS_EX_LAYERED; // composite window for better performance and effects support
9973 
9974 			hwnd = CreateWindowEx(flags, cn.ptr, toWStringz(title), style | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, // the clip children helps avoid flickering in minigui and doesn't seem to harm other use (mostly, sdpy is no child windows anyway) sooo i think it is ok
9975 				CW_USEDEFAULT, CW_USEDEFAULT, width, height,
9976 				parent is null ? null : parent.impl.hwnd, null, hInstance, null);
9977 
9978 			if ((customizationFlags & WindowFlags.extraComposite) != 0)
9979 				setOpacity(255);
9980 
9981 			SimpleWindow.nativeMapping[hwnd] = this;
9982 			CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this;
9983 
9984 			if(windowType == WindowTypes.eventOnly)
9985 				return;
9986 
9987 			HDC hdc = GetDC(hwnd);
9988 
9989 
9990 			version(without_opengl) {}
9991 			else {
9992 				if(opengl == OpenGlOptions.yes) {
9993 					if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
9994 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
9995 					static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports
9996 					ghDC = hdc;
9997 					PIXELFORMATDESCRIPTOR pfd;
9998 
9999 					pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
10000 					pfd.nVersion = 1;
10001 					pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |  PFD_DOUBLEBUFFER;
10002 					pfd.dwLayerMask = PFD_MAIN_PLANE;
10003 					pfd.iPixelType = PFD_TYPE_RGBA;
10004 					pfd.cColorBits = 24;
10005 					pfd.cDepthBits = 24;
10006 					pfd.cAccumBits = 0;
10007 					pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway
10008 
10009 					auto pixelformat = ChoosePixelFormat(hdc, &pfd);
10010 
10011 					if (pixelformat == 0)
10012 						throw new WindowsApiException("ChoosePixelFormat");
10013 
10014 					if (SetPixelFormat(hdc, pixelformat, &pfd) == 0)
10015 						throw new WindowsApiException("SetPixelFormat");
10016 
10017 					if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) {
10018 						// windoze is idiotic: we have to have OpenGL context to get function addresses
10019 						// so we will create fake context to get that stupid address
10020 						auto tmpcc = wglCreateContext(ghDC);
10021 						if (tmpcc !is null) {
10022 							scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); }
10023 							wglMakeCurrent(ghDC, tmpcc);
10024 							wglInitOtherFunctions();
10025 						}
10026 					}
10027 
10028 					if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) {
10029 						int[9] contextAttribs = [
10030 							WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
10031 							WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
10032 							WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB),
10033 							// for modern context, set "forward compatibility" flag too
10034 							(sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
10035 							0/*None*/,
10036 						];
10037 						ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr);
10038 						if (ghRC is null && sdpyOpenGLContextAllowFallback) {
10039 							// activate fallback mode
10040 							// sdpyOpenGLContextVeto-type focus management policy leads to race conditions because the window becoming unviewable may coincide with the window manager deciding to move the focus elsrsion = 0;
10041 							ghRC = wglCreateContext(ghDC);
10042 						}
10043 						if (ghRC is null)
10044 							throw new WindowsApiException("wglCreateContextAttribsARB");
10045 					} else {
10046 						// try to do at least something
10047 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
10048 							sdpyOpenGLContextVersion = 0;
10049 							ghRC = wglCreateContext(ghDC);
10050 						}
10051 						if (ghRC is null)
10052 							throw new WindowsApiException("wglCreateContext");
10053 					}
10054 				}
10055 			}
10056 
10057 			if(opengl == OpenGlOptions.no) {
10058 				buffer = CreateCompatibleBitmap(hdc, width, height);
10059 
10060 				auto hdcBmp = CreateCompatibleDC(hdc);
10061 				// make sure it's filled with a blank slate
10062 				auto oldBmp = SelectObject(hdcBmp, buffer);
10063 				auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH));
10064 				auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN));
10065 				gdi.Rectangle(hdcBmp, 0, 0, width, height);
10066 				SelectObject(hdcBmp, oldBmp);
10067 				SelectObject(hdcBmp, oldBrush);
10068 				SelectObject(hdcBmp, oldPen);
10069 				DeleteDC(hdcBmp);
10070 
10071 				ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now
10072 			}
10073 
10074 			// We want the window's client area to match the image size
10075 			RECT rcClient, rcWindow;
10076 			POINT ptDiff;
10077 			GetClientRect(hwnd, &rcClient);
10078 			GetWindowRect(hwnd, &rcWindow);
10079 			ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
10080 			ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
10081 			MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true);
10082 
10083 			if ((customizationFlags&WindowFlags.dontAutoShow) == 0) {
10084 				ShowWindow(hwnd, SW_SHOWNORMAL);
10085 			} else {
10086 				_hidden = true;
10087 			}
10088 			this._visibleForTheFirstTimeCalled = false; // hack!
10089 		}
10090 
10091 
10092 		void dispose() {
10093 			if(buffer)
10094 				DeleteObject(buffer);
10095 		}
10096 
10097 		void closeWindow() {
10098 			DestroyWindow(hwnd);
10099 		}
10100 
10101 		bool setOpacity(ubyte alpha) {
10102 			return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE;
10103 		}
10104 
10105 		HANDLE currentCursor;
10106 
10107 		// returns zero if it recognized the event
10108 		static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) {
10109 			MouseEvent mouse;
10110 
10111 			void mouseEvent(bool isScreen, ulong mods) {
10112 				auto x = LOWORD(lParam);
10113 				auto y = HIWORD(lParam);
10114 				if(isScreen) {
10115 					POINT p;
10116 					p.x = x;
10117 					p.y = y;
10118 					ScreenToClient(hwnd, &p);
10119 					x = cast(ushort) p.x;
10120 					y = cast(ushort) p.y;
10121 				}
10122 				mouse.x = x + offsetX;
10123 				mouse.y = y + offsetY;
10124 
10125 				wind.mdx(mouse);
10126 				mouse.modifierState = cast(int) mods;
10127 				mouse.window = wind;
10128 
10129 				if(wind.handleMouseEvent)
10130 					wind.handleMouseEvent(mouse);
10131 			}
10132 
10133 			switch(msg) {
10134 				case WM_GETMINMAXINFO:
10135 					MINMAXINFO* mmi = cast(MINMAXINFO*) lParam;
10136 
10137 					if(wind.minWidth > 0) {
10138 						RECT rect;
10139 						rect.left = 100;
10140 						rect.top = 100;
10141 						rect.right = wind.minWidth + 100;
10142 						rect.bottom = wind.minHeight + 100;
10143 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
10144 							throw new WindowsApiException("AdjustWindowRect");
10145 
10146 						mmi.ptMinTrackSize.x = rect.right - rect.left;
10147 						mmi.ptMinTrackSize.y = rect.bottom - rect.top;
10148 					}
10149 
10150 					if(wind.maxWidth < int.max) {
10151 						RECT rect;
10152 						rect.left = 100;
10153 						rect.top = 100;
10154 						rect.right = wind.maxWidth + 100;
10155 						rect.bottom = wind.maxHeight + 100;
10156 						if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null))
10157 							throw new WindowsApiException("AdjustWindowRect");
10158 
10159 						mmi.ptMaxTrackSize.x = rect.right - rect.left;
10160 						mmi.ptMaxTrackSize.y = rect.bottom - rect.top;
10161 					}
10162 				break;
10163 				case WM_CHAR:
10164 					wchar c = cast(wchar) wParam;
10165 					if(wind.handleCharEvent)
10166 						wind.handleCharEvent(cast(dchar) c);
10167 				break;
10168 				  case WM_SETFOCUS:
10169 				  case WM_KILLFOCUS:
10170 					wind._focused = (msg == WM_SETFOCUS);
10171 					if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...)
10172 					if(wind.onFocusChange)
10173 						wind.onFocusChange(msg == WM_SETFOCUS);
10174 				  break;
10175 				case WM_SYSKEYDOWN:
10176 					goto case;
10177 				case WM_SYSKEYUP:
10178 					if(lParam & (1 << 29)) {
10179 						goto case;
10180 					} else {
10181 						// no window has keyboard focus
10182 						goto default;
10183 					}
10184 				case WM_KEYDOWN:
10185 				case WM_KEYUP:
10186 					KeyEvent ev;
10187 					ev.key = cast(Key) wParam;
10188 					ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
10189 					if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way
10190 
10191 					ev.hardwareCode = (lParam & 0xff0000) >> 16;
10192 
10193 					if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
10194 						ev.modifierState |= ModifierState.shift;
10195 					//k8: this doesn't work; thanks for nothing, windows
10196 					/*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
10197 						ev.modifierState |= ModifierState.alt;*/
10198 					if (wParam == 0x12) altPressed = (msg == WM_SYSKEYDOWN);
10199 					if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt;
10200 					if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
10201 						ev.modifierState |= ModifierState.ctrl;
10202 					if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
10203 						ev.modifierState |= ModifierState.windows;
10204 					if(GetKeyState(Key.NumLock))
10205 						ev.modifierState |= ModifierState.numLock;
10206 					if(GetKeyState(Key.CapsLock))
10207 						ev.modifierState |= ModifierState.capsLock;
10208 
10209 					/+
10210 					// we always want to send the character too, so let's convert it
10211 					ubyte[256] state;
10212 					wchar[16] buffer;
10213 					GetKeyboardState(state.ptr);
10214 					ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null);
10215 
10216 					foreach(dchar d; buffer) {
10217 						ev.character = d;
10218 						break;
10219 					}
10220 					+/
10221 
10222 					ev.window = wind;
10223 					if(wind.handleKeyEvent)
10224 						wind.handleKeyEvent(ev);
10225 				break;
10226 				case 0x020a /*WM_MOUSEWHEEL*/:
10227 					// send click
10228 					mouse.type = cast(MouseEventType) 1;
10229 					mouse.button = ((HIWORD(wParam) > 120) ? MouseButton.wheelDown : MouseButton.wheelUp);
10230 					mouseEvent(true, LOWORD(wParam));
10231 
10232 					// also send release
10233 					mouse.type = cast(MouseEventType) 2;
10234 					mouse.button = ((HIWORD(wParam) > 120) ? MouseButton.wheelDown : MouseButton.wheelUp);
10235 					mouseEvent(true, LOWORD(wParam));
10236 				break;
10237 				case WM_MOUSEMOVE:
10238 					mouse.type = cast(MouseEventType) 0;
10239 					mouseEvent(false, wParam);
10240 				break;
10241 				case WM_LBUTTONDOWN:
10242 				case WM_LBUTTONDBLCLK:
10243 					mouse.type = cast(MouseEventType) 1;
10244 					mouse.button = MouseButton.left;
10245 					mouse.doubleClick = msg == WM_LBUTTONDBLCLK;
10246 					mouseEvent(false, wParam);
10247 				break;
10248 				case WM_LBUTTONUP:
10249 					mouse.type = cast(MouseEventType) 2;
10250 					mouse.button = MouseButton.left;
10251 					mouseEvent(false, wParam);
10252 				break;
10253 				case WM_RBUTTONDOWN:
10254 				case WM_RBUTTONDBLCLK:
10255 					mouse.type = cast(MouseEventType) 1;
10256 					mouse.button = MouseButton.right;
10257 					mouse.doubleClick = msg == WM_RBUTTONDBLCLK;
10258 					mouseEvent(false, wParam);
10259 				break;
10260 				case WM_RBUTTONUP:
10261 					mouse.type = cast(MouseEventType) 2;
10262 					mouse.button = MouseButton.right;
10263 					mouseEvent(false, wParam);
10264 				break;
10265 				case WM_MBUTTONDOWN:
10266 				case WM_MBUTTONDBLCLK:
10267 					mouse.type = cast(MouseEventType) 1;
10268 					mouse.button = MouseButton.middle;
10269 					mouse.doubleClick = msg == WM_MBUTTONDBLCLK;
10270 					mouseEvent(false, wParam);
10271 				break;
10272 				case WM_MBUTTONUP:
10273 					mouse.type = cast(MouseEventType) 2;
10274 					mouse.button = MouseButton.middle;
10275 					mouseEvent(false, wParam);
10276 				break;
10277 				case WM_XBUTTONDOWN:
10278 				case WM_XBUTTONDBLCLK:
10279 					mouse.type = cast(MouseEventType) 1;
10280 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
10281 					mouse.doubleClick = msg == WM_XBUTTONDBLCLK;
10282 					mouseEvent(false, wParam);
10283 				return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs
10284 				case WM_XBUTTONUP:
10285 					mouse.type = cast(MouseEventType) 2;
10286 					mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton;
10287 					mouseEvent(false, wParam);
10288 				return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx
10289 
10290 				default: return 1;
10291 			}
10292 			return 0;
10293 		}
10294 
10295 		HWND hwnd;
10296 		int oldWidth;
10297 		int oldHeight;
10298 		bool inSizeMove;
10299 
10300 		// the extern(Windows) wndproc should just forward to this
10301 		LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) {
10302 			assert(hwnd is this.hwnd);
10303 
10304 			if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this))
10305 			switch(msg) {
10306 				case WM_SETCURSOR:
10307 					if(cast(HWND) wParam !is hwnd)
10308 						return 0; // further processing elsewhere
10309 
10310 					if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) {
10311 						SetCursor(this.curHidden > 0 ? null : currentCursor);
10312 						return 1;
10313 					} else {
10314 						return DefWindowProc(hwnd, msg, wParam, lParam);
10315 					}
10316 				//break;
10317 
10318 				case WM_CLOSE:
10319 					if (this.closeQuery !is null) this.closeQuery(); else this.close();
10320 				break;
10321 				case WM_DESTROY:
10322 					if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry
10323 					SimpleWindow.nativeMapping.remove(hwnd);
10324 					CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd);
10325 
10326 					bool anyImportant = false;
10327 					foreach(SimpleWindow w; SimpleWindow.nativeMapping)
10328 						if(w.beingOpenKeepsAppOpen) {
10329 							anyImportant = true;
10330 							break;
10331 						}
10332 					if(!anyImportant) {
10333 						PostQuitMessage(0);
10334 					}
10335 				break;
10336 				case WM_SIZE:
10337 					if(wParam == 1 /* SIZE_MINIMIZED */)
10338 						break;
10339 					_width = LOWORD(lParam);
10340 					_height = HIWORD(lParam);
10341 
10342 					// I want to avoid tearing in the windows (my code is inefficient
10343 					// so this is a hack around that) so while sizing, we don't trigger,
10344 					// but we do want to trigger on events like mazimize.
10345 					if(!inSizeMove)
10346 						goto size_changed;
10347 				break;
10348 				// I don't like the tearing I get when redrawing on WM_SIZE
10349 				// (I know there's other ways to fix that but I don't like that behavior anyway)
10350 				// so instead it is going to redraw only at the end of a size.
10351 				case 0x0231: /* WM_ENTERSIZEMOVE */
10352 					oldWidth = this.width;
10353 					oldHeight = this.height;
10354 					inSizeMove = true;
10355 				break;
10356 				case 0x0232: /* WM_EXITSIZEMOVE */
10357 					inSizeMove = false;
10358 					// nothing relevant changed, don't bother redrawing
10359 					if(oldWidth == width && oldHeight == height)
10360 						break;
10361 
10362 					size_changed:
10363 
10364 					// note: OpenGL windows don't use a backing bmp, so no need to change them
10365 					// if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing
10366 					if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) {
10367 						// gotta get the double buffer bmp to match the window
10368 					// FIXME: could this be more efficient? It isn't really necessary to make
10369 					// a new buffer if we're sizing down at least.
10370 						auto hdc = GetDC(hwnd);
10371 						auto oldBuffer = buffer;
10372 						buffer = CreateCompatibleBitmap(hdc, width, height);
10373 
10374 						auto hdcBmp = CreateCompatibleDC(hdc);
10375 						auto oldBmp = SelectObject(hdcBmp, buffer);
10376 
10377 						auto hdcOldBmp = CreateCompatibleDC(hdc);
10378 						auto oldOldBmp = SelectObject(hdcOldBmp, oldBmp);
10379 
10380 						BitBlt(hdcBmp, 0, 0, width, height, hdcOldBmp, oldWidth, oldHeight, SRCCOPY);
10381 
10382 						SelectObject(hdcOldBmp, oldOldBmp);
10383 						DeleteDC(hdcOldBmp);
10384 
10385 						SelectObject(hdcBmp, oldBmp);
10386 						DeleteDC(hdcBmp);
10387 
10388 						ReleaseDC(hwnd, hdc);
10389 
10390 						DeleteObject(oldBuffer);
10391 					}
10392 
10393 					version(without_opengl) {} else
10394 					if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) {
10395 						glViewport(0, 0, width, height);
10396 					}
10397 
10398 					if(windowResized !is null)
10399 						windowResized(width, height);
10400 				break;
10401 				case WM_ERASEBKGND:
10402 					// call `visibleForTheFirstTime` here, so we can do initialization as early as possible
10403 					if (!this._visibleForTheFirstTimeCalled) {
10404 						this._visibleForTheFirstTimeCalled = true;
10405 						if (this.visibleForTheFirstTime !is null) {
10406 							version(without_opengl) {} else {
10407 								if(openglMode == OpenGlOptions.yes) {
10408 									this.setAsCurrentOpenGlContextNT();
10409 									glViewport(0, 0, width, height);
10410 								}
10411 							}
10412 							this.visibleForTheFirstTime();
10413 						}
10414 					}
10415 					// block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene
10416 					version(without_opengl) {} else {
10417 						if (openglMode == OpenGlOptions.yes) return 1;
10418 					}
10419 					// call windows default handler, so it can paint standard controls
10420 					goto default;
10421 				case WM_CTLCOLORBTN:
10422 				case WM_CTLCOLORSTATIC:
10423 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
10424 					return cast(typeof(return)) //GetStockObject(NULL_BRUSH);
10425 					GetSysColorBrush(COLOR_3DFACE);
10426 				//break;
10427 				case WM_SHOWWINDOW:
10428 					this._visible = (wParam != 0);
10429 					if (!this._visibleForTheFirstTimeCalled && this._visible) {
10430 						this._visibleForTheFirstTimeCalled = true;
10431 						if (this.visibleForTheFirstTime !is null) {
10432 							version(without_opengl) {} else {
10433 								if(openglMode == OpenGlOptions.yes) {
10434 									this.setAsCurrentOpenGlContextNT();
10435 									glViewport(0, 0, width, height);
10436 								}
10437 							}
10438 							this.visibleForTheFirstTime();
10439 						}
10440 					}
10441 					if (this.visibilityChanged !is null) this.visibilityChanged(this._visible);
10442 					break;
10443 				case WM_PAINT: {
10444 					if (!this._visibleForTheFirstTimeCalled) {
10445 						this._visibleForTheFirstTimeCalled = true;
10446 						if (this.visibleForTheFirstTime !is null) {
10447 							version(without_opengl) {} else {
10448 								if(openglMode == OpenGlOptions.yes) {
10449 									this.setAsCurrentOpenGlContextNT();
10450 									glViewport(0, 0, width, height);
10451 								}
10452 							}
10453 							this.visibleForTheFirstTime();
10454 						}
10455 					}
10456 
10457 					BITMAP bm;
10458 					PAINTSTRUCT ps;
10459 
10460 					HDC hdc = BeginPaint(hwnd, &ps);
10461 
10462 					if(openglMode == OpenGlOptions.no) {
10463 
10464 						HDC hdcMem = CreateCompatibleDC(hdc);
10465 						HBITMAP hbmOld = SelectObject(hdcMem, buffer);
10466 
10467 						GetObject(buffer, bm.sizeof, &bm);
10468 
10469 						// FIXME: only BitBlt the invalidated rectangle, not the whole thing
10470 						if(resizability == Resizability.automaticallyScaleIfPossible)
10471 						StretchBlt(hdc, 0, 0, this.width, this.height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
10472 						else
10473 						BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
10474 
10475 						SelectObject(hdcMem, hbmOld);
10476 						DeleteDC(hdcMem);
10477 						EndPaint(hwnd, &ps);
10478 					} else {
10479 						EndPaint(hwnd, &ps);
10480 						version(without_opengl) {} else
10481 							redrawOpenGlSceneNow();
10482 					}
10483 				} break;
10484 				  default:
10485 					return DefWindowProc(hwnd, msg, wParam, lParam);
10486 			}
10487 			 return 0;
10488 
10489 		}
10490 	}
10491 
10492 	mixin template NativeImageImplementation() {
10493 		HBITMAP handle;
10494 		ubyte* rawData;
10495 
10496 	final:
10497 
10498 		Color getPixel(int x, int y) {
10499 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
10500 			// remember, bmps are upside down
10501 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
10502 
10503 			Color c;
10504 			c.a = 255;
10505 			c.b = rawData[offset + 0];
10506 			c.g = rawData[offset + 1];
10507 			c.r = rawData[offset + 2];
10508 			return c;
10509 		}
10510 
10511 		void setPixel(int x, int y, Color c) {
10512 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
10513 			// remember, bmps are upside down
10514 			auto offset = itemsPerLine * (height - y - 1) + x * 3;
10515 
10516 			rawData[offset + 0] = c.b;
10517 			rawData[offset + 1] = c.g;
10518 			rawData[offset + 2] = c.r;
10519 		}
10520 
10521 		void convertToRgbaBytes(ubyte[] where) {
10522 			assert(where.length == this.width * this.height * 4);
10523 
10524 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
10525 			int idx = 0;
10526 			int offset = itemsPerLine * (height - 1);
10527 			// remember, bmps are upside down
10528 			for(int y = height - 1; y >= 0; y--) {
10529 				auto offsetStart = offset;
10530 				for(int x = 0; x < width; x++) {
10531 					where[idx + 0] = rawData[offset + 2]; // r
10532 					where[idx + 1] = rawData[offset + 1]; // g
10533 					where[idx + 2] = rawData[offset + 0]; // b
10534 					where[idx + 3] = 255; // a
10535 					idx += 4;
10536 					offset += 3;
10537 				}
10538 
10539 				offset = offsetStart - itemsPerLine;
10540 			}
10541 		}
10542 
10543 		void setFromRgbaBytes(in ubyte[] what) {
10544 			assert(what.length == this.width * this.height * 4);
10545 
10546 			auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4;
10547 			int idx = 0;
10548 			int offset = itemsPerLine * (height - 1);
10549 			// remember, bmps are upside down
10550 			for(int y = height - 1; y >= 0; y--) {
10551 				auto offsetStart = offset;
10552 				for(int x = 0; x < width; x++) {
10553 					rawData[offset + 2] = what[idx + 0]; // r
10554 					rawData[offset + 1] = what[idx + 1]; // g
10555 					rawData[offset + 0] = what[idx + 2]; // b
10556 					//where[idx + 3] = 255; // a
10557 					idx += 4;
10558 					offset += 3;
10559 				}
10560 
10561 				offset = offsetStart - itemsPerLine;
10562 			}
10563 		}
10564 
10565 
10566 		void createImage(int width, int height, bool forcexshm=false) {
10567 			BITMAPINFO infoheader;
10568 			infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof;
10569 			infoheader.bmiHeader.biWidth = width;
10570 			infoheader.bmiHeader.biHeight = height;
10571 			infoheader.bmiHeader.biPlanes = 1;
10572 			infoheader.bmiHeader.biBitCount = 24;
10573 			infoheader.bmiHeader.biCompression = BI_RGB;
10574 
10575 			handle = CreateDIBSection(
10576 				null,
10577 				&infoheader,
10578 				DIB_RGB_COLORS,
10579 				cast(void**) &rawData,
10580 				null,
10581 				0);
10582 			if(handle is null)
10583 				throw new WindowsApiException("create image failed");
10584 
10585 		}
10586 
10587 		void dispose() {
10588 			DeleteObject(handle);
10589 		}
10590 	}
10591 
10592 	enum KEY_ESCAPE = 27;
10593 }
10594 version(X11) {
10595 	/// This is the default font used. You might change this before doing anything else with
10596 	/// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)`
10597 	/// for cross-platform compatibility.
10598 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
10599 	//__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*";
10600 	__gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*";
10601 	//__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
10602 
10603 	alias int delegate(XEvent) NativeEventHandler;
10604 	alias Window NativeWindowHandle;
10605 
10606 	enum KEY_ESCAPE = 9;
10607 
10608 	mixin template NativeScreenPainterImplementation() {
10609 		Display* display;
10610 		Drawable d;
10611 		Drawable destiny;
10612 
10613 		// FIXME: should the gc be static too so it isn't recreated every time draw is called?
10614 		GC gc;
10615 
10616 		__gshared bool fontAttempted;
10617 
10618 		__gshared XFontStruct* defaultfont;
10619 		__gshared XFontSet defaultfontset;
10620 
10621 		XFontStruct* font;
10622 		XFontSet fontset;
10623 
10624 		void create(NativeWindowHandle window) {
10625 			this.display = XDisplayConnection.get();
10626 
10627 			Drawable buffer = None;
10628 			if(auto sw = cast(SimpleWindow) this.window) {
10629 				buffer = sw.impl.buffer;
10630 				this.destiny = cast(Drawable) window;
10631 			} else {
10632 				buffer = cast(Drawable) window;
10633 				this.destiny = None;
10634 			}
10635 
10636 			this.d = cast(Drawable) buffer;
10637 
10638 			auto dgc = DefaultGC(display, DefaultScreen(display));
10639 
10640 			this.gc = XCreateGC(display, d, 0, null);
10641 
10642 			XCopyGC(display, dgc, 0xffffffff, this.gc);
10643 
10644 			ensureDefaultFontLoaded();
10645 
10646 			font = defaultfont;
10647 			fontset = defaultfontset;
10648 
10649 			if(font) {
10650 				XSetFont(display, gc, font.fid);
10651 			}
10652 		}
10653 
10654 		static void ensureDefaultFontLoaded() {
10655 			if(!fontAttempted) {
10656 				auto display = XDisplayConnection.get;
10657 				auto font = XLoadQueryFont(display, xfontstr.ptr);
10658 				// if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either
10659 				if(font is null)
10660 					font = XLoadQueryFont(display, "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*".ptr);
10661 
10662 				char** lol;
10663 				int lol2;
10664 				char* lol3;
10665 				auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3);
10666 
10667 				fontAttempted = true;
10668 
10669 				defaultfont = font;
10670 				defaultfontset = fontset;
10671 			}
10672 		}
10673 
10674 		arsd.color.Rectangle _clipRectangle;
10675 		void setClipRectangle(int x, int y, int width, int height) {
10676 			_clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height));
10677 			if(width == 0 || height == 0)
10678 				XSetClipMask(display, gc, None);
10679 			else {
10680 				XRectangle[1] rects;
10681 				rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
10682 				XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
10683 			}
10684 		}
10685 
10686 		version(with_xft) {
10687 			XftFont* xftFont;
10688 			XftDraw* xftDraw;
10689 
10690 			XftColor xftColor;
10691 
10692 			void updateXftColor() {
10693 				if(xftFont is null)
10694 					return;
10695 
10696 				// not bothering with XftColorFree since p sure i don't need it on 24 bit displays....
10697 				XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255);
10698 
10699 				XftColorAllocValue(
10700 					display,
10701 					DefaultVisual(display, DefaultScreen(display)),
10702 					DefaultColormap(display, 0),
10703 					&colorIn,
10704 					&xftColor
10705 				);
10706 			}
10707 		}
10708 
10709 		void setFont(OperatingSystemFont font) {
10710 			version(with_xft) {
10711 				if(font && font.isXft && font.xftFont)
10712 					this.xftFont = font.xftFont;
10713 				else
10714 					this.xftFont = null;
10715 
10716 				if(this.xftFont) {
10717 					if(xftDraw is null) {
10718 						xftDraw = XftDrawCreate(
10719 							display,
10720 							d,
10721 							DefaultVisual(display, DefaultScreen(display)),
10722 							DefaultColormap(display, 0)
10723 						);
10724 
10725 						updateXftColor();
10726 					}
10727 
10728 					return;
10729 				}
10730 			}
10731 
10732 			if(font && font.font) {
10733 				this.font = font.font;
10734 				this.fontset = font.fontset;
10735 				XSetFont(display, gc, font.font.fid);
10736 			} else {
10737 				this.font = defaultfont;
10738 				this.fontset = defaultfontset;
10739 			}
10740 
10741 		}
10742 
10743 		void dispose() {
10744 			this.rasterOp = RasterOp.normal;
10745 
10746 			// FIXME: this.window.width/height is probably wrong
10747 
10748 			// src x,y     then dest x, y
10749 			if(destiny != None) {
10750 				XSetClipMask(display, gc, None);
10751 				XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0);
10752 			}
10753 
10754 			XFreeGC(display, gc);
10755 
10756 			version(with_xft)
10757 			if(xftDraw) {
10758 				XftDrawDestroy(xftDraw);
10759 				xftDraw = null;
10760 			}
10761 
10762 			version(none) // we don't want to free it because we can use it later
10763 			if(font)
10764 				XFreeFont(display, font);
10765 			version(none) // we don't want to free it because we can use it later
10766 			if(fontset)
10767 				XFreeFontSet(display, fontset);
10768 			XFlush(display);
10769 
10770 			if(window.paintingFinishedDg !is null)
10771 				window.paintingFinishedDg()();
10772 		}
10773 
10774 		bool backgroundIsNotTransparent = true;
10775 		bool foregroundIsNotTransparent = true;
10776 
10777 		bool _penInitialized = false;
10778 		Pen _activePen;
10779 
10780 		Color _outlineColor;
10781 		Color _fillColor;
10782 
10783 		@property void pen(Pen p) {
10784 			if(_penInitialized && p == _activePen) {
10785 				return;
10786 			}
10787 			_penInitialized = true;
10788 			_activePen = p;
10789 			_outlineColor = p.color;
10790 
10791 			int style;
10792 
10793 			byte dashLength;
10794 
10795 			final switch(p.style) {
10796 				case Pen.Style.Solid:
10797 					style = 0 /*LineSolid*/;
10798 				break;
10799 				case Pen.Style.Dashed:
10800 					style = 1 /*LineOnOffDash*/;
10801 					dashLength = 4;
10802 				break;
10803 				case Pen.Style.Dotted:
10804 					style = 1 /*LineOnOffDash*/;
10805 					dashLength = 1;
10806 				break;
10807 			}
10808 
10809 			XSetLineAttributes(display, gc, p.width, style, 0, 0);
10810 			if(dashLength)
10811 				XSetDashes(display, gc, 0, &dashLength, 1);
10812 
10813 			if(p.color.a == 0) {
10814 				foregroundIsNotTransparent = false;
10815 				return;
10816 			}
10817 
10818 			foregroundIsNotTransparent = true;
10819 
10820 			XSetForeground(display, gc, colorToX(p.color, display));
10821 
10822 			version(with_xft)
10823 				updateXftColor();
10824 		}
10825 
10826 		RasterOp _currentRasterOp;
10827 		bool _currentRasterOpInitialized = false;
10828 		@property void rasterOp(RasterOp op) {
10829 			if(_currentRasterOpInitialized && _currentRasterOp == op)
10830 				return;
10831 			_currentRasterOp = op;
10832 			_currentRasterOpInitialized = true;
10833 			int mode;
10834 			final switch(op) {
10835 				case RasterOp.normal:
10836 					mode = GXcopy;
10837 				break;
10838 				case RasterOp.xor:
10839 					mode = GXxor;
10840 				break;
10841 			}
10842 			XSetFunction(display, gc, mode);
10843 		}
10844 
10845 
10846 		bool _fillColorInitialized = false;
10847 
10848 		@property void fillColor(Color c) {
10849 			if(_fillColorInitialized && _fillColor == c)
10850 				return; // already good, no need to waste time calling it
10851 			_fillColor = c;
10852 			_fillColorInitialized = true;
10853 			if(c.a == 0) {
10854 				backgroundIsNotTransparent = false;
10855 				return;
10856 			}
10857 
10858 			backgroundIsNotTransparent = true;
10859 
10860 			XSetBackground(display, gc, colorToX(c, display));
10861 
10862 		}
10863 
10864 		void swapColors() {
10865 			auto tmp = _fillColor;
10866 			fillColor = _outlineColor;
10867 			auto newPen = _activePen;
10868 			newPen.color = tmp;
10869 			pen(newPen);
10870 		}
10871 
10872 		uint colorToX(Color c, Display* display) {
10873 			auto visual = DefaultVisual(display, DefaultScreen(display));
10874 			import core.bitop;
10875 			uint color = 0;
10876 			{
10877 			auto startBit = bsf(visual.red_mask);
10878 			auto lastBit = bsr(visual.red_mask);
10879 			auto r = cast(uint) c.r;
10880 			r >>= 7 - (lastBit - startBit);
10881 			r <<= startBit;
10882 			color |= r;
10883 			}
10884 			{
10885 			auto startBit = bsf(visual.green_mask);
10886 			auto lastBit = bsr(visual.green_mask);
10887 			auto g = cast(uint) c.g;
10888 			g >>= 7 - (lastBit - startBit);
10889 			g <<= startBit;
10890 			color |= g;
10891 			}
10892 			{
10893 			auto startBit = bsf(visual.blue_mask);
10894 			auto lastBit = bsr(visual.blue_mask);
10895 			auto b = cast(uint) c.b;
10896 			b >>= 7 - (lastBit - startBit);
10897 			b <<= startBit;
10898 			color |= b;
10899 			}
10900 
10901 
10902 
10903 			return color;
10904 		}
10905 
10906 		void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
10907 			// source x, source y
10908 			if(ix >= i.width) return;
10909 			if(iy >= i.height) return;
10910 			if(ix + w > i.width) w = i.width - ix;
10911 			if(iy + h > i.height) h = i.height - iy;
10912 			if(i.usingXshm)
10913 				XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
10914 			else
10915 				XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
10916 		}
10917 
10918 		void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) {
10919 			XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y);
10920 		}
10921 
10922 		int fontHeight() {
10923 			version(with_xft)
10924 				if(xftFont !is null)
10925 					return xftFont.height;
10926 			if(font)
10927 				return font.max_bounds.ascent + font.max_bounds.descent;
10928 			return 12; // pretty common default...
10929 		}
10930 
10931 		int textWidth(in char[] line) {
10932 			version(with_xft)
10933 			if(xftFont) {
10934 				if(line.length == 0)
10935 					return 0;
10936 				XGlyphInfo extents;
10937 				XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents);
10938 				return extents.width;
10939 			}
10940 
10941 			if(fontset) {
10942 				if(line.length == 0)
10943 					return 0;
10944 				XRectangle rect;
10945 				Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect);
10946 
10947 				return rect.width;
10948 			}
10949 
10950 			if(font)
10951 				// FIXME: unicode
10952 				return XTextWidth( font, line.ptr, cast(int) line.length);
10953 			else
10954 				return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio
10955 		}
10956 
10957 		Size textSize(in char[] text) {
10958 			auto maxWidth = 0;
10959 			auto lineHeight = fontHeight;
10960 			int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height
10961 			foreach(line; text.split('\n')) {
10962 				int textWidth = this.textWidth(line);
10963 				if(textWidth > maxWidth)
10964 					maxWidth = textWidth;
10965 				h += lineHeight + 4;
10966 			}
10967 			return Size(maxWidth, h);
10968 		}
10969 
10970 		void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) {
10971 			const(char)[] text;
10972 			version(with_xft)
10973 			if(xftFont) {
10974 				text = originalText;
10975 				goto loaded;
10976 			}
10977 
10978 			if(fontset)
10979 				text = originalText;
10980 			else {
10981 				text.reserve(originalText.length);
10982 				// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
10983 				// then strip the rest so there isn't garbage
10984 				foreach(dchar ch; originalText)
10985 					if(ch < 256)
10986 						text ~= cast(ubyte) ch;
10987 					else
10988 						text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space
10989 			}
10990 			loaded:
10991 			if(text.length == 0)
10992 				return;
10993 
10994 			// FIXME: should we clip it to the bounding box?
10995 			int textHeight = fontHeight;
10996 
10997 			auto lines = text.split('\n');
10998 
10999 			const lineHeight = textHeight;
11000 			textHeight *= lines.length;
11001 
11002 			int cy = y;
11003 
11004 			if(alignment & TextAlignment.VerticalBottom) {
11005 				assert(y2);
11006 				auto h = y2 - y;
11007 				if(h > textHeight) {
11008 					cy += h - textHeight;
11009 					cy -= lineHeight / 2;
11010 				}
11011 			} else if(alignment & TextAlignment.VerticalCenter) {
11012 				assert(y2);
11013 				auto h = y2 - y;
11014 				if(textHeight < h) {
11015 					cy += (h - textHeight) / 2;
11016 					//cy -= lineHeight / 4;
11017 				}
11018 			}
11019 
11020 			foreach(line; text.split('\n')) {
11021 				int textWidth = this.textWidth(line);
11022 
11023 				int px = x, py = cy;
11024 
11025 				if(alignment & TextAlignment.Center) {
11026 					assert(x2);
11027 					auto w = x2 - x;
11028 					if(w > textWidth)
11029 						px += (w - textWidth) / 2;
11030 				} else if(alignment & TextAlignment.Right) {
11031 					assert(x2);
11032 					auto pos = x2 - textWidth;
11033 					if(pos > x)
11034 						px = pos;
11035 				}
11036 
11037 				version(with_xft)
11038 				if(xftFont) {
11039 					XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length);
11040 
11041 					goto carry_on;
11042 				}
11043 
11044 				if(fontset)
11045 					Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
11046 
11047 				else
11048 					XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length);
11049 				carry_on:
11050 				cy += lineHeight + 4;
11051 			}
11052 		}
11053 
11054 		void drawPixel(int x, int y) {
11055 			XDrawPoint(display, d, gc, x, y);
11056 		}
11057 
11058 		// The basic shapes, outlined
11059 
11060 		void drawLine(int x1, int y1, int x2, int y2) {
11061 			if(foregroundIsNotTransparent)
11062 				XDrawLine(display, d, gc, x1, y1, x2, y2);
11063 		}
11064 
11065 		void drawRectangle(int x, int y, int width, int height) {
11066 			if(backgroundIsNotTransparent) {
11067 				swapColors();
11068 				XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once...
11069 				swapColors();
11070 			}
11071 			if(foregroundIsNotTransparent)
11072 				XDrawRectangle(display, d, gc, x, y, width - 1, height - 1);
11073 		}
11074 
11075 		/// Arguments are the points of the bounding rectangle
11076 		void drawEllipse(int x1, int y1, int x2, int y2) {
11077 			drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64);
11078 		}
11079 
11080 		// NOTE: start and finish are in units of degrees * 64
11081 		void drawArc(int x1, int y1, int width, int height, int start, int finish) {
11082 			if(backgroundIsNotTransparent) {
11083 				swapColors();
11084 				XFillArc(display, d, gc, x1, y1, width, height, start, finish);
11085 				swapColors();
11086 			}
11087 			if(foregroundIsNotTransparent)
11088 				XDrawArc(display, d, gc, x1, y1, width, height, start, finish);
11089 		}
11090 
11091 		void drawPolygon(Point[] vertexes) {
11092 			XPoint[16] pointsBuffer;
11093 			XPoint[] points;
11094 			if(vertexes.length <= pointsBuffer.length)
11095 				points = pointsBuffer[0 .. vertexes.length];
11096 			else
11097 				points.length = vertexes.length;
11098 
11099 			foreach(i, p; vertexes) {
11100 				points[i].x = cast(short) p.x;
11101 				points[i].y = cast(short) p.y;
11102 			}
11103 
11104 			if(backgroundIsNotTransparent) {
11105 				swapColors();
11106 				XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin);
11107 				swapColors();
11108 			}
11109 			if(foregroundIsNotTransparent) {
11110 				XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin);
11111 			}
11112 		}
11113 	}
11114 
11115 	/* XRender { */
11116 
11117 	struct XRenderColor {
11118 		ushort red;
11119 		ushort green;
11120 		ushort blue;
11121 		ushort alpha;
11122 	}
11123 
11124 	alias Picture = XID;
11125 	alias PictFormat = XID;
11126 
11127 	struct XGlyphInfo {
11128 		ushort width;
11129 		ushort height;
11130 		short x;
11131 		short y;
11132 		short xOff;
11133 		short yOff;
11134 	}
11135 
11136 struct XRenderDirectFormat {
11137     short   red;
11138     short   redMask;
11139     short   green;
11140     short   greenMask;
11141     short   blue;
11142     short   blueMask;
11143     short   alpha;
11144     short   alphaMask;
11145 }
11146 
11147 struct XRenderPictFormat {
11148     PictFormat		id;
11149     int			type;
11150     int			depth;
11151     XRenderDirectFormat	direct;
11152     Colormap		colormap;
11153 }
11154 
11155 enum PictFormatID	=   (1 << 0);
11156 enum PictFormatType	=   (1 << 1);
11157 enum PictFormatDepth	=   (1 << 2);
11158 enum PictFormatRed	=   (1 << 3);
11159 enum PictFormatRedMask  =(1 << 4);
11160 enum PictFormatGreen	=   (1 << 5);
11161 enum PictFormatGreenMask=(1 << 6);
11162 enum PictFormatBlue	=   (1 << 7);
11163 enum PictFormatBlueMask =(1 << 8);
11164 enum PictFormatAlpha	=   (1 << 9);
11165 enum PictFormatAlphaMask=(1 << 10);
11166 enum PictFormatColormap =(1 << 11);
11167 
11168 struct XRenderPictureAttributes {
11169 	int 		repeat;
11170 	Picture		alpha_map;
11171 	int			alpha_x_origin;
11172 	int			alpha_y_origin;
11173 	int			clip_x_origin;
11174 	int			clip_y_origin;
11175 	Pixmap		clip_mask;
11176 	Bool		graphics_exposures;
11177 	int			subwindow_mode;
11178 	int			poly_edge;
11179 	int			poly_mode;
11180 	Atom		dither;
11181 	Bool		component_alpha;
11182 }
11183 
11184 alias int XFixed;
11185 
11186 struct XPointFixed {
11187     XFixed  x, y;
11188 }
11189 
11190 struct XCircle {
11191     XFixed x;
11192     XFixed y;
11193     XFixed radius;
11194 }
11195 
11196 struct XTransform {
11197     XFixed[3][3]  matrix;
11198 }
11199 
11200 struct XFilters {
11201     int	    nfilter;
11202     char    **filter;
11203     int	    nalias;
11204     short   *alias_;
11205 }
11206 
11207 struct XIndexValue {
11208     c_ulong    pixel;
11209     ushort   red, green, blue, alpha;
11210 }
11211 
11212 struct XAnimCursor {
11213     Cursor	    cursor;
11214     c_ulong   delay;
11215 }
11216 
11217 struct XLinearGradient {
11218     XPointFixed p1;
11219     XPointFixed p2;
11220 }
11221 
11222 struct XRadialGradient {
11223     XCircle inner;
11224     XCircle outer;
11225 }
11226 
11227 struct XConicalGradient {
11228     XPointFixed center;
11229     XFixed angle; /* in degrees */
11230 }
11231 
11232 enum PictStandardARGB32  = 0;
11233 enum PictStandardRGB24   = 1;
11234 enum PictStandardA8	 =  2;
11235 enum PictStandardA4	 =  3;
11236 enum PictStandardA1	 =  4;
11237 enum PictStandardNUM	 =  5;
11238 
11239 interface XRender {
11240 extern(C) @nogc:
11241 
11242 	Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep);
11243 
11244 	Status XRenderQueryVersion (Display *dpy,
11245 			int     *major_versionp,
11246 			int     *minor_versionp);
11247 
11248 	Status XRenderQueryFormats (Display *dpy);
11249 
11250 	int XRenderQuerySubpixelOrder (Display *dpy, int screen);
11251 
11252 	Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel);
11253 
11254 	XRenderPictFormat *
11255 		XRenderFindVisualFormat (Display *dpy, const Visual *visual);
11256 
11257 	XRenderPictFormat *
11258 		XRenderFindFormat (Display			*dpy,
11259 				c_ulong		mask,
11260 				const XRenderPictFormat	*templ,
11261 				int				count);
11262 	XRenderPictFormat *
11263 		XRenderFindStandardFormat (Display		*dpy,
11264 				int			format);
11265 
11266 	XIndexValue *
11267 		XRenderQueryPictIndexValues(Display			*dpy,
11268 				const XRenderPictFormat	*format,
11269 				int				*num);
11270 
11271 	Picture XRenderCreatePicture(
11272 		Display *dpy,
11273 		Drawable drawable,
11274 		const XRenderPictFormat *format,
11275 		c_ulong valuemask,
11276 		const XRenderPictureAttributes *attributes);
11277 
11278 	void XRenderChangePicture (Display				*dpy,
11279 				Picture				picture,
11280 				c_ulong			valuemask,
11281 				const XRenderPictureAttributes  *attributes);
11282 
11283 	void
11284 		XRenderSetPictureClipRectangles (Display	    *dpy,
11285 				Picture	    picture,
11286 				int		    xOrigin,
11287 				int		    yOrigin,
11288 				const XRectangle *rects,
11289 				int		    n);
11290 
11291 	void
11292 		XRenderSetPictureClipRegion (Display	    *dpy,
11293 				Picture	    picture,
11294 				Region	    r);
11295 
11296 	void
11297 		XRenderSetPictureTransform (Display	    *dpy,
11298 				Picture	    picture,
11299 				XTransform	    *transform);
11300 
11301 	void
11302 		XRenderFreePicture (Display                   *dpy,
11303 				Picture                   picture);
11304 
11305 	void
11306 		XRenderComposite (Display   *dpy,
11307 				int	    op,
11308 				Picture   src,
11309 				Picture   mask,
11310 				Picture   dst,
11311 				int	    src_x,
11312 				int	    src_y,
11313 				int	    mask_x,
11314 				int	    mask_y,
11315 				int	    dst_x,
11316 				int	    dst_y,
11317 				uint	width,
11318 				uint	height);
11319 
11320 
11321 	Picture XRenderCreateSolidFill (Display *dpy,
11322 			const XRenderColor *color);
11323 
11324 	Picture XRenderCreateLinearGradient (Display *dpy,
11325 			const XLinearGradient *gradient,
11326 			const XFixed *stops,
11327 			const XRenderColor *colors,
11328 			int nstops);
11329 
11330 	Picture XRenderCreateRadialGradient (Display *dpy,
11331 			const XRadialGradient *gradient,
11332 			const XFixed *stops,
11333 			const XRenderColor *colors,
11334 			int nstops);
11335 
11336 	Picture XRenderCreateConicalGradient (Display *dpy,
11337 			const XConicalGradient *gradient,
11338 			const XFixed *stops,
11339 			const XRenderColor *colors,
11340 			int nstops);
11341 
11342 
11343 
11344 	Cursor
11345 		XRenderCreateCursor (Display	    *dpy,
11346 				Picture	    source,
11347 				uint   x,
11348 				uint   y);
11349 
11350 	XFilters *
11351 		XRenderQueryFilters (Display *dpy, Drawable drawable);
11352 
11353 	void
11354 		XRenderSetPictureFilter (Display    *dpy,
11355 				Picture    picture,
11356 				const char *filter,
11357 				XFixed	    *params,
11358 				int	    nparams);
11359 
11360 	Cursor
11361 		XRenderCreateAnimCursor (Display	*dpy,
11362 				int		ncursor,
11363 				XAnimCursor	*cursors);
11364 }
11365 
11366 mixin DynamicLoad!(XRender, "Xrender", 1, false, true) XRenderLibrary;
11367 
11368 
11369 
11370 	/* XRender } */
11371 
11372 	/* Xft { */
11373 
11374 	// actually freetype
11375 	alias void FT_Face;
11376 
11377 	// actually fontconfig
11378 	private alias FcBool = int;
11379 	alias void FcCharSet;
11380 	alias void FcPattern;
11381 	alias void FcResult;
11382 	enum FcEndian { FcEndianBig, FcEndianLittle }
11383 	struct FcFontSet {
11384 		int nfont;
11385 		int sfont;
11386 		FcPattern** fonts;
11387 	}
11388 
11389 	// actually XRegion
11390 	struct BOX {
11391 		short x1, x2, y1, y2;
11392 	}
11393 	struct Region {
11394 		c_long size;
11395 		c_long numRects;
11396 		BOX* rects;
11397 		BOX extents;
11398 	}
11399 
11400 	// ok actually Xft
11401 
11402 	struct XftFontInfo;
11403 
11404 	struct XftFont {
11405 		int         ascent;
11406 		int         descent;
11407 		int         height;
11408 		int         max_advance_width;
11409 		FcCharSet*  charset;
11410 		FcPattern*  pattern;
11411 	}
11412 
11413 	struct XftDraw;
11414 
11415 	struct XftColor {
11416 		c_ulong pixel;
11417 		XRenderColor color;
11418 	}
11419 
11420 	struct XftCharSpec {
11421 		dchar           ucs4;
11422 		short           x;
11423 		short           y;
11424 	}
11425 
11426 	struct XftCharFontSpec {
11427 		XftFont         *font;
11428 		dchar           ucs4;
11429 		short           x;
11430 		short           y;
11431 	}
11432 
11433 	struct XftGlyphSpec {
11434 		uint            glyph;
11435 		short           x;
11436 		short           y;
11437 	}
11438 
11439 	struct XftGlyphFontSpec {
11440 		XftFont         *font;
11441 		uint            glyph;
11442 		short           x;
11443 		short           y;
11444 	}
11445 
11446 	interface Xft {
11447 	extern(C) @nogc pure:
11448 
11449 	Bool XftColorAllocName (Display  *dpy,
11450 				const Visual   *visual,
11451 				Colormap cmap,
11452 				const char     *name,
11453 				XftColor *result);
11454 
11455 	Bool XftColorAllocValue (Display         *dpy,
11456 				Visual          *visual,
11457 				Colormap        cmap,
11458 				const XRenderColor    *color,
11459 				XftColor        *result);
11460 
11461 	void XftColorFree (Display   *dpy,
11462 				Visual    *visual,
11463 				Colormap  cmap,
11464 				XftColor  *color);
11465 
11466 	Bool XftDefaultHasRender (Display *dpy);
11467 
11468 	Bool XftDefaultSet (Display *dpy, FcPattern *defaults);
11469 
11470 	void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern);
11471 
11472 	XftDraw * XftDrawCreate (Display   *dpy,
11473 		       Drawable  drawable,
11474 		       Visual    *visual,
11475 		       Colormap  colormap);
11476 
11477 	XftDraw * XftDrawCreateBitmap (Display  *dpy,
11478 			     Pixmap   bitmap);
11479 
11480 	XftDraw * XftDrawCreateAlpha (Display *dpy,
11481 			    Pixmap  pixmap,
11482 			    int     depth);
11483 
11484 	void XftDrawChange (XftDraw  *draw,
11485 		       Drawable drawable);
11486 
11487 	Display * XftDrawDisplay (XftDraw *draw);
11488 
11489 	Drawable XftDrawDrawable (XftDraw *draw);
11490 
11491 	Colormap XftDrawColormap (XftDraw *draw);
11492 
11493 	Visual * XftDrawVisual (XftDraw *draw);
11494 
11495 	void XftDrawDestroy (XftDraw *draw);
11496 
11497 	Picture XftDrawPicture (XftDraw *draw);
11498 
11499 	Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color);
11500 
11501 	void XftDrawGlyphs (XftDraw          *draw,
11502 				const XftColor *color,
11503 				XftFont          *pub,
11504 				int              x,
11505 				int              y,
11506 				const uint  *glyphs,
11507 				int              nglyphs);
11508 
11509 	void XftDrawString8 (XftDraw             *draw,
11510 				const XftColor    *color,
11511 				XftFont             *pub,
11512 				int                 x,
11513 				int                 y,
11514 				const char     *string,
11515 				int                 len);
11516 
11517 	void XftDrawString16 (XftDraw            *draw,
11518 				const XftColor   *color,
11519 				XftFont            *pub,
11520 				int                x,
11521 				int                y,
11522 				const wchar   *string,
11523 				int                len);
11524 
11525 	void XftDrawString32 (XftDraw            *draw,
11526 				const XftColor   *color,
11527 				XftFont            *pub,
11528 				int                x,
11529 				int                y,
11530 				const dchar   *string,
11531 				int                len);
11532 
11533 	void XftDrawStringUtf8 (XftDraw          *draw,
11534 				const XftColor *color,
11535 				XftFont          *pub,
11536 				int              x,
11537 				int              y,
11538 				const char  *string,
11539 				int              len);
11540 	void XftDrawStringUtf16 (XftDraw             *draw,
11541 				const XftColor    *color,
11542 				XftFont             *pub,
11543 				int                 x,
11544 				int                 y,
11545 				const char     *string,
11546 				FcEndian            endian,
11547 				int                 len);
11548 
11549 	void XftDrawCharSpec (XftDraw                *draw,
11550 				const XftColor       *color,
11551 				XftFont                *pub,
11552 				const XftCharSpec    *chars,
11553 				int                    len);
11554 
11555 	void XftDrawCharFontSpec (XftDraw                    *draw,
11556 				const XftColor           *color,
11557 				const XftCharFontSpec    *chars,
11558 				int                        len);
11559 
11560 	void XftDrawGlyphSpec (XftDraw               *draw,
11561 				const XftColor      *color,
11562 				XftFont               *pub,
11563 				const XftGlyphSpec  *glyphs,
11564 				int                   len);
11565 
11566 	void XftDrawGlyphFontSpec (XftDraw                   *draw,
11567 				const XftColor          *color,
11568 				const XftGlyphFontSpec  *glyphs,
11569 				int                       len);
11570 
11571 	void XftDrawRect (XftDraw            *draw,
11572 				const XftColor   *color,
11573 				int                x,
11574 				int                y,
11575 				uint       width,
11576 				uint       height);
11577 
11578 	Bool XftDrawSetClip (XftDraw     *draw,
11579 				Region      r);
11580 
11581 
11582 	Bool XftDrawSetClipRectangles (XftDraw               *draw,
11583 				int                   xOrigin,
11584 				int                   yOrigin,
11585 				const XRectangle    *rects,
11586 				int                   n);
11587 
11588 	void XftDrawSetSubwindowMode (XftDraw    *draw,
11589 				int        mode);
11590 
11591 	void XftGlyphExtents (Display            *dpy,
11592 				XftFont            *pub,
11593 				const uint    *glyphs,
11594 				int                nglyphs,
11595 				XGlyphInfo         *extents);
11596 
11597 	void XftTextExtents8 (Display            *dpy,
11598 				XftFont            *pub,
11599 				const char    *string,
11600 				int                len,
11601 				XGlyphInfo         *extents);
11602 
11603 	void XftTextExtents16 (Display           *dpy,
11604 				XftFont           *pub,
11605 				const wchar  *string,
11606 				int               len,
11607 				XGlyphInfo        *extents);
11608 
11609 	void XftTextExtents32 (Display           *dpy,
11610 				XftFont           *pub,
11611 				const dchar  *string,
11612 				int               len,
11613 				XGlyphInfo        *extents);
11614 
11615 	void XftTextExtentsUtf8 (Display         *dpy,
11616 				XftFont         *pub,
11617 				const char *string,
11618 				int             len,
11619 				XGlyphInfo      *extents);
11620 
11621 	void XftTextExtentsUtf16 (Display            *dpy,
11622 				XftFont            *pub,
11623 				const char    *string,
11624 				FcEndian           endian,
11625 				int                len,
11626 				XGlyphInfo         *extents);
11627 
11628 	FcPattern * XftFontMatch (Display           *dpy,
11629 				int               screen,
11630 				const FcPattern *pattern,
11631 				FcResult          *result);
11632 
11633 	XftFont * XftFontOpen (Display *dpy, int screen, ...);
11634 
11635 	XftFont * XftFontOpenName (Display *dpy, int screen, const char *name);
11636 
11637 	XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd);
11638 
11639 	FT_Face XftLockFace (XftFont *pub);
11640 
11641 	void XftUnlockFace (XftFont *pub);
11642 
11643 	XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern);
11644 
11645 	void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi);
11646 
11647 	dchar XftFontInfoHash (const XftFontInfo *fi);
11648 
11649 	FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b);
11650 
11651 	XftFont * XftFontOpenInfo (Display        *dpy,
11652 				FcPattern      *pattern,
11653 				XftFontInfo    *fi);
11654 
11655 	XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern);
11656 
11657 	XftFont * XftFontCopy (Display *dpy, XftFont *pub);
11658 
11659 	void XftFontClose (Display *dpy, XftFont *pub);
11660 
11661 	FcBool XftInitFtLibrary();
11662 	void XftFontLoadGlyphs (Display          *dpy,
11663 				XftFont          *pub,
11664 				FcBool           need_bitmaps,
11665 				const uint  *glyphs,
11666 				int              nglyph);
11667 
11668 	void XftFontUnloadGlyphs (Display            *dpy,
11669 				XftFont            *pub,
11670 				const uint    *glyphs,
11671 				int                nglyph);
11672 
11673 	FcBool XftFontCheckGlyph (Display  *dpy,
11674 				XftFont  *pub,
11675 				FcBool   need_bitmaps,
11676 				uint  glyph,
11677 				uint  *missing,
11678 				int      *nmissing);
11679 
11680 	FcBool XftCharExists (Display      *dpy,
11681 				XftFont      *pub,
11682 				dchar    ucs4);
11683 
11684 	uint XftCharIndex (Display       *dpy,
11685 				XftFont       *pub,
11686 				dchar      ucs4);
11687 	FcBool XftInit (const char *config);
11688 
11689 	int XftGetVersion ();
11690 
11691 
11692 	FcFontSet * XftListFonts (Display   *dpy,
11693 				int       screen,
11694 				...);
11695 
11696 	FcPattern *XftNameParse (const char *name);
11697 
11698 	void XftGlyphRender (Display         *dpy,
11699 				int             op,
11700 				Picture         src,
11701 				XftFont         *pub,
11702 				Picture         dst,
11703 				int             srcx,
11704 				int             srcy,
11705 				int             x,
11706 				int             y,
11707 				const uint *glyphs,
11708 				int             nglyphs);
11709 
11710 	void XftGlyphSpecRender (Display                 *dpy,
11711 				int                     op,
11712 				Picture                 src,
11713 				XftFont                 *pub,
11714 				Picture                 dst,
11715 				int                     srcx,
11716 				int                     srcy,
11717 				const XftGlyphSpec    *glyphs,
11718 				int                     nglyphs);
11719 
11720 	void XftCharSpecRender (Display              *dpy,
11721 				int                  op,
11722 				Picture              src,
11723 				XftFont              *pub,
11724 				Picture              dst,
11725 				int                  srcx,
11726 				int                  srcy,
11727 				const XftCharSpec  *chars,
11728 				int                  len);
11729 	void XftGlyphFontSpecRender (Display                     *dpy,
11730 				int                         op,
11731 				Picture                     src,
11732 				Picture                     dst,
11733 				int                         srcx,
11734 				int                         srcy,
11735 				const XftGlyphFontSpec    *glyphs,
11736 				int                         nglyphs);
11737 
11738 	void XftCharFontSpecRender (Display                  *dpy,
11739 				int                      op,
11740 				Picture                  src,
11741 				Picture                  dst,
11742 				int                      srcx,
11743 				int                      srcy,
11744 				const XftCharFontSpec  *chars,
11745 				int                      len);
11746 
11747 	void XftTextRender8 (Display         *dpy,
11748 				int             op,
11749 				Picture         src,
11750 				XftFont         *pub,
11751 				Picture         dst,
11752 				int             srcx,
11753 				int             srcy,
11754 				int             x,
11755 				int             y,
11756 				const char *string,
11757 				int             len);
11758 	void XftTextRender16 (Display            *dpy,
11759 				int                op,
11760 				Picture            src,
11761 				XftFont            *pub,
11762 				Picture            dst,
11763 				int                srcx,
11764 				int                srcy,
11765 				int                x,
11766 				int                y,
11767 				const wchar   *string,
11768 				int                len);
11769 
11770 	void XftTextRender16BE (Display          *dpy,
11771 				int              op,
11772 				Picture          src,
11773 				XftFont          *pub,
11774 				Picture          dst,
11775 				int              srcx,
11776 				int              srcy,
11777 				int              x,
11778 				int              y,
11779 				const char  *string,
11780 				int              len);
11781 
11782 	void XftTextRender16LE (Display          *dpy,
11783 				int              op,
11784 				Picture          src,
11785 				XftFont          *pub,
11786 				Picture          dst,
11787 				int              srcx,
11788 				int              srcy,
11789 				int              x,
11790 				int              y,
11791 				const char  *string,
11792 				int              len);
11793 
11794 	void XftTextRender32 (Display            *dpy,
11795 				int                op,
11796 				Picture            src,
11797 				XftFont            *pub,
11798 				Picture            dst,
11799 				int                srcx,
11800 				int                srcy,
11801 				int                x,
11802 				int                y,
11803 				const dchar   *string,
11804 				int                len);
11805 
11806 	void XftTextRender32BE (Display          *dpy,
11807 				int              op,
11808 				Picture          src,
11809 				XftFont          *pub,
11810 				Picture          dst,
11811 				int              srcx,
11812 				int              srcy,
11813 				int              x,
11814 				int              y,
11815 				const char  *string,
11816 				int              len);
11817 
11818 	void XftTextRender32LE (Display          *dpy,
11819 				int              op,
11820 				Picture          src,
11821 				XftFont          *pub,
11822 				Picture          dst,
11823 				int              srcx,
11824 				int              srcy,
11825 				int              x,
11826 				int              y,
11827 				const char  *string,
11828 				int              len);
11829 
11830 	void XftTextRenderUtf8 (Display          *dpy,
11831 				int              op,
11832 				Picture          src,
11833 				XftFont          *pub,
11834 				Picture          dst,
11835 				int              srcx,
11836 				int              srcy,
11837 				int              x,
11838 				int              y,
11839 				const char  *string,
11840 				int              len);
11841 
11842 	void XftTextRenderUtf16 (Display         *dpy,
11843 				int             op,
11844 				Picture         src,
11845 				XftFont         *pub,
11846 				Picture         dst,
11847 				int             srcx,
11848 				int             srcy,
11849 				int             x,
11850 				int             y,
11851 				const char *string,
11852 				FcEndian        endian,
11853 				int             len);
11854 	FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete);
11855 
11856 	}
11857 
11858 	mixin DynamicLoad!(Xft, "Xft", 2) XftLibrary;
11859 
11860 
11861 	/* Xft } */
11862 
11863 	class XDisconnectException : Exception {
11864 		bool userRequested;
11865 		this(bool userRequested = true) {
11866 			this.userRequested = userRequested;
11867 			super("X disconnected");
11868 		}
11869 	}
11870 
11871 	/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a Display*
11872 	class XDisplayConnection {
11873 		private __gshared Display* display;
11874 		private __gshared XIM xim;
11875 		private __gshared char* displayName;
11876 
11877 		private __gshared int connectionSequence_;
11878 
11879 		/// use this for lazy caching when reconnection
11880 		static int connectionSequenceNumber() { return connectionSequence_; }
11881 
11882 		/// Attempts recreation of state, may require application assistance
11883 		/// You MUST call this OUTSIDE the event loop. Let the exception kill the loop,
11884 		/// then call this, and if successful, reenter the loop.
11885 		static void discardAndRecreate(string newDisplayString = null) {
11886 			if(insideXEventLoop)
11887 				throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop");
11888 
11889 			// auto swnm = SimpleWindow.nativeMapping.dup; // this SHOULD be unnecessary because all simple windows are capable of handling native events, so the latter ought to do it all
11890 			auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup;
11891 
11892 			foreach(handle; chnenhm) {
11893 				handle.discardConnectionState();
11894 			}
11895 
11896 			discardState();
11897 
11898 			if(newDisplayString !is null)
11899 				setDisplayName(newDisplayString);
11900 
11901 			auto display = get();
11902 
11903 			foreach(handle; chnenhm) {
11904 				handle.recreateAfterDisconnect();
11905 			}
11906 		}
11907 
11908 		private __gshared EventMask rootEventMask;
11909 
11910 		/++
11911 			Requests the specified input from the root window on the connection, in addition to any other request.
11912 
11913 			
11914 			Since plain XSelectInput will replace the existing mask, adding input from multiple locations is tricky. This central function will combine all the masks for you.
11915 
11916 			$(WARNING it calls XSelectInput itself, which will override any other root window input you have!)
11917 		+/
11918 		static void addRootInput(EventMask mask) {
11919 			auto old = rootEventMask;
11920 			rootEventMask |= mask;
11921 			get(); // to ensure display connected
11922 			if(display !is null && rootEventMask != old)
11923 				XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask);
11924 		}
11925 
11926 		static void discardState() {
11927 			freeImages();
11928 
11929 			foreach(atomPtr; interredAtoms)
11930 				*atomPtr = 0;
11931 			interredAtoms = null;
11932 			interredAtoms.assumeSafeAppend();
11933 
11934 			ScreenPainterImplementation.fontAttempted = false;
11935 			ScreenPainterImplementation.defaultfont = null;
11936 			ScreenPainterImplementation.defaultfontset = null;
11937 
11938 			Image.impl.xshmQueryCompleted = false;
11939 			Image.impl._xshmAvailable = false;
11940 
11941 			SimpleWindow.nativeMapping = null;
11942 			CapableOfHandlingNativeEvent.nativeHandleMapping = null;
11943 			// GlobalHotkeyManager
11944 
11945 			display = null;
11946 			xim = null;
11947 		}
11948 
11949 		// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
11950 		private static void createXIM () {
11951 			import core.stdc.locale : setlocale, LC_ALL;
11952 			import core.stdc.stdio : stderr, fprintf;
11953 			import core.stdc.stdlib : free;
11954 			import core.stdc..string : strdup;
11955 
11956 			static immutable string[3] mtry = [ null, "@im=local", "@im=" ];
11957 
11958 			auto olocale = strdup(setlocale(LC_ALL, null));
11959 			setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
11960 			scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
11961 
11962 			//fprintf(stderr, "opening IM...\n");
11963 			foreach (string s; mtry) {
11964 				if (s.length) XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
11965 				if ((xim = XOpenIM(display, null, null, null)) !is null) return;
11966 			}
11967 			fprintf(stderr, "createXIM: XOpenIM failed!\n");
11968 		}
11969 
11970 		// for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing.
11971 		// we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor.
11972 		static struct ImgList {
11973 			size_t img; // class; hide it from GC
11974 			ImgList* next;
11975 		}
11976 
11977 		static __gshared ImgList* imglist = null;
11978 		static __gshared bool imglistLocked = false; // true: don't register and unregister images
11979 
11980 		static void registerImage (Image img) {
11981 			if (!imglistLocked && img !is null) {
11982 				import core.stdc.stdlib : malloc;
11983 				auto it = cast(ImgList*)malloc(ImgList.sizeof);
11984 				assert(it !is null); // do proper checks
11985 				it.img = cast(size_t)cast(void*)img;
11986 				it.next = imglist;
11987 				imglist = it;
11988 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); }
11989 			}
11990 		}
11991 
11992 		static void unregisterImage (Image img) {
11993 			if (!imglistLocked && img !is null) {
11994 				import core.stdc.stdlib : free;
11995 				ImgList* prev = null;
11996 				ImgList* cur = imglist;
11997 				while (cur !is null) {
11998 					if (cur.img == cast(size_t)cast(void*)img) break; // i found her!
11999 					prev = cur;
12000 					cur = cur.next;
12001 				}
12002 				if (cur !is null) {
12003 					if (prev is null) imglist = cur.next; else prev.next = cur.next;
12004 					free(cur);
12005 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); }
12006 				} else {
12007 					version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); }
12008 				}
12009 			}
12010 		}
12011 
12012 		static void freeImages () { // needed for discardAndRecreate
12013 			imglistLocked = true;
12014 			scope(exit) imglistLocked = false;
12015 			ImgList* cur = imglist;
12016 			ImgList* next = null;
12017 			while (cur !is null) {
12018 				import core.stdc.stdlib : free;
12019 				next = cur.next;
12020 				version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); }
12021 				(cast(Image)cast(void*)cur.img).dispose();
12022 				free(cur);
12023 				cur = next;
12024 			}
12025 			imglist = null;
12026 		}
12027 
12028 		/// can be used to override normal handling of display name
12029 		/// from environment and/or command line
12030 		static setDisplayName(string newDisplayName) {
12031 			displayName = cast(char*) (newDisplayName ~ '\0');
12032 		}
12033 
12034 		/// resets to the default display string
12035 		static resetDisplayName() {
12036 			displayName = null;
12037 		}
12038 
12039 		///
12040 		static Display* get() {
12041 			if(display is null) {
12042 				if(!librariesSuccessfullyLoaded)
12043 					throw new Exception("Unable to load X11 client libraries");
12044 				display = XOpenDisplay(displayName);
12045 				connectionSequence_++;
12046 				if(display is null)
12047 					throw new Exception("Unable to open X display");
12048 				XSetIOErrorHandler(&x11ioerrCB);
12049 				Bool sup;
12050 				XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
12051 				createXIM();
12052 				version(with_eventloop) {
12053 					import arsd.eventloop;
12054 					addFileEventListeners(display.fd, &eventListener, null, null);
12055 				}
12056 			}
12057 
12058 			return display;
12059 		}
12060 
12061 		extern(C)
12062 		static int x11ioerrCB(Display* dpy) {
12063 			throw new XDisconnectException(false);
12064 		}
12065 
12066 		version(with_eventloop) {
12067 			import arsd.eventloop;
12068 			static void eventListener(OsFileHandle fd) {
12069 				//this.mtLock();
12070 				//scope(exit) this.mtUnlock();
12071 				while(XPending(display))
12072 					doXNextEvent(display);
12073 			}
12074 		}
12075 
12076 		// close connection on program exit -- we need this to properly free all images
12077 		static ~this () {
12078 			// the gui thread must clean up after itself or else Xlib might deadlock
12079 			// using this flag on any thread destruction is the easiest way i know of
12080 			// (shared static this is run by the LAST thread to exit, which may not be
12081 			// the gui thread, and normal static this run by ALL threads, so we gotta check.)
12082 			if(thisIsGuiThread)
12083 				close();
12084 		}
12085 
12086 		///
12087 		static void close() {
12088 			if(display is null)
12089 				return;
12090 
12091 			version(with_eventloop) {
12092 				import arsd.eventloop;
12093 				removeFileEventListeners(display.fd);
12094 			}
12095 
12096 			// now remove all registered images to prevent shared memory leaks
12097 			freeImages();
12098 
12099 			// tbh I don't know why it is doing this but like if this happens to run
12100 			// from the other thread there's frequent hanging inside here.
12101 			if(thisIsGuiThread)
12102 				XCloseDisplay(display);
12103 			display = null;
12104 		}
12105 	}
12106 
12107 	mixin template NativeImageImplementation() {
12108 		XImage* handle;
12109 		ubyte* rawData;
12110 
12111 		XShmSegmentInfo shminfo;
12112 
12113 		__gshared bool xshmQueryCompleted;
12114 		__gshared bool _xshmAvailable;
12115 		public static @property bool xshmAvailable() {
12116 			if(!xshmQueryCompleted) {
12117 				int i1, i2, i3;
12118 				xshmQueryCompleted = true;
12119 
12120 				auto str = XDisplayConnection.get().display_name;
12121 				// only if we are actually on the same machine does this
12122 				// have any hope, and the query extension only asks if
12123 				// the server can in theory, not in practice.
12124 				if(str is null || (str[0] != ':' && str[0] != '/'))
12125 					_xshmAvailable = false;
12126 				else
12127 					_xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0;
12128 			}
12129 			return _xshmAvailable;
12130 		}
12131 
12132 		bool usingXshm;
12133 	final:
12134 
12135 		void createImage(int width, int height, bool forcexshm=false) {
12136 			auto display = XDisplayConnection.get();
12137 			assert(display !is null);
12138 			auto screen = DefaultScreen(display);
12139 
12140 			// it will only use shared memory for somewhat largish images,
12141 			// since otherwise we risk wasting shared memory handles on a lot of little ones
12142 			if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) {
12143 				usingXshm = true;
12144 				handle = XShmCreateImage(
12145 					display,
12146 					DefaultVisual(display, screen),
12147 					24,
12148 					ImageFormat.ZPixmap,
12149 					null,
12150 					&shminfo,
12151 					width, height);
12152 				assert(handle !is null);
12153 
12154 				assert(handle.bytes_per_line == 4 * width);
12155 				shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
12156 				//import std.conv; import core.stdc.errno;
12157 				assert(shminfo.shmid >= 0);//, to!string(errno));
12158 				handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
12159 				assert(rawData != cast(ubyte*) -1);
12160 				shminfo.readOnly = 0;
12161 				XShmAttach(display, &shminfo);
12162 				XDisplayConnection.registerImage(this);
12163 				// if I don't flush here there's a chance the dtor will run before the
12164 				// ctor and lead to a bad value X error. While this hurts the efficiency
12165 				// it is local anyway so prolly better to keep it simple
12166 				XFlush(display);
12167 			} else {
12168 				if (forcexshm) throw new Exception("can't create XShm Image");
12169 				// This actually needs to be malloc to avoid a double free error when XDestroyImage is called
12170 				import core.stdc.stdlib : malloc;
12171 				rawData = cast(ubyte*) malloc(width * height * 4);
12172 
12173 				handle = XCreateImage(
12174 					display,
12175 					DefaultVisual(display, screen),
12176 					24, // bpp
12177 					ImageFormat.ZPixmap,
12178 					0, // offset
12179 					rawData,
12180 					width, height,
12181 					8 /* FIXME */, 4 * width); // padding, bytes per line
12182 			}
12183 		}
12184 
12185 		void dispose() {
12186 			// note: this calls free(rawData) for us
12187 			if(handle) {
12188 				if (usingXshm) {
12189 					XDisplayConnection.unregisterImage(this);
12190 					if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo);
12191 				}
12192 				XDestroyImage(handle);
12193 				if(usingXshm) {
12194 					shmdt(shminfo.shmaddr);
12195 					shmctl(shminfo.shmid, IPC_RMID, null);
12196 				}
12197 				handle = null;
12198 			}
12199 		}
12200 
12201 		Color getPixel(int x, int y) {
12202 			auto offset = (y * width + x) * 4;
12203 			Color c;
12204 			c.a = 255;
12205 			c.b = rawData[offset + 0];
12206 			c.g = rawData[offset + 1];
12207 			c.r = rawData[offset + 2];
12208 			return c;
12209 		}
12210 
12211 		void setPixel(int x, int y, Color c) {
12212 			auto offset = (y * width + x) * 4;
12213 			rawData[offset + 0] = c.b;
12214 			rawData[offset + 1] = c.g;
12215 			rawData[offset + 2] = c.r;
12216 		}
12217 
12218 		void convertToRgbaBytes(ubyte[] where) {
12219 			assert(where.length == this.width * this.height * 4);
12220 
12221 			// if rawData had a length....
12222 			//assert(rawData.length == where.length);
12223 			for(int idx = 0; idx < where.length; idx += 4) {
12224 				where[idx + 0] = rawData[idx + 2]; // r
12225 				where[idx + 1] = rawData[idx + 1]; // g
12226 				where[idx + 2] = rawData[idx + 0]; // b
12227 				where[idx + 3] = 255; // a
12228 			}
12229 		}
12230 
12231 		void setFromRgbaBytes(in ubyte[] where) {
12232 			assert(where.length == this.width * this.height * 4);
12233 
12234 			// if rawData had a length....
12235 			//assert(rawData.length == where.length);
12236 			for(int idx = 0; idx < where.length; idx += 4) {
12237 				rawData[idx + 2] = where[idx + 0]; // r
12238 				rawData[idx + 1] = where[idx + 1]; // g
12239 				rawData[idx + 0] = where[idx + 2]; // b
12240 				//rawData[idx + 3] = 255; // a
12241 			}
12242 		}
12243 
12244 	}
12245 
12246 	mixin template NativeSimpleWindowImplementation() {
12247 		GC gc;
12248 		Window window;
12249 		Display* display;
12250 
12251 		Pixmap buffer;
12252 		int bufferw, bufferh; // size of the buffer; can be bigger than window
12253 		XIC xic; // input context
12254 		int curHidden = 0; // counter
12255 		Cursor blankCurPtr = 0;
12256 		int cursorSequenceNumber = 0;
12257 		int warpEventCount = 0; // number of mouse movement events to eat
12258 
12259 		__gshared X11SetSelectionHandler[Atom] setSelectionHandlers;
12260 		X11GetSelectionHandler[Atom] getSelectionHandlers;
12261 
12262 		version(without_opengl) {} else
12263 		GLXContext glc;
12264 
12265 		private void fixFixedSize(bool forced=false) (int width, int height) {
12266 			if (forced || this.resizability == Resizability.fixedSize) {
12267 				//{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); }
12268 				XSizeHints sh;
12269 				static if (!forced) {
12270 					c_long spr;
12271 					XGetWMNormalHints(display, window, &sh, &spr);
12272 					sh.flags |= PMaxSize | PMinSize;
12273 				} else {
12274 					sh.flags = PMaxSize | PMinSize;
12275 				}
12276 				sh.min_width = width;
12277 				sh.min_height = height;
12278 				sh.max_width = width;
12279 				sh.max_height = height;
12280 				XSetWMNormalHints(display, window, &sh);
12281 				//XFlush(display);
12282 			}
12283 		}
12284 
12285 		ScreenPainter getPainter() {
12286 			return ScreenPainter(this, window);
12287 		}
12288 
12289 		void move(int x, int y) {
12290 			XMoveWindow(display, window, x, y);
12291 		}
12292 
12293 		void resize(int w, int h) {
12294 			if (w < 1) w = 1;
12295 			if (h < 1) h = 1;
12296 			XResizeWindow(display, window, w, h);
12297 
12298 			// calling this now to avoid waiting for the server to
12299 			// acknowledge the resize; draws without returning to the
12300 			// event loop will thus actually work. the server's event
12301 			// btw might overrule this and resize it again
12302 			recordX11Resize(display, this, w, h);
12303 
12304 			// FIXME: do we need to set this as the opengl context to do the glViewport change?
12305 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
12306 		}
12307 
12308 		void moveResize (int x, int y, int w, int h) {
12309 			if (w < 1) w = 1;
12310 			if (h < 1) h = 1;
12311 			XMoveResizeWindow(display, window, x, y, w, h);
12312 			version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h);
12313 		}
12314 
12315 		void hideCursor () {
12316 			if (curHidden++ == 0) {
12317 				if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) {
12318 					static const(char)[1] cmbmp = 0;
12319 					XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
12320 					Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1);
12321 					blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0);
12322 					cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber;
12323 					XFreePixmap(display, pm);
12324 				}
12325 				XDefineCursor(display, window, blankCurPtr);
12326 			}
12327 		}
12328 
12329 		void showCursor () {
12330 			if (--curHidden == 0) XUndefineCursor(display, window);
12331 		}
12332 
12333 		void warpMouse (int x, int y) {
12334 			// here i will send dummy "ignore next mouse motion" event,
12335 			// 'cause `XWarpPointer()` sends synthesised mouse motion,
12336 			// and we don't need to report it to the user (as warping is
12337 			// used when the user needs movement deltas).
12338 			//XClientMessageEvent xclient;
12339 			XEvent e;
12340 			e.xclient.type = EventType.ClientMessage;
12341 			e.xclient.window = window;
12342 			e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
12343 			e.xclient.format = 32;
12344 			e.xclient.data.l[0] = 0;
12345 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); }
12346 			//{ import core.stdc.stdio : printf; printf("*X11 CLIENT: w=%u; type=%u; [0]=%u\n", cast(uint)e.xclient.window, cast(uint)e.xclient.message_type, cast(uint)e.xclient.data.l[0]); }
12347 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
12348 			// now warp pointer...
12349 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); }
12350 			XWarpPointer(display, None, window, 0, 0, 0, 0, x, y);
12351 			// ...and flush
12352 			debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); }
12353 			XFlush(display);
12354 		}
12355 
12356 		void sendDummyEvent () {
12357 			// here i will send dummy event to ping event queue
12358 			XEvent e;
12359 			e.xclient.type = EventType.ClientMessage;
12360 			e.xclient.window = window;
12361 			e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-)
12362 			e.xclient.format = 32;
12363 			e.xclient.data.l[0] = 0;
12364 			XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e);
12365 			XFlush(display);
12366 		}
12367 
12368 		void setTitle(string title) {
12369 			if (title.ptr is null) title = "";
12370 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
12371 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
12372 			XTextProperty windowName;
12373 			windowName.value = title.ptr;
12374 			windowName.encoding = XA_UTF8; //XA_STRING;
12375 			windowName.format = 8;
12376 			windowName.nitems = cast(uint)title.length;
12377 			XSetWMName(display, window, &windowName);
12378 			char[1024] namebuf = 0;
12379 			auto maxlen = namebuf.length-1;
12380 			if (maxlen > title.length) maxlen = title.length;
12381 			namebuf[0..maxlen] = title[0..maxlen];
12382 			XStoreName(display, window, namebuf.ptr);
12383 			XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length);
12384 			flushGui(); // without this OpenGL windows has a LONG delay before changing title
12385 		}
12386 
12387 		string[] getTitles() {
12388 			auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false);
12389 			auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false);
12390 			XTextProperty textProp;
12391 			if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) {
12392 				if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) {
12393 					return textProp.value[0 .. textProp.nitems].idup.split('\0');
12394 				} else
12395 					return [];
12396 			} else
12397 				return null;
12398 		}
12399 
12400 		string getTitle() {
12401 			auto titles = getTitles();
12402 			return titles.length ? titles[0] : null;
12403 		}
12404 
12405 		void setMinSize (int minwidth, int minheight) {
12406 			import core.stdc.config : c_long;
12407 			if (minwidth < 1) minwidth = 1;
12408 			if (minheight < 1) minheight = 1;
12409 			XSizeHints sh;
12410 			c_long spr;
12411 			XGetWMNormalHints(display, window, &sh, &spr);
12412 			sh.min_width = minwidth;
12413 			sh.min_height = minheight;
12414 			sh.flags |= PMinSize;
12415 			XSetWMNormalHints(display, window, &sh);
12416 			flushGui();
12417 		}
12418 
12419 		void setMaxSize (int maxwidth, int maxheight) {
12420 			import core.stdc.config : c_long;
12421 			if (maxwidth < 1) maxwidth = 1;
12422 			if (maxheight < 1) maxheight = 1;
12423 			XSizeHints sh;
12424 			c_long spr;
12425 			XGetWMNormalHints(display, window, &sh, &spr);
12426 			sh.max_width = maxwidth;
12427 			sh.max_height = maxheight;
12428 			sh.flags |= PMaxSize;
12429 			XSetWMNormalHints(display, window, &sh);
12430 			flushGui();
12431 		}
12432 
12433 		void setResizeGranularity (int granx, int grany) {
12434 			import core.stdc.config : c_long;
12435 			if (granx < 1) granx = 1;
12436 			if (grany < 1) grany = 1;
12437 			XSizeHints sh;
12438 			c_long spr;
12439 			XGetWMNormalHints(display, window, &sh, &spr);
12440 			sh.width_inc = granx;
12441 			sh.height_inc = grany;
12442 			sh.flags |= PResizeInc;
12443 			XSetWMNormalHints(display, window, &sh);
12444 			flushGui();
12445 		}
12446 
12447 		void setOpacity (uint opacity) {
12448 			arch_ulong o = opacity;
12449 			if (opacity == uint.max)
12450 				XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false));
12451 			else
12452 				XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false),
12453 					XA_CARDINAL, 32, PropModeReplace, &o, 1);
12454 		}
12455 
12456 		void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) {
12457 			version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load");
12458 			display = XDisplayConnection.get();
12459 			auto screen = DefaultScreen(display);
12460 
12461 			version(without_opengl) {}
12462 			else {
12463 				if(opengl == OpenGlOptions.yes) {
12464 					GLXFBConfig fbconf = null;
12465 					XVisualInfo* vi = null;
12466 					bool useLegacy = false;
12467 					static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions
12468 					if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) {
12469 						int[23] visualAttribs = [
12470 							GLX_X_RENDERABLE , 1/*True*/,
12471 							GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
12472 							GLX_RENDER_TYPE  , GLX_RGBA_BIT,
12473 							GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
12474 							GLX_RED_SIZE     , 8,
12475 							GLX_GREEN_SIZE   , 8,
12476 							GLX_BLUE_SIZE    , 8,
12477 							GLX_ALPHA_SIZE   , 8,
12478 							GLX_DEPTH_SIZE   , 24,
12479 							GLX_STENCIL_SIZE , 8,
12480 							GLX_DOUBLEBUFFER , 1/*True*/,
12481 							0/*None*/,
12482 						];
12483 						int fbcount;
12484 						GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount);
12485 						if (fbcount == 0) {
12486 							useLegacy = true; // try to do at least something
12487 						} else {
12488 							// pick the FB config/visual with the most samples per pixel
12489 							int bestidx = -1, bestns = -1;
12490 							foreach (int fbi; 0..fbcount) {
12491 								int sb, samples;
12492 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb);
12493 								glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples);
12494 								if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; }
12495 							}
12496 							//{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); }
12497 							fbconf = fbc[bestidx];
12498 							// Be sure to free the FBConfig list allocated by glXChooseFBConfig()
12499 							XFree(fbc);
12500 							vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf);
12501 						}
12502 					}
12503 					if (vi is null || useLegacy) {
12504 						static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
12505 						vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr);
12506 						useLegacy = true;
12507 					}
12508 					if (vi is null) throw new Exception("no open gl visual found");
12509 
12510 					XSetWindowAttributes swa;
12511 					auto root = RootWindow(display, screen);
12512 					swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone);
12513 
12514 					window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
12515 						0, 0, width, height,
12516 						0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap, &swa);
12517 
12518 					// now try to use `glXCreateContextAttribsARB()` if it's here
12519 					if (!useLegacy) {
12520 						// request fairly advanced context, even with stencil buffer!
12521 						int[9] contextAttribs = [
12522 							GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8),
12523 							GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff),
12524 							/*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01),
12525 							// for modern context, set "forward compatibility" flag too
12526 							(sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02,
12527 							0/*None*/,
12528 						];
12529 						glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr);
12530 						if (glc is null && sdpyOpenGLContextAllowFallback) {
12531 							sdpyOpenGLContextVersion = 0;
12532 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
12533 						}
12534 						//{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); }
12535 					} else {
12536 						// fallback to old GLX call
12537 						if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) {
12538 							sdpyOpenGLContextVersion = 0;
12539 							glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1);
12540 						}
12541 					}
12542 					// sync to ensure any errors generated are processed
12543 					XSync(display, 0/*False*/);
12544 					//{ import core.stdc.stdio; printf("ogl is here\n"); }
12545 					if(glc is null)
12546 						throw new Exception("glc");
12547 				}
12548 			}
12549 
12550 			if(opengl == OpenGlOptions.no) {
12551 
12552 				bool overrideRedirect = false;
12553 				if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)
12554 					overrideRedirect = true;
12555 
12556 				XSetWindowAttributes swa;
12557 				swa.background_pixel = WhitePixel(display, screen);
12558 				swa.border_pixel = BlackPixel(display, screen);
12559 				swa.override_redirect = overrideRedirect;
12560 				auto root = RootWindow(display, screen);
12561 				swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone);
12562 
12563 				window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window,
12564 					0, 0, width, height,
12565 					0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap | CWBackPixel | CWBorderPixel | CWOverrideRedirect, &swa);
12566 
12567 
12568 
12569 				/*
12570 				window = XCreateSimpleWindow(
12571 					display,
12572 					parent is null ? RootWindow(display, screen) : parent.impl.window,
12573 					0, 0, // x, y
12574 					width, height,
12575 					1, // border width
12576 					BlackPixel(display, screen), // border
12577 					WhitePixel(display, screen)); // background
12578 				*/
12579 
12580 				buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display));
12581 				bufferw = width;
12582 				bufferh = height;
12583 
12584 				gc = DefaultGC(display, screen);
12585 
12586 				// clear out the buffer to get us started...
12587 				XSetForeground(display, gc, WhitePixel(display, screen));
12588 				XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height);
12589 				XSetForeground(display, gc, BlackPixel(display, screen));
12590 			}
12591 
12592 			// input context
12593 			//TODO: create this only for top-level windows, and reuse that?
12594 			if (XDisplayConnection.xim !is null) {
12595 				xic = XCreateIC(XDisplayConnection.xim,
12596 						/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
12597 						/*XNClientWindow*/"clientWindow".ptr, window,
12598 						/*XNFocusWindow*/"focusWindow".ptr, window,
12599 						null);
12600 				if (xic is null) {
12601 					import core.stdc.stdio : stderr, fprintf;
12602 					fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
12603 				}
12604 			}
12605 
12606 			if (sdpyWindowClassStr is null) loadBinNameToWindowClassName();
12607 			if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow";
12608 			// window class
12609 			if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) {
12610 				//{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); }
12611 				XClassHint klass;
12612 				XWMHints wh;
12613 				XSizeHints size;
12614 				klass.res_name = sdpyWindowClassStr;
12615 				klass.res_class = sdpyWindowClassStr;
12616 				XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass);
12617 			}
12618 
12619 			setTitle(title);
12620 			SimpleWindow.nativeMapping[window] = this;
12621 			CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
12622 
12623 			// This gives our window a close button
12624 			if (windowType != WindowTypes.eventOnly) {
12625 				// FIXME: actually implement the WM_TAKE_FOCUS correctly
12626 				//Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)];
12627 				Atom[1] atoms = [GetAtom!"WM_DELETE_WINDOW"(display)];
12628 				XSetWMProtocols(display, window, atoms.ptr, cast(int) atoms.length);
12629 			}
12630 
12631 			// FIXME: windowType and customizationFlags
12632 			Atom[8] wsatoms; // here, due to goto
12633 			int wmsacount = 0; // here, due to goto
12634 
12635 			try
12636 			final switch(windowType) {
12637 				case WindowTypes.normal:
12638 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
12639 				break;
12640 				case WindowTypes.undecorated:
12641 					motifHideDecorations();
12642 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display));
12643 				break;
12644 				case WindowTypes.eventOnly:
12645 					_hidden = true;
12646 					XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification
12647 					goto hiddenWindow;
12648 				//break;
12649 				case WindowTypes.nestedChild:
12650 					// handled in XCreateWindow calls
12651 				break;
12652 
12653 				case WindowTypes.dropdownMenu:
12654 					motifHideDecorations();
12655 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display));
12656 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
12657 				break;
12658 				case WindowTypes.popupMenu:
12659 					motifHideDecorations();
12660 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display));
12661 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
12662 				break;
12663 				case WindowTypes.notification:
12664 					motifHideDecorations();
12665 					setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display));
12666 					customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop;
12667 				break;
12668 				/+
12669 				case WindowTypes.menu:
12670 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
12671 					motifHideDecorations();
12672 				break;
12673 				case WindowTypes.desktop:
12674 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display);
12675 				break;
12676 				case WindowTypes.dock:
12677 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display);
12678 				break;
12679 				case WindowTypes.toolbar:
12680 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display);
12681 				break;
12682 				case WindowTypes.menu:
12683 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display);
12684 				break;
12685 				case WindowTypes.utility:
12686 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display);
12687 				break;
12688 				case WindowTypes.splash:
12689 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display);
12690 				break;
12691 				case WindowTypes.dialog:
12692 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display);
12693 				break;
12694 				case WindowTypes.tooltip:
12695 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display);
12696 				break;
12697 				case WindowTypes.notification:
12698 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display);
12699 				break;
12700 				case WindowTypes.combo:
12701 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display);
12702 				break;
12703 				case WindowTypes.dnd:
12704 					atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display);
12705 				break;
12706 				+/
12707 			}
12708 			catch(Exception e) {
12709 				// XInternAtom failed, prolly a WM
12710 				// that doesn't support these things
12711 			}
12712 
12713 			if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display);
12714 			// the two following flags may be ignored by WM
12715 			if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display);
12716 			if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display);
12717 
12718 			if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount);
12719 
12720 			if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height);
12721 
12722 			// What would be ideal here is if they only were
12723 			// selected if there was actually an event handler
12724 			// for them...
12725 
12726 			selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
12727 
12728 			hiddenWindow:
12729 
12730 			// set the pid property for lookup later by window managers
12731 			// a standard convenience
12732 			import core.sys.posix.unistd;
12733 			arch_ulong pid = getpid();
12734 
12735 			XChangeProperty(
12736 				display,
12737 				impl.window,
12738 				GetAtom!("_NET_WM_PID", true)(display),
12739 				XA_CARDINAL,
12740 				32 /* bits */,
12741 				0 /*PropModeReplace*/,
12742 				&pid,
12743 				1);
12744 
12745 			if(isTransient && parent) { // customizationFlags & WindowFlags.transient) {
12746 				if(parent is null) assert(0);
12747 				XChangeProperty(
12748 					display,
12749 					impl.window,
12750 					GetAtom!("WM_TRANSIENT_FOR", true)(display),
12751 					XA_WINDOW,
12752 					32 /* bits */,
12753 					0 /*PropModeReplace*/,
12754 					&parent.impl.window,
12755 					1);
12756 
12757 			}
12758 
12759 			if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) {
12760 				XMapWindow(display, window);
12761 			} else {
12762 				_hidden = true;
12763 			}
12764 		}
12765 
12766 		void selectDefaultInput(bool forceIncludeMouseMotion) {
12767 			auto mask = EventMask.ExposureMask |
12768 				EventMask.KeyPressMask |
12769 				EventMask.KeyReleaseMask |
12770 				EventMask.PropertyChangeMask |
12771 				EventMask.FocusChangeMask |
12772 				EventMask.StructureNotifyMask |
12773 				EventMask.VisibilityChangeMask
12774 				| EventMask.ButtonPressMask
12775 				| EventMask.ButtonReleaseMask
12776 			;
12777 
12778 			// xshm is our shortcut for local connections
12779 			if(Image.impl.xshmAvailable || forceIncludeMouseMotion)
12780 				mask |= EventMask.PointerMotionMask;
12781 			else
12782 				mask |= EventMask.ButtonMotionMask;
12783 
12784 			XSelectInput(display, window, mask);
12785 		}
12786 
12787 
12788 		void setNetWMWindowType(Atom type) {
12789 			Atom[2] atoms;
12790 
12791 			atoms[0] = type;
12792 			// generic fallback
12793 			atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display);
12794 
12795 			XChangeProperty(
12796 				display,
12797 				impl.window,
12798 				GetAtom!"_NET_WM_WINDOW_TYPE"(display),
12799 				XA_ATOM,
12800 				32 /* bits */,
12801 				0 /*PropModeReplace*/,
12802 				atoms.ptr,
12803 				cast(int) atoms.length);
12804 		}
12805 
12806 		void motifHideDecorations() {
12807 			MwmHints hints;
12808 			hints.flags = MWM_HINTS_DECORATIONS;
12809 
12810 			XChangeProperty(
12811 				display,
12812 				impl.window,
12813 				GetAtom!"_MOTIF_WM_HINTS"(display),
12814 				GetAtom!"_MOTIF_WM_HINTS"(display),
12815 				32 /* bits */,
12816 				0 /*PropModeReplace*/,
12817 				&hints,
12818 				hints.sizeof / 4);
12819 		}
12820 
12821 		/*k8: unused
12822 		void createOpenGlContext() {
12823 
12824 		}
12825 		*/
12826 
12827 		void closeWindow() {
12828 			// I can't close this or a child window closing will
12829 			// break events for everyone. So I'm just leaking it right
12830 			// now and that is probably perfectly fine...
12831 			version(none)
12832 			if (customEventFDRead != -1) {
12833 				import core.sys.posix.unistd : close;
12834 				auto same = customEventFDRead == customEventFDWrite;
12835 
12836 				close(customEventFDRead);
12837 				if(!same)
12838 					close(customEventFDWrite);
12839 				customEventFDRead = -1;
12840 				customEventFDWrite = -1;
12841 			}
12842 			if(buffer)
12843 				XFreePixmap(display, buffer);
12844 			bufferw = bufferh = 0;
12845 			if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr);
12846 			XDestroyWindow(display, window);
12847 			XFlush(display);
12848 		}
12849 
12850 		void dispose() {
12851 		}
12852 
12853 		bool destroyed = false;
12854 	}
12855 
12856 	bool insideXEventLoop;
12857 }
12858 
12859 version(X11) {
12860 
12861 	int mouseDoubleClickTimeout = 350; /// double click timeout. X only, you probably shouldn't change this.
12862 
12863 	void recordX11Resize(Display* display, SimpleWindow win, int width, int height) {
12864 		if(width != win.width || height != win.height) {
12865 			win._width = width;
12866 			win._height = height;
12867 
12868 			if(win.openglMode == OpenGlOptions.no) {
12869 				// FIXME: could this be more efficient?
12870 
12871 				if (win.bufferw < width || win.bufferh < height) {
12872 					//{ import core.stdc.stdio; printf("new buffer; old size: %dx%d; new size: %dx%d\n", win.bufferw, win.bufferh, cast(int)width, cast(int)height); }
12873 					// grow the internal buffer to match the window...
12874 					auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display));
12875 					{
12876 						GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
12877 						XCopyGC(win.display, win.gc, 0xffffffff, xgc);
12878 						scope(exit) XFreeGC(win.display, xgc);
12879 						XSetClipMask(win.display, xgc, None);
12880 						XSetForeground(win.display, xgc, 0);
12881 						XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height);
12882 					}
12883 					XCopyArea(display,
12884 						cast(Drawable) win.buffer,
12885 						cast(Drawable) newPixmap,
12886 						win.gc, 0, 0,
12887 						win.bufferw < width ? win.bufferw : win.width,
12888 						win.bufferh < height ? win.bufferh : win.height,
12889 						0, 0);
12890 
12891 					XFreePixmap(display, win.buffer);
12892 					win.buffer = newPixmap;
12893 					win.bufferw = width;
12894 					win.bufferh = height;
12895 				}
12896 
12897 				// clear unused parts of the buffer
12898 				if (win.bufferw > width || win.bufferh > height) {
12899 					GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null);
12900 					XCopyGC(win.display, win.gc, 0xffffffff, xgc);
12901 					scope(exit) XFreeGC(win.display, xgc);
12902 					XSetClipMask(win.display, xgc, None);
12903 					XSetForeground(win.display, xgc, 0);
12904 					immutable int maxw = (win.bufferw > width ? win.bufferw : width);
12905 					immutable int maxh = (win.bufferh > height ? win.bufferh : height);
12906 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping
12907 					XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping
12908 				}
12909 
12910 			}
12911 
12912 			version(without_opengl) {} else
12913 			if(win.openglMode == OpenGlOptions.yes && win.resizability == Resizability.automaticallyScaleIfPossible) {
12914 				glViewport(0, 0, width, height);
12915 			}
12916 
12917 			win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?!
12918 
12919 			if(win.windowResized !is null) {
12920 				XUnlockDisplay(display);
12921 				scope(exit) XLockDisplay(display);
12922 				win.windowResized(width, height);
12923 			}
12924 		}
12925 	}
12926 
12927 
12928 	/// Platform-specific, you might use it when doing a custom event loop
12929 	bool doXNextEvent(Display* display) {
12930 		bool done;
12931 		XEvent e;
12932 		XNextEvent(display, &e);
12933 		version(sddddd) {
12934 			import std.stdio, std.conv : to;
12935 			if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
12936 				if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo)
12937 				writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type));
12938 			}
12939 		}
12940 
12941 		// filter out compose events
12942 		if (XFilterEvent(&e, None)) {
12943 			//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
12944 			//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
12945 			return false;
12946 		}
12947 		// process keyboard mapping changes
12948 		if (e.type == EventType.KeymapNotify) {
12949 			//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
12950 			XRefreshKeyboardMapping(&e.xmapping);
12951 			return false;
12952 		}
12953 
12954 		version(with_eventloop)
12955 			import arsd.eventloop;
12956 
12957 		if(SimpleWindow.handleNativeGlobalEvent !is null) {
12958 			// see windows impl's comments
12959 			XUnlockDisplay(display);
12960 			scope(exit) XLockDisplay(display);
12961 			auto ret = SimpleWindow.handleNativeGlobalEvent(e);
12962 			if(ret == 0)
12963 				return done;
12964 		}
12965 
12966 
12967 		if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) {
12968 			if(win.getNativeEventHandler !is null) {
12969 				XUnlockDisplay(display);
12970 				scope(exit) XLockDisplay(display);
12971 				auto ret = win.getNativeEventHandler()(e);
12972 				if(ret == 0)
12973 					return done;
12974 			}
12975 		}
12976 
12977 		switch(e.type) {
12978 		  case EventType.SelectionClear:
12979 		  	if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) {
12980 				// FIXME so it is supposed to finish any in progress transfers... but idk...
12981 				//import std.stdio; writeln("SelectionClear");
12982 				SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection);
12983 			}
12984 		  break;
12985 		  case EventType.SelectionRequest:
12986 		  	if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping)
12987 			if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) {
12988 				// import std.stdio; printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target));
12989 				XUnlockDisplay(display);
12990 				scope(exit) XLockDisplay(display);
12991 				(*ssh).handleRequest(e);
12992 			}
12993 		  break;
12994 		  case EventType.PropertyNotify:
12995 			// import std.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state);
12996 
12997 			foreach(ssh; SimpleWindow.impl.setSelectionHandlers) {
12998 				if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete)
12999 					ssh.sendMoreIncr(&e.xproperty);
13000 			}
13001 
13002 
13003 		  	if(auto win = e.xproperty.window in SimpleWindow.nativeMapping)
13004 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
13005 				if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) {
13006 					Atom target;
13007 					int format;
13008 					arch_ulong bytesafter, length;
13009 					void* value;
13010 
13011 					ubyte[] s;
13012 					Atom targetToKeep;
13013 
13014 					XGetWindowProperty(
13015 						e.xproperty.display,
13016 						e.xproperty.window,
13017 						e.xproperty.atom,
13018 						0,
13019 						100000 /* length */,
13020 						true, /* erase it to signal we got it and want more */
13021 						0 /*AnyPropertyType*/,
13022 						&target, &format, &length, &bytesafter, &value);
13023 
13024 					if(!targetToKeep)
13025 						targetToKeep = target;
13026 
13027 					auto id = (cast(ubyte*) value)[0 .. length];
13028 
13029 					handler.handleIncrData(targetToKeep, id);
13030 
13031 					XFree(value);
13032 				}
13033 			}
13034 		  break;
13035 		  case EventType.SelectionNotify:
13036 		  	if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
13037 		  	if(auto handler = e.xproperty.atom in win.getSelectionHandlers) {
13038 				if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) {
13039 					XUnlockDisplay(display);
13040 					scope(exit) XLockDisplay(display);
13041 					handler.handleData(None, null);
13042 				} else {
13043 					Atom target;
13044 					int format;
13045 					arch_ulong bytesafter, length;
13046 					void* value;
13047 					XGetWindowProperty(
13048 						e.xselection.display,
13049 						e.xselection.requestor,
13050 						e.xselection.property,
13051 						0,
13052 						100000 /* length */,
13053 						//false, /* don't erase it */
13054 						true, /* do erase it lol */
13055 						0 /*AnyPropertyType*/,
13056 						&target, &format, &length, &bytesafter, &value);
13057 
13058 					// FIXME: I don't have to copy it now since it is in char[] instead of string
13059 
13060 					{
13061 						XUnlockDisplay(display);
13062 						scope(exit) XLockDisplay(display);
13063 
13064 						if(target == XA_ATOM) {
13065 							// initial request, see what they are able to work with and request the best one
13066 							// we can handle, if available
13067 
13068 							Atom[] answer = (cast(Atom*) value)[0 .. length];
13069 							Atom best = handler.findBestFormat(answer);
13070 
13071 							/+
13072 							writeln("got ", answer);
13073 							foreach(a; answer)
13074 								printf("%s\n", XGetAtomName(display, a));
13075 							writeln("best ", best);
13076 							+/
13077 
13078 							if(best != None) {
13079 								// actually request the best format
13080 								XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/);
13081 							}
13082 						} else if(target == GetAtom!"INCR"(display)) {
13083 							// incremental
13084 
13085 							handler.prepareIncremental(e.xselection.requestor, e.xselection.property);
13086 
13087 							// signal the sending program that we see
13088 							// the incr and are ready to receive more.
13089 							XDeleteProperty(
13090 								e.xselection.display,
13091 								e.xselection.requestor,
13092 								e.xselection.property);
13093 						} else {
13094 							// unsupported type... maybe, forward
13095 							handler.handleData(target, cast(ubyte[]) value[0 .. length]);
13096 						}
13097 					}
13098 					XFree(value);
13099 					/*
13100 					XDeleteProperty(
13101 						e.xselection.display,
13102 						e.xselection.requestor,
13103 						e.xselection.property);
13104 					*/
13105 				}
13106 			}
13107 		  break;
13108 		  case EventType.ConfigureNotify:
13109 			auto event = e.xconfigure;
13110 		 	if(auto win = event.window in SimpleWindow.nativeMapping) {
13111 					//version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); }
13112 
13113 				recordX11Resize(display, *win, event.width, event.height);
13114 			}
13115 		  break;
13116 		  case EventType.Expose:
13117 		 	if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
13118 				// if it is closing from a popup menu, it can get
13119 				// an Expose event right by the end and trigger a
13120 				// BadDrawable error ... we'll just check
13121 				// closed to handle that.
13122 				if((*win).closed) break;
13123 				if((*win).openglMode == OpenGlOptions.no) {
13124 					bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh
13125 					if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
13126 					if (doCopy) XCopyArea(display, cast(Drawable) (*win).buffer, cast(Drawable) (*win).window, (*win).gc, e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.x, e.xexpose.y);
13127 				} else {
13128 					// need to redraw the scene somehow
13129 					if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all
13130 						XUnlockDisplay(display);
13131 						scope(exit) XLockDisplay(display);
13132 						version(without_opengl) {} else
13133 						win.redrawOpenGlSceneSoon();
13134 					}
13135 				}
13136 			}
13137 		  break;
13138 		  case EventType.FocusIn:
13139 		  case EventType.FocusOut:
13140 		  	if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
13141 				if (win.xic !is null) {
13142 					//{ import core.stdc.stdio : printf; printf("XIC focus change!\n"); }
13143 					if (e.type == EventType.FocusIn) XSetICFocus(win.xic); else XUnsetICFocus(win.xic);
13144 				}
13145 
13146 				win._focused = e.type == EventType.FocusIn;
13147 
13148 				if(win.demandingAttention)
13149 					demandAttention(*win, false);
13150 
13151 				if(win.onFocusChange) {
13152 					XUnlockDisplay(display);
13153 					scope(exit) XLockDisplay(display);
13154 					win.onFocusChange(e.type == EventType.FocusIn);
13155 				}
13156 			}
13157 		  break;
13158 		  case EventType.VisibilityNotify:
13159 				if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
13160 					if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) {
13161 						if (win.visibilityChanged !is null) {
13162 								XUnlockDisplay(display);
13163 								scope(exit) XLockDisplay(display);
13164 								win.visibilityChanged(false);
13165 							}
13166 					} else {
13167 						if (win.visibilityChanged !is null) {
13168 							XUnlockDisplay(display);
13169 							scope(exit) XLockDisplay(display);
13170 							win.visibilityChanged(true);
13171 						}
13172 					}
13173 				}
13174 				break;
13175 		  case EventType.ClientMessage:
13176 				if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) {
13177 					// "ignore next mouse motion" event, increment ignore counter for teh window
13178 					if (auto win = e.xclient.window in SimpleWindow.nativeMapping) {
13179 						++(*win).warpEventCount;
13180 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); }
13181 					} else {
13182 						debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); }
13183 					}
13184 				} else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) {
13185 					// user clicked the close button on the window manager
13186 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
13187 						XUnlockDisplay(display);
13188 						scope(exit) XLockDisplay(display);
13189 						if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close();
13190 					}
13191 
13192 				} else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) {
13193 					import std.stdio; writeln("HAPPENED");
13194 					// user clicked the close button on the window manager
13195 					if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
13196 						XUnlockDisplay(display);
13197 						scope(exit) XLockDisplay(display);
13198 
13199 						// FIXME: so this is actually supposed to focus to a relevant child window if appropriate
13200 						XSetInputFocus(display, e.xclient.window, RevertToParent, e.xclient.data.l[1]);
13201 					}
13202 				} else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) {
13203 					foreach(nai; NotificationAreaIcon.activeIcons)
13204 						nai.newManager();
13205 				} else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) {
13206 
13207 					bool xDragWindow = true;
13208 					if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) {
13209 						//XDefineCursor(display, xDragWindow.impl.window,
13210 							//import std.stdio; writeln("XdndStatus ", e.xclient.data.l);
13211 					}
13212 					if(auto dh = win.dropHandler) {
13213 
13214 						static Atom[3] xFormatsBuffer;
13215 						static Atom[] xFormats;
13216 
13217 						void resetXFormats() {
13218 							xFormatsBuffer[] = 0;
13219 							xFormats = xFormatsBuffer[];
13220 						}
13221 
13222 						if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) {
13223 							// on Windows it is supposed to return the effect you actually do FIXME
13224 
13225 							auto sourceWindow =  e.xclient.data.l[0];
13226 
13227 							xFormatsBuffer[0] = e.xclient.data.l[2];
13228 							xFormatsBuffer[1] = e.xclient.data.l[3];
13229 							xFormatsBuffer[2] = e.xclient.data.l[4];
13230 
13231 							if(e.xclient.data.l[1] & 1) {
13232 								// can just grab it all but like we don't necessarily need them...
13233 								xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM);
13234 							} else {
13235 								int len;
13236 								foreach(fmt; xFormatsBuffer)
13237 									if(fmt) len++;
13238 								xFormats = xFormatsBuffer[0 .. len];
13239 							}
13240 
13241 							auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats);
13242 
13243 							dh.dragEnter(&pkg);
13244 						} else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) {
13245 
13246 							auto pack = e.xclient.data.l[2];
13247 
13248 							auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords
13249 
13250 
13251 							XClientMessageEvent xclient;
13252 
13253 							xclient.type = EventType.ClientMessage;
13254 							xclient.window = e.xclient.data.l[0];
13255 							xclient.message_type = GetAtom!"XdndStatus"(display);
13256 							xclient.format = 32;
13257 							xclient.data.l[0] = win.impl.window;
13258 							xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept
13259 							auto r = result.consistentWithin;
13260 							xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top);
13261 							xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height);
13262 							xclient.data.l[4] = dndActionAtom(e.xany.display, result.action);
13263 
13264 							XSendEvent(
13265 								display,
13266 								e.xclient.data.l[0],
13267 								false,
13268 								EventMask.NoEventMask,
13269 								cast(XEvent*) &xclient
13270 							);
13271 
13272 
13273 						} else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) {
13274 							//import std.stdio; writeln("XdndLeave");
13275 							// drop cancelled.
13276 							// data.l[0] is the source window
13277 							dh.dragLeave();
13278 
13279 							resetXFormats();
13280 						} else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) {
13281 							// drop happening, should fetch data, then send finished
13282 							//import std.stdio; writeln("XdndDrop");
13283 
13284 							auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats);
13285 
13286 							dh.drop(&pkg);
13287 
13288 							resetXFormats();
13289 						} else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) {
13290 							// import std.stdio; writeln("XdndFinished");
13291 
13292 							dh.finish();
13293 						}
13294 
13295 					}
13296 				}
13297 		  break;
13298 		  case EventType.MapNotify:
13299 				if(auto win = e.xmap.window in SimpleWindow.nativeMapping) {
13300 					(*win)._visible = true;
13301 					if (!(*win)._visibleForTheFirstTimeCalled) {
13302 						(*win)._visibleForTheFirstTimeCalled = true;
13303 						if ((*win).visibleForTheFirstTime !is null) {
13304 							XUnlockDisplay(display);
13305 							scope(exit) XLockDisplay(display);
13306 							version(without_opengl) {} else {
13307 								if((*win).openglMode == OpenGlOptions.yes) {
13308 									(*win).setAsCurrentOpenGlContextNT();
13309 									glViewport(0, 0, (*win).width, (*win).height);
13310 								}
13311 							}
13312 							(*win).visibleForTheFirstTime();
13313 						}
13314 					}
13315 					if ((*win).visibilityChanged !is null) {
13316 						XUnlockDisplay(display);
13317 						scope(exit) XLockDisplay(display);
13318 						(*win).visibilityChanged(true);
13319 					}
13320 				}
13321 		  break;
13322 		  case EventType.UnmapNotify:
13323 				if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
13324 					win._visible = false;
13325 					if (win.visibilityChanged !is null) {
13326 						XUnlockDisplay(display);
13327 						scope(exit) XLockDisplay(display);
13328 						win.visibilityChanged(false);
13329 					}
13330 			}
13331 		  break;
13332 		  case EventType.DestroyNotify:
13333 			if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
13334 				if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry
13335 				win._closed = true; // just in case
13336 				win.destroyed = true;
13337 				if (win.xic !is null) {
13338 					XDestroyIC(win.xic);
13339 					win.xic = null; // just in calse
13340 				}
13341 				SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
13342 				bool anyImportant = false;
13343 				foreach(SimpleWindow w; SimpleWindow.nativeMapping)
13344 					if(w.beingOpenKeepsAppOpen) {
13345 						anyImportant = true;
13346 						break;
13347 					}
13348 				if(!anyImportant)
13349 					done = true;
13350 			}
13351 			auto window = e.xdestroywindow.window;
13352 			if(window in CapableOfHandlingNativeEvent.nativeHandleMapping)
13353 				CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window);
13354 
13355 			version(with_eventloop) {
13356 				if(done) exit();
13357 			}
13358 		  break;
13359 
13360 		  case EventType.MotionNotify:
13361 			MouseEvent mouse;
13362 			auto event = e.xmotion;
13363 
13364 			mouse.type = MouseEventType.motion;
13365 			mouse.x = event.x;
13366 			mouse.y = event.y;
13367 			mouse.modifierState = event.state;
13368 
13369 			mouse.timestamp = event.time;
13370 
13371 			if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
13372 				mouse.window = *win;
13373 				if (win.warpEventCount > 0) {
13374 					debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); }
13375 					--(*win).warpEventCount;
13376 					(*win).mdx(mouse); // so deltas will be correctly updated
13377 				} else {
13378 					win.warpEventCount = 0; // just in case
13379 					(*win).mdx(mouse);
13380 					if((*win).handleMouseEvent) {
13381 						XUnlockDisplay(display);
13382 						scope(exit) XLockDisplay(display);
13383 						(*win).handleMouseEvent(mouse);
13384 					}
13385 				}
13386 			}
13387 
13388 		  	version(with_eventloop)
13389 				send(mouse);
13390 		  break;
13391 		  case EventType.ButtonPress:
13392 		  case EventType.ButtonRelease:
13393 			MouseEvent mouse;
13394 			auto event = e.xbutton;
13395 
13396 			mouse.timestamp = event.time;
13397 
13398 			mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
13399 			mouse.x = event.x;
13400 			mouse.y = event.y;
13401 
13402 			static Time lastMouseDownTime = 0;
13403 
13404 			mouse.doubleClick = e.type == EventType.ButtonPress && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout;
13405 			if(e.type == EventType.ButtonPress) lastMouseDownTime = event.time;
13406 
13407 			switch(event.button) {
13408 				case 1: mouse.button = MouseButton.left; break; // left
13409 				case 2: mouse.button = MouseButton.middle; break; // middle
13410 				case 3: mouse.button = MouseButton.right; break; // right
13411 				case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
13412 				case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
13413 				case 6: break; // idk
13414 				case 7: break; // idk
13415 				case 8: mouse.button = MouseButton.backButton; break;
13416 				case 9: mouse.button = MouseButton.forwardButton; break;
13417 				default:
13418 			}
13419 
13420 			// FIXME: double check this
13421 			mouse.modifierState = event.state;
13422 
13423 			//mouse.modifierState = event.detail;
13424 
13425 			if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
13426 				mouse.window = *win;
13427 				(*win).mdx(mouse);
13428 				if((*win).handleMouseEvent) {
13429 					XUnlockDisplay(display);
13430 					scope(exit) XLockDisplay(display);
13431 					(*win).handleMouseEvent(mouse);
13432 				}
13433 			}
13434 			version(with_eventloop)
13435 				send(mouse);
13436 		  break;
13437 
13438 		  case EventType.KeyPress:
13439 		  case EventType.KeyRelease:
13440 			//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
13441 			KeyEvent ke;
13442 			ke.pressed = e.type == EventType.KeyPress;
13443 			ke.hardwareCode = cast(ubyte) e.xkey.keycode;
13444 
13445 			auto sym = XKeycodeToKeysym(
13446 				XDisplayConnection.get(),
13447 				e.xkey.keycode,
13448 				0);
13449 
13450 			ke.key = cast(Key) sym;//e.xkey.keycode;
13451 
13452 			ke.modifierState = e.xkey.state;
13453 
13454 			// import std.stdio; writefln("%x", sym);
13455 			wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
13456 			int charbuflen = 0; // return value of XwcLookupString
13457 			if (ke.pressed) {
13458 				auto win = e.xkey.window in SimpleWindow.nativeMapping;
13459 				if (win !is null && win.xic !is null) {
13460 					//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
13461 					Status status;
13462 					charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
13463 					//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
13464 				} else {
13465 					//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
13466 					// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
13467 					char[16] buffer;
13468 					auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
13469 					if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
13470 				}
13471 			}
13472 
13473 			// if there's no char, subst one
13474 			if (charbuflen == 0) {
13475 				switch (sym) {
13476 					case 0xff09: charbuf[charbuflen++] = '\t'; break;
13477 					case 0xff8d: // keypad enter
13478 					case 0xff0d: charbuf[charbuflen++] = '\n'; break;
13479 					default : // ignore
13480 				}
13481 			}
13482 
13483 			if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
13484 				ke.window = *win;
13485 
13486 
13487 				if(win.inputProxy)
13488 					win = &win.inputProxy;
13489 
13490 				if (win.handleKeyEvent) {
13491 					XUnlockDisplay(display);
13492 					scope(exit) XLockDisplay(display);
13493 					win.handleKeyEvent(ke);
13494 				}
13495 
13496 				// char events are separate since they are on Windows too
13497 				// also, xcompose can generate long char sequences
13498 				// don't send char events if Meta and/or Hyper is pressed
13499 				// TODO: ctrl+char should only send control chars; not yet
13500 				if ((e.xkey.state&ModifierState.ctrl) != 0) {
13501 					if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0;
13502 				}
13503 				if (ke.pressed && charbuflen > 0 && (e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0) {
13504 					// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
13505 					foreach (immutable dchar ch; charbuf[0..charbuflen]) {
13506 						if (win.handleCharEvent) {
13507 							XUnlockDisplay(display);
13508 							scope(exit) XLockDisplay(display);
13509 							win.handleCharEvent(ch);
13510 						}
13511 					}
13512 				}
13513 			}
13514 
13515 			version(with_eventloop)
13516 				send(ke);
13517 		  break;
13518 		  default:
13519 		}
13520 
13521 		return done;
13522 	}
13523 }
13524 
13525 /* *************************************** */
13526 /*      Done with simpledisplay stuff      */
13527 /* *************************************** */
13528 
13529 // Necessary C library bindings follow
13530 version(Windows) {} else
13531 version(X11) {
13532 
13533 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
13534 
13535 // X11 bindings needed here
13536 /*
13537 	A little of this is from the bindings project on
13538 	D Source and some of it is copy/paste from the C
13539 	header.
13540 
13541 	The DSource listing consistently used D's long
13542 	where C used long. That's wrong - C long is 32 bit, so
13543 	it should be int in D. I changed that here.
13544 
13545 	Note:
13546 	This isn't complete, just took what I needed for myself.
13547 */
13548 
13549 import core.stdc.stddef : wchar_t;
13550 
13551 interface XLib {
13552 extern(C) nothrow @nogc {
13553 	char* XResourceManagerString(Display*);
13554 	void XrmInitialize();
13555 	XrmDatabase XrmGetStringDatabase(char* data);
13556 	bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*);
13557 
13558 	Cursor XCreateFontCursor(Display*, uint shape);
13559 	int XDefineCursor(Display* display, Window w, Cursor cursor);
13560 	int XUndefineCursor(Display* display, Window w);
13561 
13562 	Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height);
13563 	Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y);
13564 	int XFreeCursor(Display* display, Cursor cursor);
13565 
13566 	int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
13567 
13568 	int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
13569 
13570 	char *XKeysymToString(KeySym keysym);
13571 	KeySym XKeycodeToKeysym(
13572 		Display*		/* display */,
13573 		KeyCode		/* keycode */,
13574 		int			/* index */
13575 	);
13576 
13577 	int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
13578 
13579 	int XFree(void*);
13580 	int XDeleteProperty(Display *display, Window w, Atom property);
13581 
13582 	int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements);
13583 
13584 	int XGetWindowProperty(Display *display, Window w, Atom property, arch_long
13585 		long_offset, arch_long long_length, Bool del, Atom req_type, Atom
13586 		*actual_type_return, int *actual_format_return, arch_ulong
13587 		*nitems_return, arch_ulong *bytes_after_return, void** prop_return);
13588 	Atom* XListProperties(Display *display, Window w, int *num_prop_return);
13589 	Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property);
13590 	Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return);
13591 
13592 	int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
13593 
13594 	Window XGetSelectionOwner(Display *display, Atom selection);
13595 
13596 	XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*);
13597 
13598 	Display* XOpenDisplay(const char*);
13599 	int XCloseDisplay(Display*);
13600 
13601 	Bool XQueryExtension(Display*, const char*, int*, int*, int*);
13602 
13603 	Bool XSupportsLocale();
13604 	char* XSetLocaleModifiers(const(char)* modifier_list);
13605 	XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
13606 	Status XCloseOM(XOM om);
13607 
13608 	XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class);
13609 	Status XCloseIM(XIM im);
13610 
13611 	char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
13612 	char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/;
13613 	Display* XDisplayOfIM(XIM im);
13614 	char* XLocaleOfIM(XIM im);
13615 	XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/;
13616 	void XDestroyIC(XIC ic);
13617 	void XSetICFocus(XIC ic);
13618 	void XUnsetICFocus(XIC ic);
13619 	//wchar_t* XwcResetIC(XIC ic);
13620 	char* XmbResetIC(XIC ic);
13621 	char* Xutf8ResetIC(XIC ic);
13622 	char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
13623 	char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/;
13624 	XIM XIMOfIC(XIC ic);
13625 
13626 	uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send);
13627 
13628 
13629 	XFontStruct *XLoadQueryFont(Display *display, in char *name);
13630 	int XFreeFont(Display *display, XFontStruct *font_struct);
13631 	int XSetFont(Display* display, GC gc, Font font);
13632 	int XTextWidth(XFontStruct*, in char*, int);
13633 
13634 	int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style);
13635 	int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n);
13636 
13637 	Window XCreateSimpleWindow(
13638 		Display*	/* display */,
13639 		Window		/* parent */,
13640 		int			/* x */,
13641 		int			/* y */,
13642 		uint		/* width */,
13643 		uint		/* height */,
13644 		uint		/* border_width */,
13645 		uint		/* border */,
13646 		uint		/* background */
13647 	);
13648 	Window XCreateWindow(Display *display, Window parent, int x, int y, uint width, uint height, uint border_width, int depth, uint class_, Visual *visual, arch_ulong valuemask, XSetWindowAttributes *attributes);
13649 
13650 	int XReparentWindow(Display*, Window, Window, int, int);
13651 	int XClearWindow(Display*, Window);
13652 	int XMoveResizeWindow(Display*, Window, int, int, uint, uint);
13653 	int XMoveWindow(Display*, Window, int, int);
13654 	int XResizeWindow(Display *display, Window w, uint width, uint height);
13655 
13656 	Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc);
13657 
13658 	Status XGetWindowAttributes(Display*, Window, XWindowAttributes*);
13659 
13660 	XImage *XCreateImage(
13661 		Display*		/* display */,
13662 		Visual*		/* visual */,
13663 		uint	/* depth */,
13664 		int			/* format */,
13665 		int			/* offset */,
13666 		ubyte*		/* data */,
13667 		uint	/* width */,
13668 		uint	/* height */,
13669 		int			/* bitmap_pad */,
13670 		int			/* bytes_per_line */
13671 	);
13672 
13673 	Status XInitImage (XImage* image);
13674 
13675 	Atom XInternAtom(
13676 		Display*		/* display */,
13677 		const char*	/* atom_name */,
13678 		Bool		/* only_if_exists */
13679 	);
13680 
13681 	Status XInternAtoms(Display*, const char**, int, Bool, Atom*);
13682 	char* XGetAtomName(Display*, Atom);
13683 	Status XGetAtomNames(Display*, Atom*, int count, char**);
13684 
13685 	int XPutImage(
13686 		Display*	/* display */,
13687 		Drawable	/* d */,
13688 		GC			/* gc */,
13689 		XImage*	/* image */,
13690 		int			/* src_x */,
13691 		int			/* src_y */,
13692 		int			/* dest_x */,
13693 		int			/* dest_y */,
13694 		uint		/* width */,
13695 		uint		/* height */
13696 	);
13697 
13698 	XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format);
13699 
13700 
13701 	int XDestroyWindow(
13702 		Display*	/* display */,
13703 		Window		/* w */
13704 	);
13705 
13706 	int XDestroyImage(XImage*);
13707 
13708 	int XSelectInput(
13709 		Display*	/* display */,
13710 		Window		/* w */,
13711 		EventMask	/* event_mask */
13712 	);
13713 
13714 	int XMapWindow(
13715 		Display*	/* display */,
13716 		Window		/* w */
13717 	);
13718 
13719 	Status XIconifyWindow(Display*, Window, int);
13720 	int XMapRaised(Display*, Window);
13721 	int XMapSubwindows(Display*, Window);
13722 
13723 	int XNextEvent(
13724 		Display*	/* display */,
13725 		XEvent*		/* event_return */
13726 	);
13727 
13728 	int XMaskEvent(Display*, arch_long, XEvent*);
13729 
13730 	Bool XFilterEvent(XEvent *event, Window window);
13731 	int XRefreshKeyboardMapping(XMappingEvent *event_map);
13732 
13733 	Status XSetWMProtocols(
13734 		Display*	/* display */,
13735 		Window		/* w */,
13736 		Atom*		/* protocols */,
13737 		int			/* count */
13738 	);
13739 
13740 	void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints);
13741 	Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return);
13742 
13743 
13744 	Status XInitThreads();
13745 	void XLockDisplay (Display* display);
13746 	void XUnlockDisplay (Display* display);
13747 
13748 	void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*);
13749 
13750 	int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel);
13751 	int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap);
13752 	//int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel);
13753 	//int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap);
13754 	//int XSetWindowBorderWidth (Display* display, Window w, uint width);
13755 
13756 
13757 	// check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial
13758 	int XDrawString(Display*, Drawable, GC, int, int, in char*, int);
13759 	int XDrawLine(Display*, Drawable, GC, int, int, int, int);
13760 	int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint);
13761 	int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
13762 	int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint);
13763 	int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int);
13764 	int XDrawPoint(Display*, Drawable, GC, int, int);
13765 	int XSetForeground(Display*, GC, uint);
13766 	int XSetBackground(Display*, GC, uint);
13767 
13768 	XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**);
13769 	void XFreeFontSet(Display*, XFontSet);
13770 	void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int);
13771 	void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int);
13772 
13773 	int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
13774 	 	
13775 
13776 //Status Xutf8TextPerCharExtents(XFontSet font_set, char *string, int num_bytes, XRectangle *ink_array_return, XRectangle *logical_array_return, int array_size, int *num_chars_return, XRectangle *overall_ink_return, XRectangle *overall_logical_return);
13777 
13778 	void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int);
13779 	int XSetFunction(Display*, GC, int);
13780 
13781 	GC XCreateGC(Display*, Drawable, uint, void*);
13782 	int XCopyGC(Display*, GC, uint, GC);
13783 	int XFreeGC(Display*, GC);
13784 
13785 	bool XCheckWindowEvent(Display*, Window, int, XEvent*);
13786 	bool XCheckMaskEvent(Display*, int, XEvent*);
13787 
13788 	int XPending(Display*);
13789 	int XEventsQueued(Display* display, int mode);
13790 
13791 	Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint);
13792 	int XFreePixmap(Display*, Pixmap);
13793 	int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int);
13794 	int XFlush(Display*);
13795 	int XBell(Display*, int);
13796 	int XSync(Display*, bool);
13797 
13798 	int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
13799 	int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
13800 	KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
13801 
13802 	int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode);
13803 	int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode);
13804 
13805 	Status XAllocColor(Display*, Colormap, XColor*);
13806 
13807 	int XWithdrawWindow(Display*, Window, int);
13808 	int XUnmapWindow(Display*, Window);
13809 	int XLowerWindow(Display*, Window);
13810 	int XRaiseWindow(Display*, Window);
13811 
13812 	int XWarpPointer(Display *display, Window src_w, Window dest_w, int src_x, int src_y, uint src_width, uint src_height, int dest_x, int dest_y);
13813 	Bool XTranslateCoordinates(Display *display, Window src_w, Window dest_w, int src_x, int src_y, int *dest_x_return, int *dest_y_return, Window *child_return);
13814 
13815 	int XGetInputFocus(Display*, Window*, int*);
13816 	int XSetInputFocus(Display*, Window, int, Time);
13817 
13818 	XErrorHandler XSetErrorHandler(XErrorHandler);
13819 
13820 	int XGetErrorText(Display*, int, char*, int);
13821 
13822 	Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported);
13823 
13824 
13825 	int XGrabPointer(Display *display, Window grab_window, Bool owner_events, uint event_mask, int pointer_mode, int keyboard_mode, Window confine_to, Cursor cursor, Time time);
13826 	int XUngrabPointer(Display *display, Time time);
13827 	int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time);
13828 
13829 	int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong);
13830 
13831 	Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*);
13832 	int XSetClipMask(Display*, GC, Pixmap);
13833 	int XSetClipOrigin(Display*, GC, int, int);
13834 
13835 	void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int);
13836 
13837 	void XSetWMName(Display*, Window, XTextProperty*);
13838 	Status XGetWMName(Display*, Window, XTextProperty*);
13839 	int XStoreName(Display* display, Window w, const(char)* window_name);
13840 
13841 	XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler);
13842 
13843 }
13844 }
13845 
13846 interface Xext {
13847 extern(C) nothrow @nogc {
13848 	Status XShmAttach(Display*, XShmSegmentInfo*);
13849 	Status XShmDetach(Display*, XShmSegmentInfo*);
13850 	Status XShmPutImage(
13851 		Display*            /* dpy */,
13852 		Drawable            /* d */,
13853 		GC                  /* gc */,
13854 		XImage*             /* image */,
13855 		int                 /* src_x */,
13856 		int                 /* src_y */,
13857 		int                 /* dst_x */,
13858 		int                 /* dst_y */,
13859 		uint        /* src_width */,
13860 		uint        /* src_height */,
13861 		Bool                /* send_event */
13862 	);
13863 
13864 	Status XShmQueryExtension(Display*);
13865 
13866 	XImage *XShmCreateImage(
13867 		Display*            /* dpy */,
13868 		Visual*             /* visual */,
13869 		uint        /* depth */,
13870 		int                 /* format */,
13871 		char*               /* data */,
13872 		XShmSegmentInfo*    /* shminfo */,
13873 		uint        /* width */,
13874 		uint        /* height */
13875 	);
13876 
13877 	Pixmap XShmCreatePixmap(
13878 		Display*            /* dpy */,
13879 		Drawable            /* d */,
13880 		char*               /* data */,
13881 		XShmSegmentInfo*    /* shminfo */,
13882 		uint        /* width */,
13883 		uint        /* height */,
13884 		uint        /* depth */
13885 	);
13886 
13887 }
13888 }
13889 
13890 	// this requires -lXpm
13891 	//int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes
13892 
13893 
13894 mixin DynamicLoad!(XLib, "X11", 6) xlib;
13895 mixin DynamicLoad!(Xext, "Xext", 6) xext;
13896 shared static this() {
13897 	xlib.loadDynamicLibrary();
13898 	xext.loadDynamicLibrary();
13899 }
13900 
13901 
13902 extern(C) nothrow @nogc {
13903 
13904 alias XrmDatabase = void*;
13905 struct XrmValue {
13906 	uint size;
13907 	void* addr;
13908 }
13909 
13910 struct XVisualInfo {
13911 	Visual* visual;
13912 	VisualID visualid;
13913 	int screen;
13914 	uint depth;
13915 	int c_class;
13916 	c_ulong red_mask;
13917 	c_ulong green_mask;
13918 	c_ulong blue_mask;
13919 	int colormap_size;
13920 	int bits_per_rgb;
13921 }
13922 
13923 enum VisualNoMask=	0x0;
13924 enum VisualIDMask=	0x1;
13925 enum VisualScreenMask=0x2;
13926 enum VisualDepthMask=	0x4;
13927 enum VisualClassMask=	0x8;
13928 enum VisualRedMaskMask=0x10;
13929 enum VisualGreenMaskMask=0x20;
13930 enum VisualBlueMaskMask=0x40;
13931 enum VisualColormapSizeMask=0x80;
13932 enum VisualBitsPerRGBMask=0x100;
13933 enum VisualAllMask=	0x1FF;
13934 
13935 
13936 // XIM and other crap
13937 struct _XOM {}
13938 struct _XIM {}
13939 struct _XIC {}
13940 alias XOM = _XOM*;
13941 alias XIM = _XIM*;
13942 alias XIC = _XIC*;
13943 
13944 alias XIMStyle = arch_ulong;
13945 enum : arch_ulong {
13946 	XIMPreeditArea      = 0x0001,
13947 	XIMPreeditCallbacks = 0x0002,
13948 	XIMPreeditPosition  = 0x0004,
13949 	XIMPreeditNothing   = 0x0008,
13950 	XIMPreeditNone      = 0x0010,
13951 	XIMStatusArea       = 0x0100,
13952 	XIMStatusCallbacks  = 0x0200,
13953 	XIMStatusNothing    = 0x0400,
13954 	XIMStatusNone       = 0x0800,
13955 }
13956 
13957 
13958 /* X Shared Memory Extension functions */
13959 	//pragma(lib, "Xshm");
13960 	alias arch_ulong ShmSeg;
13961 	struct XShmSegmentInfo {
13962 		ShmSeg shmseg;
13963 		int shmid;
13964 		ubyte* shmaddr;
13965 		Bool readOnly;
13966 	}
13967 
13968 	// and the necessary OS functions
13969 	int shmget(int, size_t, int);
13970 	void* shmat(int, in void*, int);
13971 	int shmdt(in void*);
13972 	int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/);
13973 
13974 	enum IPC_PRIVATE = 0;
13975 	enum IPC_CREAT = 512;
13976 	enum IPC_RMID = 0;
13977 
13978 /* MIT-SHM end */
13979 
13980 
13981 enum MappingType:int {
13982 	MappingModifier		=0,
13983 	MappingKeyboard		=1,
13984 	MappingPointer		=2
13985 }
13986 
13987 /* ImageFormat -- PutImage, GetImage */
13988 enum ImageFormat:int {
13989 	XYBitmap	=0,	/* depth 1, XYFormat */
13990 	XYPixmap	=1,	/* depth == drawable depth */
13991 	ZPixmap	=2	/* depth == drawable depth */
13992 }
13993 
13994 enum ModifierName:int {
13995 	ShiftMapIndex	=0,
13996 	LockMapIndex	=1,
13997 	ControlMapIndex	=2,
13998 	Mod1MapIndex	=3,
13999 	Mod2MapIndex	=4,
14000 	Mod3MapIndex	=5,
14001 	Mod4MapIndex	=6,
14002 	Mod5MapIndex	=7
14003 }
14004 
14005 enum ButtonMask:int {
14006 	Button1Mask	=1<<8,
14007 	Button2Mask	=1<<9,
14008 	Button3Mask	=1<<10,
14009 	Button4Mask	=1<<11,
14010 	Button5Mask	=1<<12,
14011 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
14012 }
14013 
14014 enum KeyOrButtonMask:uint {
14015 	ShiftMask	=1<<0,
14016 	LockMask	=1<<1,
14017 	ControlMask	=1<<2,
14018 	Mod1Mask	=1<<3,
14019 	Mod2Mask	=1<<4,
14020 	Mod3Mask	=1<<5,
14021 	Mod4Mask	=1<<6,
14022 	Mod5Mask	=1<<7,
14023 	Button1Mask	=1<<8,
14024 	Button2Mask	=1<<9,
14025 	Button3Mask	=1<<10,
14026 	Button4Mask	=1<<11,
14027 	Button5Mask	=1<<12,
14028 	AnyModifier	=1<<15/* used in GrabButton, GrabKey */
14029 }
14030 
14031 enum ButtonName:int {
14032 	Button1	=1,
14033 	Button2	=2,
14034 	Button3	=3,
14035 	Button4	=4,
14036 	Button5	=5
14037 }
14038 
14039 /* Notify modes */
14040 enum NotifyModes:int
14041 {
14042 	NotifyNormal		=0,
14043 	NotifyGrab			=1,
14044 	NotifyUngrab		=2,
14045 	NotifyWhileGrabbed	=3
14046 }
14047 enum NotifyHint = 1;	/* for MotionNotify events */
14048 
14049 /* Notify detail */
14050 enum NotifyDetail:int
14051 {
14052 	NotifyAncestor			=0,
14053 	NotifyVirtual			=1,
14054 	NotifyInferior			=2,
14055 	NotifyNonlinear			=3,
14056 	NotifyNonlinearVirtual	=4,
14057 	NotifyPointer			=5,
14058 	NotifyPointerRoot		=6,
14059 	NotifyDetailNone		=7
14060 }
14061 
14062 /* Visibility notify */
14063 
14064 enum VisibilityNotify:int
14065 {
14066 VisibilityUnobscured		=0,
14067 VisibilityPartiallyObscured	=1,
14068 VisibilityFullyObscured		=2
14069 }
14070 
14071 
14072 enum WindowStackingMethod:int
14073 {
14074 	Above		=0,
14075 	Below		=1,
14076 	TopIf		=2,
14077 	BottomIf	=3,
14078 	Opposite	=4
14079 }
14080 
14081 /* Circulation request */
14082 enum CirculationRequest:int
14083 {
14084 	PlaceOnTop		=0,
14085 	PlaceOnBottom	=1
14086 }
14087 
14088 enum PropertyNotification:int
14089 {
14090 	PropertyNewValue	=0,
14091 	PropertyDelete		=1
14092 }
14093 
14094 enum ColorMapNotification:int
14095 {
14096 	ColormapUninstalled	=0,
14097 	ColormapInstalled		=1
14098 }
14099 
14100 
14101 	struct _XPrivate {}
14102 	struct _XrmHashBucketRec {}
14103 
14104 	alias void* XPointer;
14105 	alias void* XExtData;
14106 
14107 	version( X86_64 ) {
14108 		alias ulong XID;
14109 		alias ulong arch_ulong;
14110 		alias long arch_long;
14111 	} else {
14112 		alias uint XID;
14113 		alias uint arch_ulong;
14114 		alias int arch_long;
14115 	}
14116 
14117 	alias XID Window;
14118 	alias XID Drawable;
14119 	alias XID Pixmap;
14120 
14121 	alias arch_ulong Atom;
14122 	alias int Bool;
14123 	alias Display XDisplay;
14124 
14125 	alias int ByteOrder;
14126 	alias arch_ulong Time;
14127 	alias void ScreenFormat;
14128 
14129 	struct XImage {
14130 		int width, height;			/* size of image */
14131 		int xoffset;				/* number of pixels offset in X direction */
14132 		ImageFormat format;		/* XYBitmap, XYPixmap, ZPixmap */
14133 		void *data;					/* pointer to image data */
14134 		ByteOrder byte_order;		/* data byte order, LSBFirst, MSBFirst */
14135 		int bitmap_unit;			/* quant. of scanline 8, 16, 32 */
14136 		int bitmap_bit_order;		/* LSBFirst, MSBFirst */
14137 		int bitmap_pad;			/* 8, 16, 32 either XY or ZPixmap */
14138 		int depth;					/* depth of image */
14139 		int bytes_per_line;			/* accelarator to next line */
14140 		int bits_per_pixel;			/* bits per pixel (ZPixmap) */
14141 		arch_ulong red_mask;	/* bits in z arrangment */
14142 		arch_ulong green_mask;
14143 		arch_ulong blue_mask;
14144 		XPointer obdata;			/* hook for the object routines to hang on */
14145 		static struct F {				/* image manipulation routines */
14146 			XImage* function(
14147 				XDisplay* 			/* display */,
14148 				Visual*				/* visual */,
14149 				uint				/* depth */,
14150 				int					/* format */,
14151 				int					/* offset */,
14152 				ubyte*				/* data */,
14153 				uint				/* width */,
14154 				uint				/* height */,
14155 				int					/* bitmap_pad */,
14156 				int					/* bytes_per_line */) create_image;
14157 			int function(XImage *) destroy_image;
14158 			arch_ulong function(XImage *, int, int) get_pixel;
14159 			int function(XImage *, int, int, arch_ulong) put_pixel;
14160 			XImage* function(XImage *, int, int, uint, uint) sub_image;
14161 			int function(XImage *, arch_long) add_pixel;
14162 		}
14163 		F f;
14164 	}
14165 	version(X86_64) static assert(XImage.sizeof == 136);
14166 	else version(X86) static assert(XImage.sizeof == 88);
14167 
14168 struct XCharStruct {
14169 	short       lbearing;       /* origin to left edge of raster */
14170 	short       rbearing;       /* origin to right edge of raster */
14171 	short       width;          /* advance to next char's origin */
14172 	short       ascent;         /* baseline to top edge of raster */
14173 	short       descent;        /* baseline to bottom edge of raster */
14174 	ushort attributes;  /* per char flags (not predefined) */
14175 }
14176 
14177 /*
14178  * To allow arbitrary information with fonts, there are additional properties
14179  * returned.
14180  */
14181 struct XFontProp {
14182 	Atom name;
14183 	arch_ulong card32;
14184 }
14185 
14186 alias Atom Font;
14187 
14188 struct XFontStruct {
14189 	XExtData *ext_data;           /* Hook for extension to hang data */
14190 	Font fid;                     /* Font ID for this font */
14191 	uint direction;           /* Direction the font is painted */
14192 	uint min_char_or_byte2;   /* First character */
14193 	uint max_char_or_byte2;   /* Last character */
14194 	uint min_byte1;           /* First row that exists (for two-byte fonts) */
14195 	uint max_byte1;           /* Last row that exists (for two-byte fonts) */
14196 	Bool all_chars_exist;         /* Flag if all characters have nonzero size */
14197 	uint default_char;        /* Char to print for undefined character */
14198 	int n_properties;             /* How many properties there are */
14199 	XFontProp *properties;        /* Pointer to array of additional properties*/
14200 	XCharStruct min_bounds;       /* Minimum bounds over all existing char*/
14201 	XCharStruct max_bounds;       /* Maximum bounds over all existing char*/
14202 	XCharStruct *per_char;        /* first_char to last_char information */
14203 	int ascent;                   /* Max extent above baseline for spacing */
14204 	int descent;                  /* Max descent below baseline for spacing */
14205 }
14206 
14207 
14208 /*
14209  * Definitions of specific events.
14210  */
14211 struct XKeyEvent
14212 {
14213 	int type;			/* of event */
14214 	arch_ulong serial;		/* # of last request processed by server */
14215 	Bool send_event;	/* true if this came from a SendEvent request */
14216 	Display *display;	/* Display the event was read from */
14217 	Window window;	        /* "event" window it is reported relative to */
14218 	Window root;	        /* root window that the event occurred on */
14219 	Window subwindow;	/* child window */
14220 	Time time;		/* milliseconds */
14221 	int x, y;		/* pointer x, y coordinates in event window */
14222 	int x_root, y_root;	/* coordinates relative to root */
14223 	KeyOrButtonMask state;	/* key or button mask */
14224 	uint keycode;	/* detail */
14225 	Bool same_screen;	/* same screen flag */
14226 }
14227 version(X86_64) static assert(XKeyEvent.sizeof == 96);
14228 alias XKeyEvent XKeyPressedEvent;
14229 alias XKeyEvent XKeyReleasedEvent;
14230 
14231 struct XButtonEvent
14232 {
14233 	int type;		/* of event */
14234 	arch_ulong serial;	/* # of last request processed by server */
14235 	Bool send_event;	/* true if this came from a SendEvent request */
14236 	Display *display;	/* Display the event was read from */
14237 	Window window;	        /* "event" window it is reported relative to */
14238 	Window root;	        /* root window that the event occurred on */
14239 	Window subwindow;	/* child window */
14240 	Time time;		/* milliseconds */
14241 	int x, y;		/* pointer x, y coordinates in event window */
14242 	int x_root, y_root;	/* coordinates relative to root */
14243 	KeyOrButtonMask state;	/* key or button mask */
14244 	uint button;	/* detail */
14245 	Bool same_screen;	/* same screen flag */
14246 }
14247 alias XButtonEvent XButtonPressedEvent;
14248 alias XButtonEvent XButtonReleasedEvent;
14249 
14250 struct XMotionEvent{
14251 	int type;		/* of event */
14252 	arch_ulong serial;	/* # of last request processed by server */
14253 	Bool send_event;	/* true if this came from a SendEvent request */
14254 	Display *display;	/* Display the event was read from */
14255 	Window window;	        /* "event" window reported relative to */
14256 	Window root;	        /* root window that the event occurred on */
14257 	Window subwindow;	/* child window */
14258 	Time time;		/* milliseconds */
14259 	int x, y;		/* pointer x, y coordinates in event window */
14260 	int x_root, y_root;	/* coordinates relative to root */
14261 	KeyOrButtonMask state;	/* key or button mask */
14262 	byte is_hint;		/* detail */
14263 	Bool same_screen;	/* same screen flag */
14264 }
14265 alias XMotionEvent XPointerMovedEvent;
14266 
14267 struct XCrossingEvent{
14268 	int type;		/* of event */
14269 	arch_ulong serial;	/* # of last request processed by server */
14270 	Bool send_event;	/* true if this came from a SendEvent request */
14271 	Display *display;	/* Display the event was read from */
14272 	Window window;	        /* "event" window reported relative to */
14273 	Window root;	        /* root window that the event occurred on */
14274 	Window subwindow;	/* child window */
14275 	Time time;		/* milliseconds */
14276 	int x, y;		/* pointer x, y coordinates in event window */
14277 	int x_root, y_root;	/* coordinates relative to root */
14278 	NotifyModes mode;		/* NotifyNormal, NotifyGrab, NotifyUngrab */
14279 	NotifyDetail detail;
14280 	/*
14281 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
14282 	 * NotifyNonlinear,NotifyNonlinearVirtual
14283 	 */
14284 	Bool same_screen;	/* same screen flag */
14285 	Bool focus;		/* Boolean focus */
14286 	KeyOrButtonMask state;	/* key or button mask */
14287 }
14288 alias XCrossingEvent XEnterWindowEvent;
14289 alias XCrossingEvent XLeaveWindowEvent;
14290 
14291 struct XFocusChangeEvent{
14292 	int type;		/* FocusIn or FocusOut */
14293 	arch_ulong serial;	/* # of last request processed by server */
14294 	Bool send_event;	/* true if this came from a SendEvent request */
14295 	Display *display;	/* Display the event was read from */
14296 	Window window;		/* window of event */
14297 	NotifyModes mode;		/* NotifyNormal, NotifyWhileGrabbed,
14298 				   NotifyGrab, NotifyUngrab */
14299 	NotifyDetail detail;
14300 	/*
14301 	 * NotifyAncestor, NotifyVirtual, NotifyInferior,
14302 	 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer,
14303 	 * NotifyPointerRoot, NotifyDetailNone
14304 	 */
14305 }
14306 alias XFocusChangeEvent XFocusInEvent;
14307 alias XFocusChangeEvent XFocusOutEvent;
14308 
14309 enum CWBackPixmap              = (1L<<0);
14310 enum CWBackPixel               = (1L<<1);
14311 enum CWBorderPixmap            = (1L<<2);
14312 enum CWBorderPixel             = (1L<<3);
14313 enum CWBitGravity              = (1L<<4);
14314 enum CWWinGravity              = (1L<<5);
14315 enum CWBackingStore            = (1L<<6);
14316 enum CWBackingPlanes           = (1L<<7);
14317 enum CWBackingPixel            = (1L<<8);
14318 enum CWOverrideRedirect        = (1L<<9);
14319 enum CWSaveUnder               = (1L<<10);
14320 enum CWEventMask               = (1L<<11);
14321 enum CWDontPropagate           = (1L<<12);
14322 enum CWColormap                = (1L<<13);
14323 enum CWCursor                  = (1L<<14);
14324 
14325 struct XWindowAttributes {
14326 	int x, y;			/* location of window */
14327 	int width, height;		/* width and height of window */
14328 	int border_width;		/* border width of window */
14329 	int depth;			/* depth of window */
14330 	Visual *visual;			/* the associated visual structure */
14331 	Window root;			/* root of screen containing window */
14332 	int class_;			/* InputOutput, InputOnly*/
14333 	int bit_gravity;		/* one of the bit gravity values */
14334 	int win_gravity;		/* one of the window gravity values */
14335 	int backing_store;		/* NotUseful, WhenMapped, Always */
14336 	arch_ulong	 backing_planes;	/* planes to be preserved if possible */
14337 	arch_ulong	 backing_pixel;	/* value to be used when restoring planes */
14338 	Bool save_under;		/* boolean, should bits under be saved? */
14339 	Colormap colormap;		/* color map to be associated with window */
14340 	Bool map_installed;		/* boolean, is color map currently installed*/
14341 	int map_state;			/* IsUnmapped, IsUnviewable, IsViewable */
14342 	arch_long all_event_masks;		/* set of events all people have interest in*/
14343 	arch_long your_event_mask;		/* my event mask */
14344 	arch_long do_not_propagate_mask;	/* set of events that should not propagate */
14345 	Bool override_redirect;		/* boolean value for override-redirect */
14346 	Screen *screen;			/* back pointer to correct screen */
14347 }
14348 
14349 enum IsUnmapped = 0;
14350 enum IsUnviewable = 1;
14351 enum IsViewable = 2;
14352 
14353 struct XSetWindowAttributes {
14354 	Pixmap background_pixmap;/* background, None, or ParentRelative */
14355 	arch_ulong background_pixel;/* background pixel */
14356 	Pixmap border_pixmap;    /* border of the window or CopyFromParent */
14357 	arch_ulong border_pixel;/* border pixel value */
14358 	int bit_gravity;         /* one of bit gravity values */
14359 	int win_gravity;         /* one of the window gravity values */
14360 	int backing_store;       /* NotUseful, WhenMapped, Always */
14361 	arch_ulong backing_planes;/* planes to be preserved if possible */
14362 	arch_ulong backing_pixel;/* value to use in restoring planes */
14363 	Bool save_under;         /* should bits under be saved? (popups) */
14364 	arch_long event_mask;         /* set of events that should be saved */
14365 	arch_long do_not_propagate_mask;/* set of events that should not propagate */
14366 	Bool override_redirect;  /* boolean value for override_redirect */
14367 	Colormap colormap;       /* color map to be associated with window */
14368 	Cursor cursor;           /* cursor to be displayed (or None) */
14369 }
14370 
14371 
14372 alias int Status;
14373 
14374 
14375 enum EventMask:int
14376 {
14377 	NoEventMask				=0,
14378 	KeyPressMask			=1<<0,
14379 	KeyReleaseMask			=1<<1,
14380 	ButtonPressMask			=1<<2,
14381 	ButtonReleaseMask		=1<<3,
14382 	EnterWindowMask			=1<<4,
14383 	LeaveWindowMask			=1<<5,
14384 	PointerMotionMask		=1<<6,
14385 	PointerMotionHintMask	=1<<7,
14386 	Button1MotionMask		=1<<8,
14387 	Button2MotionMask		=1<<9,
14388 	Button3MotionMask		=1<<10,
14389 	Button4MotionMask		=1<<11,
14390 	Button5MotionMask		=1<<12,
14391 	ButtonMotionMask		=1<<13,
14392 	KeymapStateMask		=1<<14,
14393 	ExposureMask			=1<<15,
14394 	VisibilityChangeMask	=1<<16,
14395 	StructureNotifyMask		=1<<17,
14396 	ResizeRedirectMask		=1<<18,
14397 	SubstructureNotifyMask	=1<<19,
14398 	SubstructureRedirectMask=1<<20,
14399 	FocusChangeMask			=1<<21,
14400 	PropertyChangeMask		=1<<22,
14401 	ColormapChangeMask		=1<<23,
14402 	OwnerGrabButtonMask		=1<<24
14403 }
14404 
14405 struct MwmHints {
14406 	int flags;
14407 	int functions;
14408 	int decorations;
14409 	int input_mode;
14410 	int status;
14411 }
14412 
14413 enum {
14414 	MWM_HINTS_FUNCTIONS = (1L << 0),
14415 	MWM_HINTS_DECORATIONS =  (1L << 1),
14416 
14417 	MWM_FUNC_ALL = (1L << 0),
14418 	MWM_FUNC_RESIZE = (1L << 1),
14419 	MWM_FUNC_MOVE = (1L << 2),
14420 	MWM_FUNC_MINIMIZE = (1L << 3),
14421 	MWM_FUNC_MAXIMIZE = (1L << 4),
14422 	MWM_FUNC_CLOSE = (1L << 5)
14423 }
14424 
14425 import core.stdc.config : c_long, c_ulong;
14426 
14427 	/* Size hints mask bits */
14428 
14429 	enum   USPosition  = (1L << 0)          /* user specified x, y */;
14430 	enum   USSize      = (1L << 1)          /* user specified width, height */;
14431 	enum   PPosition   = (1L << 2)          /* program specified position */;
14432 	enum   PSize       = (1L << 3)          /* program specified size */;
14433 	enum   PMinSize    = (1L << 4)          /* program specified minimum size */;
14434 	enum   PMaxSize    = (1L << 5)          /* program specified maximum size */;
14435 	enum   PResizeInc  = (1L << 6)          /* program specified resize increments */;
14436 	enum   PAspect     = (1L << 7)          /* program specified min and max aspect ratios */;
14437 	enum   PBaseSize   = (1L << 8);
14438 	enum   PWinGravity = (1L << 9);
14439 	enum   PAllHints   = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect);
14440 	struct XSizeHints {
14441 		arch_long flags;         /* marks which fields in this structure are defined */
14442 		int x, y;           /* Obsolete */
14443 		int width, height;  /* Obsolete */
14444 		int min_width, min_height;
14445 		int max_width, max_height;
14446 		int width_inc, height_inc;
14447 		struct Aspect {
14448 			int x;       /* numerator */
14449 			int y;       /* denominator */
14450 		}
14451 
14452 		Aspect min_aspect;
14453 		Aspect max_aspect;
14454 		int base_width, base_height;
14455 		int win_gravity;
14456 		/* this structure may be extended in the future */
14457 	}
14458 
14459 
14460 
14461 enum EventType:int
14462 {
14463 	KeyPress			=2,
14464 	KeyRelease			=3,
14465 	ButtonPress			=4,
14466 	ButtonRelease		=5,
14467 	MotionNotify		=6,
14468 	EnterNotify			=7,
14469 	LeaveNotify			=8,
14470 	FocusIn				=9,
14471 	FocusOut			=10,
14472 	KeymapNotify		=11,
14473 	Expose				=12,
14474 	GraphicsExpose		=13,
14475 	NoExpose			=14,
14476 	VisibilityNotify	=15,
14477 	CreateNotify		=16,
14478 	DestroyNotify		=17,
14479 	UnmapNotify		=18,
14480 	MapNotify			=19,
14481 	MapRequest			=20,
14482 	ReparentNotify		=21,
14483 	ConfigureNotify		=22,
14484 	ConfigureRequest	=23,
14485 	GravityNotify		=24,
14486 	ResizeRequest		=25,
14487 	CirculateNotify		=26,
14488 	CirculateRequest	=27,
14489 	PropertyNotify		=28,
14490 	SelectionClear		=29,
14491 	SelectionRequest	=30,
14492 	SelectionNotify		=31,
14493 	ColormapNotify		=32,
14494 	ClientMessage		=33,
14495 	MappingNotify		=34,
14496 	LASTEvent			=35	/* must be bigger than any event # */
14497 }
14498 /* generated on EnterWindow and FocusIn  when KeyMapState selected */
14499 struct XKeymapEvent
14500 {
14501 	int type;
14502 	arch_ulong serial;	/* # of last request processed by server */
14503 	Bool send_event;	/* true if this came from a SendEvent request */
14504 	Display *display;	/* Display the event was read from */
14505 	Window window;
14506 	byte[32] key_vector;
14507 }
14508 
14509 struct XExposeEvent
14510 {
14511 	int type;
14512 	arch_ulong serial;	/* # of last request processed by server */
14513 	Bool send_event;	/* true if this came from a SendEvent request */
14514 	Display *display;	/* Display the event was read from */
14515 	Window window;
14516 	int x, y;
14517 	int width, height;
14518 	int count;		/* if non-zero, at least this many more */
14519 }
14520 
14521 struct XGraphicsExposeEvent{
14522 	int type;
14523 	arch_ulong serial;	/* # of last request processed by server */
14524 	Bool send_event;	/* true if this came from a SendEvent request */
14525 	Display *display;	/* Display the event was read from */
14526 	Drawable drawable;
14527 	int x, y;
14528 	int width, height;
14529 	int count;		/* if non-zero, at least this many more */
14530 	int major_code;		/* core is CopyArea or CopyPlane */
14531 	int minor_code;		/* not defined in the core */
14532 }
14533 
14534 struct XNoExposeEvent{
14535 	int type;
14536 	arch_ulong serial;	/* # of last request processed by server */
14537 	Bool send_event;	/* true if this came from a SendEvent request */
14538 	Display *display;	/* Display the event was read from */
14539 	Drawable drawable;
14540 	int major_code;		/* core is CopyArea or CopyPlane */
14541 	int minor_code;		/* not defined in the core */
14542 }
14543 
14544 struct XVisibilityEvent{
14545 	int type;
14546 	arch_ulong serial;	/* # of last request processed by server */
14547 	Bool send_event;	/* true if this came from a SendEvent request */
14548 	Display *display;	/* Display the event was read from */
14549 	Window window;
14550 	VisibilityNotify state;		/* Visibility state */
14551 }
14552 
14553 struct XCreateWindowEvent{
14554 	int type;
14555 	arch_ulong serial;	/* # of last request processed by server */
14556 	Bool send_event;	/* true if this came from a SendEvent request */
14557 	Display *display;	/* Display the event was read from */
14558 	Window parent;		/* parent of the window */
14559 	Window window;		/* window id of window created */
14560 	int x, y;		/* window location */
14561 	int width, height;	/* size of window */
14562 	int border_width;	/* border width */
14563 	Bool override_redirect;	/* creation should be overridden */
14564 }
14565 
14566 struct XDestroyWindowEvent
14567 {
14568 	int type;
14569 	arch_ulong serial;		/* # of last request processed by server */
14570 	Bool send_event;	/* true if this came from a SendEvent request */
14571 	Display *display;	/* Display the event was read from */
14572 	Window event;
14573 	Window window;
14574 }
14575 
14576 struct XUnmapEvent
14577 {
14578 	int type;
14579 	arch_ulong serial;		/* # of last request processed by server */
14580 	Bool send_event;	/* true if this came from a SendEvent request */
14581 	Display *display;	/* Display the event was read from */
14582 	Window event;
14583 	Window window;
14584 	Bool from_configure;
14585 }
14586 
14587 struct XMapEvent
14588 {
14589 	int type;
14590 	arch_ulong serial;		/* # of last request processed by server */
14591 	Bool send_event;	/* true if this came from a SendEvent request */
14592 	Display *display;	/* Display the event was read from */
14593 	Window event;
14594 	Window window;
14595 	Bool override_redirect;	/* Boolean, is override set... */
14596 }
14597 
14598 struct XMapRequestEvent
14599 {
14600 	int type;
14601 	arch_ulong serial;	/* # of last request processed by server */
14602 	Bool send_event;	/* true if this came from a SendEvent request */
14603 	Display *display;	/* Display the event was read from */
14604 	Window parent;
14605 	Window window;
14606 }
14607 
14608 struct XReparentEvent
14609 {
14610 	int type;
14611 	arch_ulong serial;	/* # of last request processed by server */
14612 	Bool send_event;	/* true if this came from a SendEvent request */
14613 	Display *display;	/* Display the event was read from */
14614 	Window event;
14615 	Window window;
14616 	Window parent;
14617 	int x, y;
14618 	Bool override_redirect;
14619 }
14620 
14621 struct XConfigureEvent
14622 {
14623 	int type;
14624 	arch_ulong serial;	/* # of last request processed by server */
14625 	Bool send_event;	/* true if this came from a SendEvent request */
14626 	Display *display;	/* Display the event was read from */
14627 	Window event;
14628 	Window window;
14629 	int x, y;
14630 	int width, height;
14631 	int border_width;
14632 	Window above;
14633 	Bool override_redirect;
14634 }
14635 
14636 struct XGravityEvent
14637 {
14638 	int type;
14639 	arch_ulong serial;	/* # of last request processed by server */
14640 	Bool send_event;	/* true if this came from a SendEvent request */
14641 	Display *display;	/* Display the event was read from */
14642 	Window event;
14643 	Window window;
14644 	int x, y;
14645 }
14646 
14647 struct XResizeRequestEvent
14648 {
14649 	int type;
14650 	arch_ulong serial;	/* # of last request processed by server */
14651 	Bool send_event;	/* true if this came from a SendEvent request */
14652 	Display *display;	/* Display the event was read from */
14653 	Window window;
14654 	int width, height;
14655 }
14656 
14657 struct  XConfigureRequestEvent
14658 {
14659 	int type;
14660 	arch_ulong serial;	/* # of last request processed by server */
14661 	Bool send_event;	/* true if this came from a SendEvent request */
14662 	Display *display;	/* Display the event was read from */
14663 	Window parent;
14664 	Window window;
14665 	int x, y;
14666 	int width, height;
14667 	int border_width;
14668 	Window above;
14669 	WindowStackingMethod detail;		/* Above, Below, TopIf, BottomIf, Opposite */
14670 	arch_ulong value_mask;
14671 }
14672 
14673 struct XCirculateEvent
14674 {
14675 	int type;
14676 	arch_ulong serial;	/* # of last request processed by server */
14677 	Bool send_event;	/* true if this came from a SendEvent request */
14678 	Display *display;	/* Display the event was read from */
14679 	Window event;
14680 	Window window;
14681 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
14682 }
14683 
14684 struct XCirculateRequestEvent
14685 {
14686 	int type;
14687 	arch_ulong serial;	/* # of last request processed by server */
14688 	Bool send_event;	/* true if this came from a SendEvent request */
14689 	Display *display;	/* Display the event was read from */
14690 	Window parent;
14691 	Window window;
14692 	CirculationRequest place;		/* PlaceOnTop, PlaceOnBottom */
14693 }
14694 
14695 struct XPropertyEvent
14696 {
14697 	int type;
14698 	arch_ulong serial;	/* # of last request processed by server */
14699 	Bool send_event;	/* true if this came from a SendEvent request */
14700 	Display *display;	/* Display the event was read from */
14701 	Window window;
14702 	Atom atom;
14703 	Time time;
14704 	PropertyNotification state;		/* NewValue, Deleted */
14705 }
14706 
14707 struct XSelectionClearEvent
14708 {
14709 	int type;
14710 	arch_ulong serial;	/* # of last request processed by server */
14711 	Bool send_event;	/* true if this came from a SendEvent request */
14712 	Display *display;	/* Display the event was read from */
14713 	Window window;
14714 	Atom selection;
14715 	Time time;
14716 }
14717 
14718 struct XSelectionRequestEvent
14719 {
14720 	int type;
14721 	arch_ulong serial;	/* # of last request processed by server */
14722 	Bool send_event;	/* true if this came from a SendEvent request */
14723 	Display *display;	/* Display the event was read from */
14724 	Window owner;
14725 	Window requestor;
14726 	Atom selection;
14727 	Atom target;
14728 	Atom property;
14729 	Time time;
14730 }
14731 
14732 struct XSelectionEvent
14733 {
14734 	int type;
14735 	arch_ulong serial;	/* # of last request processed by server */
14736 	Bool send_event;	/* true if this came from a SendEvent request */
14737 	Display *display;	/* Display the event was read from */
14738 	Window requestor;
14739 	Atom selection;
14740 	Atom target;
14741 	Atom property;		/* ATOM or None */
14742 	Time time;
14743 }
14744 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56);
14745 
14746 struct XColormapEvent
14747 {
14748 	int type;
14749 	arch_ulong serial;	/* # of last request processed by server */
14750 	Bool send_event;	/* true if this came from a SendEvent request */
14751 	Display *display;	/* Display the event was read from */
14752 	Window window;
14753 	Colormap colormap;	/* COLORMAP or None */
14754 	Bool new_;		/* C++ */
14755 	ColorMapNotification state;		/* ColormapInstalled, ColormapUninstalled */
14756 }
14757 version(X86_64) static assert(XColormapEvent.sizeof == 56);
14758 
14759 struct XClientMessageEvent
14760 {
14761 	int type;
14762 	arch_ulong serial;	/* # of last request processed by server */
14763 	Bool send_event;	/* true if this came from a SendEvent request */
14764 	Display *display;	/* Display the event was read from */
14765 	Window window;
14766 	Atom message_type;
14767 	int format;
14768 	union Data{
14769 		byte[20] b;
14770 		short[10] s;
14771 		arch_ulong[5] l;
14772 	}
14773 	Data data;
14774 
14775 }
14776 version(X86_64) static assert(XClientMessageEvent.sizeof == 96);
14777 
14778 struct XMappingEvent
14779 {
14780 	int type;
14781 	arch_ulong serial;	/* # of last request processed by server */
14782 	Bool send_event;	/* true if this came from a SendEvent request */
14783 	Display *display;	/* Display the event was read from */
14784 	Window window;		/* unused */
14785 	MappingType request;		/* one of MappingModifier, MappingKeyboard,
14786 				   MappingPointer */
14787 	int first_keycode;	/* first keycode */
14788 	int count;		/* defines range of change w. first_keycode*/
14789 }
14790 
14791 struct XErrorEvent
14792 {
14793 	int type;
14794 	Display *display;	/* Display the event was read from */
14795 	XID resourceid;		/* resource id */
14796 	arch_ulong serial;	/* serial number of failed request */
14797 	ubyte error_code;	/* error code of failed request */
14798 	ubyte request_code;	/* Major op-code of failed request */
14799 	ubyte minor_code;	/* Minor op-code of failed request */
14800 }
14801 
14802 struct XAnyEvent
14803 {
14804 	int type;
14805 	arch_ulong serial;	/* # of last request processed by server */
14806 	Bool send_event;	/* true if this came from a SendEvent request */
14807 	Display *display;/* Display the event was read from */
14808 	Window window;	/* window on which event was requested in event mask */
14809 }
14810 
14811 union XEvent{
14812 	int type;		/* must not be changed; first element */
14813 	XAnyEvent xany;
14814 	XKeyEvent xkey;
14815 	XButtonEvent xbutton;
14816 	XMotionEvent xmotion;
14817 	XCrossingEvent xcrossing;
14818 	XFocusChangeEvent xfocus;
14819 	XExposeEvent xexpose;
14820 	XGraphicsExposeEvent xgraphicsexpose;
14821 	XNoExposeEvent xnoexpose;
14822 	XVisibilityEvent xvisibility;
14823 	XCreateWindowEvent xcreatewindow;
14824 	XDestroyWindowEvent xdestroywindow;
14825 	XUnmapEvent xunmap;
14826 	XMapEvent xmap;
14827 	XMapRequestEvent xmaprequest;
14828 	XReparentEvent xreparent;
14829 	XConfigureEvent xconfigure;
14830 	XGravityEvent xgravity;
14831 	XResizeRequestEvent xresizerequest;
14832 	XConfigureRequestEvent xconfigurerequest;
14833 	XCirculateEvent xcirculate;
14834 	XCirculateRequestEvent xcirculaterequest;
14835 	XPropertyEvent xproperty;
14836 	XSelectionClearEvent xselectionclear;
14837 	XSelectionRequestEvent xselectionrequest;
14838 	XSelectionEvent xselection;
14839 	XColormapEvent xcolormap;
14840 	XClientMessageEvent xclient;
14841 	XMappingEvent xmapping;
14842 	XErrorEvent xerror;
14843 	XKeymapEvent xkeymap;
14844 	arch_ulong[24] pad;
14845 }
14846 
14847 
14848 	struct Display {
14849 		XExtData *ext_data;	/* hook for extension to hang data */
14850 		_XPrivate *private1;
14851 		int fd;			/* Network socket. */
14852 		int private2;
14853 		int proto_major_version;/* major version of server's X protocol */
14854 		int proto_minor_version;/* minor version of servers X protocol */
14855 		char *vendor;		/* vendor of the server hardware */
14856 	    	XID private3;
14857 		XID private4;
14858 		XID private5;
14859 		int private6;
14860 		XID function(Display*)resource_alloc;/* allocator function */
14861 		ByteOrder byte_order;		/* screen byte order, LSBFirst, MSBFirst */
14862 		int bitmap_unit;	/* padding and data requirements */
14863 		int bitmap_pad;		/* padding requirements on bitmaps */
14864 		ByteOrder bitmap_bit_order;	/* LeastSignificant or MostSignificant */
14865 		int nformats;		/* number of pixmap formats in list */
14866 		ScreenFormat *pixmap_format;	/* pixmap format list */
14867 		int private8;
14868 		int release;		/* release of the server */
14869 		_XPrivate *private9;
14870 		_XPrivate *private10;
14871 		int qlen;		/* Length of input event queue */
14872 		arch_ulong last_request_read; /* seq number of last event read */
14873 		arch_ulong request;	/* sequence number of last request. */
14874 		XPointer private11;
14875 		XPointer private12;
14876 		XPointer private13;
14877 		XPointer private14;
14878 		uint max_request_size; /* maximum number 32 bit words in request*/
14879 		_XrmHashBucketRec *db;
14880 		int function  (Display*)private15;
14881 		char *display_name;	/* "host:display" string used on this connect*/
14882 		int default_screen;	/* default screen for operations */
14883 		int nscreens;		/* number of screens on this server*/
14884 		Screen *screens;	/* pointer to list of screens */
14885 		arch_ulong motion_buffer;	/* size of motion buffer */
14886 		arch_ulong private16;
14887 		int min_keycode;	/* minimum defined keycode */
14888 		int max_keycode;	/* maximum defined keycode */
14889 		XPointer private17;
14890 		XPointer private18;
14891 		int private19;
14892 		byte *xdefaults;	/* contents of defaults from server */
14893 		/* there is more to this structure, but it is private to Xlib */
14894 	}
14895 
14896 	// I got these numbers from a C program as a sanity test
14897 	version(X86_64) {
14898 		static assert(Display.sizeof == 296);
14899 		static assert(XPointer.sizeof == 8);
14900 		static assert(XErrorEvent.sizeof == 40);
14901 		static assert(XAnyEvent.sizeof == 40);
14902 		static assert(XMappingEvent.sizeof == 56);
14903 		static assert(XEvent.sizeof == 192);
14904 	} else {
14905 		static assert(Display.sizeof == 176);
14906 		static assert(XPointer.sizeof == 4);
14907 		static assert(XEvent.sizeof == 96);
14908 	}
14909 
14910 struct Depth
14911 {
14912 	int depth;		/* this depth (Z) of the depth */
14913 	int nvisuals;		/* number of Visual types at this depth */
14914 	Visual *visuals;	/* list of visuals possible at this depth */
14915 }
14916 
14917 alias void* GC;
14918 alias c_ulong VisualID;
14919 alias XID Colormap;
14920 alias XID Cursor;
14921 alias XID KeySym;
14922 alias uint KeyCode;
14923 enum None = 0;
14924 }
14925 
14926 version(without_opengl) {}
14927 else {
14928 extern(C) nothrow @nogc {
14929 
14930 
14931 static if(!SdpyIsUsingIVGLBinds) {
14932 enum GLX_USE_GL=            1;       /* support GLX rendering */
14933 enum GLX_BUFFER_SIZE=       2;       /* depth of the color buffer */
14934 enum GLX_LEVEL=             3;       /* level in plane stacking */
14935 enum GLX_RGBA=              4;       /* true if RGBA mode */
14936 enum GLX_DOUBLEBUFFER=      5;       /* double buffering supported */
14937 enum GLX_STEREO=            6;       /* stereo buffering supported */
14938 enum GLX_AUX_BUFFERS=       7;       /* number of aux buffers */
14939 enum GLX_RED_SIZE=          8;       /* number of red component bits */
14940 enum GLX_GREEN_SIZE=        9;       /* number of green component bits */
14941 enum GLX_BLUE_SIZE=         10;      /* number of blue component bits */
14942 enum GLX_ALPHA_SIZE=        11;      /* number of alpha component bits */
14943 enum GLX_DEPTH_SIZE=        12;      /* number of depth bits */
14944 enum GLX_STENCIL_SIZE=      13;      /* number of stencil bits */
14945 enum GLX_ACCUM_RED_SIZE=    14;      /* number of red accum bits */
14946 enum GLX_ACCUM_GREEN_SIZE=  15;      /* number of green accum bits */
14947 enum GLX_ACCUM_BLUE_SIZE=   16;      /* number of blue accum bits */
14948 enum GLX_ACCUM_ALPHA_SIZE=  17;      /* number of alpha accum bits */
14949 
14950 
14951 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list);
14952 
14953 
14954 
14955 enum GL_TRUE = 1;
14956 enum GL_FALSE = 0;
14957 alias int GLint;
14958 }
14959 
14960 alias XID GLXContextID;
14961 alias XID GLXPixmap;
14962 alias XID GLXDrawable;
14963 alias XID GLXPbuffer;
14964 alias XID GLXWindow;
14965 alias XID GLXFBConfigID;
14966 alias void* GLXContext;
14967 
14968 }
14969 }
14970 
14971 enum AllocNone = 0;
14972 
14973 extern(C) {
14974 	/* WARNING, this type not in Xlib spec */
14975 	extern(C) alias XIOErrorHandler = int function (Display* display);
14976 }
14977 
14978 extern(C) nothrow @nogc {
14979 struct Screen{
14980 	XExtData *ext_data;		/* hook for extension to hang data */
14981 	Display *display;		/* back pointer to display structure */
14982 	Window root;			/* Root window id. */
14983 	int width, height;		/* width and height of screen */
14984 	int mwidth, mheight;	/* width and height of  in millimeters */
14985 	int ndepths;			/* number of depths possible */
14986 	Depth *depths;			/* list of allowable depths on the screen */
14987 	int root_depth;			/* bits per pixel */
14988 	Visual *root_visual;	/* root visual */
14989 	GC default_gc;			/* GC for the root root visual */
14990 	Colormap cmap;			/* default color map */
14991 	uint white_pixel;
14992 	uint black_pixel;		/* White and Black pixel values */
14993 	int max_maps, min_maps;	/* max and min color maps */
14994 	int backing_store;		/* Never, WhenMapped, Always */
14995 	bool save_unders;
14996 	int root_input_mask;	/* initial root input mask */
14997 }
14998 
14999 struct Visual
15000 {
15001 	XExtData *ext_data;	/* hook for extension to hang data */
15002 	VisualID visualid;	/* visual id of this visual */
15003 	int class_;			/* class of screen (monochrome, etc.) */
15004 	c_ulong red_mask, green_mask, blue_mask;	/* mask values */
15005 	int bits_per_rgb;	/* log base 2 of distinct color values */
15006 	int map_entries;	/* color map entries */
15007 }
15008 
15009 	alias Display* _XPrivDisplay;
15010 
15011 	Screen* ScreenOfDisplay(Display* dpy, int scr) {
15012 		assert(dpy !is null);
15013 		return &dpy.screens[scr];
15014 	}
15015 
15016 	Window	RootWindow(Display *dpy,int scr) {
15017 		return ScreenOfDisplay(dpy,scr).root;
15018 	}
15019 
15020 	struct XWMHints {
15021 		arch_long flags;
15022 		Bool input;
15023 		int initial_state;
15024 		Pixmap icon_pixmap;
15025 		Window icon_window;
15026 		int icon_x, icon_y;
15027 		Pixmap icon_mask;
15028 		XID window_group;
15029 	}
15030 
15031 	struct XClassHint {
15032 		char* res_name;
15033 		char* res_class;
15034 	}
15035 
15036 	int DefaultScreen(Display *dpy) {
15037 		return dpy.default_screen;
15038 	}
15039 
15040 	int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; }
15041 	int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; }
15042 	int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; }
15043 	int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; }
15044 	int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; }
15045 	auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; }
15046 
15047 	int ConnectionNumber(Display* dpy) { return dpy.fd; }
15048 
15049 	enum int AnyPropertyType = 0;
15050 	enum int Success = 0;
15051 
15052 	enum int RevertToNone = None;
15053 	enum int PointerRoot = 1;
15054 	enum Time CurrentTime = 0;
15055 	enum int RevertToPointerRoot = PointerRoot;
15056 	enum int RevertToParent = 2;
15057 
15058 	int DefaultDepthOfDisplay(Display* dpy) {
15059 		return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth;
15060 	}
15061 
15062 	Visual* DefaultVisual(Display *dpy,int scr) {
15063 		return ScreenOfDisplay(dpy,scr).root_visual;
15064 	}
15065 
15066 	GC DefaultGC(Display *dpy,int scr) {
15067 		return ScreenOfDisplay(dpy,scr).default_gc;
15068 	}
15069 
15070 	uint BlackPixel(Display *dpy,int scr) {
15071 		return ScreenOfDisplay(dpy,scr).black_pixel;
15072 	}
15073 
15074 	uint WhitePixel(Display *dpy,int scr) {
15075 		return ScreenOfDisplay(dpy,scr).white_pixel;
15076 	}
15077 
15078 	alias void* XFontSet; // i think
15079 	struct XmbTextItem {
15080 		char* chars;
15081 		int nchars;
15082 		int delta;
15083 		XFontSet font_set;
15084 	}
15085 
15086 	struct XTextItem {
15087 		char* chars;
15088 		int nchars;
15089 		int delta;
15090 		Font font;
15091 	}
15092 
15093 	enum {
15094 		GXclear        = 0x0, /* 0 */
15095 		GXand          = 0x1, /* src AND dst */
15096 		GXandReverse   = 0x2, /* src AND NOT dst */
15097 		GXcopy         = 0x3, /* src */
15098 		GXandInverted  = 0x4, /* NOT src AND dst */
15099 		GXnoop         = 0x5, /* dst */
15100 		GXxor          = 0x6, /* src XOR dst */
15101 		GXor           = 0x7, /* src OR dst */
15102 		GXnor          = 0x8, /* NOT src AND NOT dst */
15103 		GXequiv        = 0x9, /* NOT src XOR dst */
15104 		GXinvert       = 0xa, /* NOT dst */
15105 		GXorReverse    = 0xb, /* src OR NOT dst */
15106 		GXcopyInverted = 0xc, /* NOT src */
15107 		GXorInverted   = 0xd, /* NOT src OR dst */
15108 		GXnand         = 0xe, /* NOT src OR NOT dst */
15109 		GXset          = 0xf, /* 1 */
15110 	}
15111 	enum QueueMode : int {
15112 		QueuedAlready,
15113 		QueuedAfterReading,
15114 		QueuedAfterFlush
15115 	}
15116 
15117 	enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
15118 
15119 	struct XPoint {
15120 		short x;
15121 		short y;
15122 	}
15123 
15124 	enum CoordMode:int {
15125 		CoordModeOrigin = 0,
15126 		CoordModePrevious = 1
15127 	}
15128 
15129 	enum PolygonShape:int {
15130 		Complex = 0,
15131 		Nonconvex = 1,
15132 		Convex = 2
15133 	}
15134 
15135 	struct XTextProperty {
15136 		const(char)* value;		/* same as Property routines */
15137 		Atom encoding;			/* prop type */
15138 		int format;				/* prop data format: 8, 16, or 32 */
15139 		arch_ulong nitems;		/* number of data items in value */
15140 	}
15141 
15142 	version( X86_64 ) {
15143 		static assert(XTextProperty.sizeof == 32);
15144 	}
15145 
15146 
15147 	struct XGCValues {
15148 		int function_;           /* logical operation */
15149 		arch_ulong plane_mask;/* plane mask */
15150 		arch_ulong foreground;/* foreground pixel */
15151 		arch_ulong background;/* background pixel */
15152 		int line_width;         /* line width */
15153 		int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
15154 		int cap_style;          /* CapNotLast, CapButt,
15155 					   CapRound, CapProjecting */
15156 		int join_style;         /* JoinMiter, JoinRound, JoinBevel */
15157 		int fill_style;         /* FillSolid, FillTiled,
15158 					   FillStippled, FillOpaeueStippled */
15159 		int fill_rule;          /* EvenOddRule, WindingRule */
15160 		int arc_mode;           /* ArcChord, ArcPieSlice */
15161 		Pixmap tile;            /* tile pixmap for tiling operations */
15162 		Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
15163 		int ts_x_origin;        /* offset for tile or stipple operations */
15164 		int ts_y_origin;
15165 		Font font;              /* default text font for text operations */
15166 		int subwindow_mode;     /* ClipByChildren, IncludeInferiors */
15167 		Bool graphics_exposures;/* boolean, should exposures be generated */
15168 		int clip_x_origin;      /* origin for clipping */
15169 		int clip_y_origin;
15170 		Pixmap clip_mask;       /* bitmap clipping; other calls for rects */
15171 		int dash_offset;        /* patterned/dashed line information */
15172 		char dashes;
15173 	}
15174 
15175 	struct XColor {
15176 		arch_ulong pixel;
15177 		ushort red, green, blue;
15178 		byte flags;
15179 		byte pad;
15180 	}
15181 
15182 	alias XErrorHandler = int function(Display*, XErrorEvent*);
15183 
15184 	struct XRectangle {
15185 		short x;
15186 		short y;
15187 		ushort width;
15188 		ushort height;
15189 	}
15190 
15191 	enum ClipByChildren = 0;
15192 	enum IncludeInferiors = 1;
15193 
15194 	enum Atom XA_PRIMARY = 1;
15195 	enum Atom XA_SECONDARY = 2;
15196 	enum Atom XA_STRING = 31;
15197 	enum Atom XA_CARDINAL = 6;
15198 	enum Atom XA_WM_NAME = 39;
15199 	enum Atom XA_ATOM = 4;
15200 	enum Atom XA_WINDOW = 33;
15201 	enum Atom XA_WM_HINTS = 35;
15202 	enum int PropModeAppend = 2;
15203 	enum int PropModeReplace = 0;
15204 	enum int PropModePrepend = 1;
15205 
15206 	enum int CopyFromParent = 0;
15207 	enum int InputOutput = 1;
15208 
15209 	// XWMHints
15210 	enum InputHint = 1 << 0;
15211 	enum StateHint = 1 << 1;
15212 	enum IconPixmapHint = (1L << 2);
15213 	enum IconWindowHint = (1L << 3);
15214 	enum IconPositionHint = (1L << 4);
15215 	enum IconMaskHint = (1L << 5);
15216 	enum WindowGroupHint = (1L << 6);
15217 	enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint);
15218 	enum XUrgencyHint = (1L << 8);
15219 
15220 	// GC Components
15221 	enum GCFunction           =   (1L<<0);
15222 	enum GCPlaneMask         =    (1L<<1);
15223 	enum GCForeground       =     (1L<<2);
15224 	enum GCBackground      =      (1L<<3);
15225 	enum GCLineWidth      =       (1L<<4);
15226 	enum GCLineStyle     =        (1L<<5);
15227 	enum GCCapStyle     =         (1L<<6);
15228 	enum GCJoinStyle   =          (1L<<7);
15229 	enum GCFillStyle  =           (1L<<8);
15230 	enum GCFillRule  =            (1L<<9);
15231 	enum GCTile     =             (1L<<10);
15232 	enum GCStipple           =    (1L<<11);
15233 	enum GCTileStipXOrigin  =     (1L<<12);
15234 	enum GCTileStipYOrigin =      (1L<<13);
15235 	enum GCFont               =   (1L<<14);
15236 	enum GCSubwindowMode     =    (1L<<15);
15237 	enum GCGraphicsExposures=     (1L<<16);
15238 	enum GCClipXOrigin     =      (1L<<17);
15239 	enum GCClipYOrigin    =       (1L<<18);
15240 	enum GCClipMask      =        (1L<<19);
15241 	enum GCDashOffset   =         (1L<<20);
15242 	enum GCDashList    =          (1L<<21);
15243 	enum GCArcMode    =           (1L<<22);
15244 	enum GCLastBit   =            22;
15245 
15246 
15247 	enum int WithdrawnState = 0;
15248 	enum int NormalState = 1;
15249 	enum int IconicState = 3;
15250 
15251 }
15252 } else version (OSXCocoa) {
15253 private:
15254 	alias void* id;
15255 	alias void* Class;
15256 	alias void* SEL;
15257 	alias void* IMP;
15258 	alias void* Ivar;
15259 	alias byte BOOL;
15260 	alias const(void)* CFStringRef;
15261 	alias const(void)* CFAllocatorRef;
15262 	alias const(void)* CFTypeRef;
15263 	alias const(void)* CGContextRef;
15264 	alias const(void)* CGColorSpaceRef;
15265 	alias const(void)* CGImageRef;
15266 	alias ulong CGBitmapInfo;
15267 
15268 	struct objc_super {
15269 		id self;
15270 		Class superclass;
15271 	}
15272 
15273 	struct CFRange {
15274 		long location, length;
15275 	}
15276 
15277 	struct NSPoint {
15278 		double x, y;
15279 
15280 		static fromTuple(T)(T tupl) {
15281 			return NSPoint(tupl.tupleof);
15282 		}
15283 	}
15284 	struct NSSize {
15285 		double width, height;
15286 	}
15287 	struct NSRect {
15288 		NSPoint origin;
15289 		NSSize size;
15290 	}
15291 	alias NSPoint CGPoint;
15292 	alias NSSize CGSize;
15293 	alias NSRect CGRect;
15294 
15295 	struct CGAffineTransform {
15296 		double a, b, c, d, tx, ty;
15297 	}
15298 
15299 	enum NSApplicationActivationPolicyRegular = 0;
15300 	enum NSBackingStoreBuffered = 2;
15301 	enum kCFStringEncodingUTF8 = 0x08000100;
15302 
15303 	enum : size_t {
15304 		NSBorderlessWindowMask = 0,
15305 		NSTitledWindowMask = 1 << 0,
15306 		NSClosableWindowMask = 1 << 1,
15307 		NSMiniaturizableWindowMask = 1 << 2,
15308 		NSResizableWindowMask = 1 << 3,
15309 		NSTexturedBackgroundWindowMask = 1 << 8
15310 	}
15311 
15312 	enum : ulong {
15313 		kCGImageAlphaNone,
15314 		kCGImageAlphaPremultipliedLast,
15315 		kCGImageAlphaPremultipliedFirst,
15316 		kCGImageAlphaLast,
15317 		kCGImageAlphaFirst,
15318 		kCGImageAlphaNoneSkipLast,
15319 		kCGImageAlphaNoneSkipFirst
15320 	}
15321 	enum : ulong {
15322 		kCGBitmapAlphaInfoMask = 0x1F,
15323 		kCGBitmapFloatComponents = (1 << 8),
15324 		kCGBitmapByteOrderMask = 0x7000,
15325 		kCGBitmapByteOrderDefault = (0 << 12),
15326 		kCGBitmapByteOrder16Little = (1 << 12),
15327 		kCGBitmapByteOrder32Little = (2 << 12),
15328 		kCGBitmapByteOrder16Big = (3 << 12),
15329 		kCGBitmapByteOrder32Big = (4 << 12)
15330 	}
15331 	enum CGPathDrawingMode {
15332 		kCGPathFill,
15333 		kCGPathEOFill,
15334 		kCGPathStroke,
15335 		kCGPathFillStroke,
15336 		kCGPathEOFillStroke
15337 	}
15338 	enum objc_AssociationPolicy : size_t {
15339 		OBJC_ASSOCIATION_ASSIGN = 0,
15340 		OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
15341 		OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
15342 		OBJC_ASSOCIATION_RETAIN = 0x301, //01401,
15343 		OBJC_ASSOCIATION_COPY = 0x303 //01403
15344 	}
15345 
15346 	extern(C) {
15347 		id objc_msgSend(id receiver, SEL selector, ...);
15348 		id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...);
15349 		id objc_getClass(const(char)* name);
15350 		SEL sel_registerName(const(char)* str);
15351 		Class objc_allocateClassPair(Class superclass, const(char)* name,
15352 									 size_t extra_bytes);
15353 		void objc_registerClassPair(Class cls);
15354 		BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types);
15355 		id objc_getAssociatedObject(id object, void* key);
15356 		void objc_setAssociatedObject(id object, void* key, id value,
15357 									  objc_AssociationPolicy policy);
15358 		Ivar class_getInstanceVariable(Class cls, const(char)* name);
15359 		id object_getIvar(id object, Ivar ivar);
15360 		void object_setIvar(id object, Ivar ivar, id value);
15361 		BOOL class_addIvar(Class cls, const(char)* name,
15362 						   size_t size, ubyte alignment, const(char)* types);
15363 
15364 		extern __gshared id NSApp;
15365 
15366 		void CFRelease(CFTypeRef obj);
15367 
15368 		CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator,
15369 											const(char)* bytes, long numBytes,
15370 											long encoding,
15371 											BOOL isExternalRepresentation);
15372 		long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding,
15373 							 char lossByte, bool isExternalRepresentation,
15374 							 char* buffer, long maxBufLen, long* usedBufLen);
15375 		long CFStringGetLength(CFStringRef theString);
15376 
15377 		CGContextRef CGBitmapContextCreate(void* data,
15378 										   size_t width, size_t height,
15379 										   size_t bitsPerComponent,
15380 										   size_t bytesPerRow,
15381 										   CGColorSpaceRef colorspace,
15382 										   CGBitmapInfo bitmapInfo);
15383 		void CGContextRelease(CGContextRef c);
15384 		ubyte* CGBitmapContextGetData(CGContextRef c);
15385 		CGImageRef CGBitmapContextCreateImage(CGContextRef c);
15386 		size_t CGBitmapContextGetWidth(CGContextRef c);
15387 		size_t CGBitmapContextGetHeight(CGContextRef c);
15388 
15389 		CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
15390 		void CGColorSpaceRelease(CGColorSpaceRef cs);
15391 
15392 		void CGContextSetRGBStrokeColor(CGContextRef c,
15393 										double red, double green, double blue,
15394 										double alpha);
15395 		void CGContextSetRGBFillColor(CGContextRef c,
15396 									  double red, double green, double blue,
15397 									  double alpha);
15398 		void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);
15399 		void CGContextShowTextAtPoint(CGContextRef c, double x, double y,
15400 									  const(char)* str, size_t length);
15401 		void CGContextStrokeLineSegments(CGContextRef c,
15402 										 const(CGPoint)* points, size_t count);
15403 
15404 		void CGContextBeginPath(CGContextRef c);
15405 		void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
15406 		void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
15407 		void CGContextAddArc(CGContextRef c, double x, double y, double radius,
15408 							 double startAngle, double endAngle, long clockwise);
15409 		void CGContextAddRect(CGContextRef c, CGRect rect);
15410 		void CGContextAddLines(CGContextRef c,
15411 							   const(CGPoint)* points, size_t count);
15412 		void CGContextSaveGState(CGContextRef c);
15413 		void CGContextRestoreGState(CGContextRef c);
15414 		void CGContextSelectFont(CGContextRef c, const(char)* name, double size,
15415 								 ulong textEncoding);
15416 		CGAffineTransform CGContextGetTextMatrix(CGContextRef c);
15417 		void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);
15418 
15419 		void CGImageRelease(CGImageRef image);
15420 	}
15421 
15422 private:
15423     // A convenient method to create a CFString (=NSString) from a D string.
15424     CFStringRef createCFString(string str) {
15425         return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length,
15426                                              kCFStringEncodingUTF8, false);
15427     }
15428 
15429     // Objective-C calls.
15430     RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) {
15431         auto _cmd = sel_registerName(selector.ptr);
15432         alias extern(C) RetType function(id, SEL, T) ExpectedType;
15433         return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args);
15434     }
15435     RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) {
15436         auto _cmd = sel_registerName(selector.ptr);
15437         auto cls = objc_getClass(className);
15438         alias extern(C) RetType function(id, SEL, T) ExpectedType;
15439         return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args);
15440     }
15441     RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) {
15442         return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args);
15443     }
15444 
15445     alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay;
15446     alias objc_msgSend_classMethod!("alloc", id) alloc;
15447     alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:",
15448                                     id, NSRect, size_t, size_t, BOOL) initWithContentRect;
15449     alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle;
15450     alias objc_msgSend_specialized!("center", void) center;
15451     alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame;
15452     alias objc_msgSend_specialized!("setContentView:", void, id) setContentView;
15453     alias objc_msgSend_specialized!("release", void) release;
15454     alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor;
15455     alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor;
15456     alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront;
15457     alias objc_msgSend_specialized!("invalidate", void) invalidate;
15458     alias objc_msgSend_specialized!("close", void) close;
15459     alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:",
15460                                     id, double, id, SEL, id, BOOL) scheduledTimer;
15461     alias objc_msgSend_specialized!("run", void) run;
15462     alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext",
15463                                     id) currentNSGraphicsContext;
15464     alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort;
15465     alias objc_msgSend_specialized!("characters", CFStringRef) characters;
15466     alias objc_msgSend_specialized!("superclass", Class) superclass;
15467     alias objc_msgSend_specialized!("init", id) init;
15468     alias objc_msgSend_specialized!("addItem:", void, id) addItem;
15469     alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu;
15470     alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:",
15471                                     id, CFStringRef, SEL, CFStringRef) initWithTitle;
15472     alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu;
15473     alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate;
15474     alias objc_msgSend_specialized!("activateIgnoringOtherApps:",
15475                                     void, BOOL) activateIgnoringOtherApps;
15476     alias objc_msgSend_classMethod!("NSApplication", "sharedApplication",
15477                                     id) sharedNSApplication;
15478     alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy;
15479 } else static assert(0, "Unsupported operating system");
15480 
15481 
15482 version(OSXCocoa) {
15483 	// I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me
15484 	//
15485 	// http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com
15486 	// https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d
15487 	//
15488 	// and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me!
15489 	// Probably won't even fully compile right now
15490 
15491     import std.math : PI;
15492     import std.algorithm : map;
15493     import std.array : array;
15494 
15495     alias SimpleWindow NativeWindowHandle;
15496     alias void delegate(id) NativeEventHandler;
15497 
15498     __gshared Ivar simpleWindowIvar;
15499 
15500     enum KEY_ESCAPE = 27;
15501 
15502     mixin template NativeImageImplementation() {
15503         CGContextRef context;
15504         ubyte* rawData;
15505     final:
15506 
15507 	void convertToRgbaBytes(ubyte[] where) {
15508 		assert(where.length == this.width * this.height * 4);
15509 
15510 		// if rawData had a length....
15511 		//assert(rawData.length == where.length);
15512 		for(long idx = 0; idx < where.length; idx += 4) {
15513 			auto alpha = rawData[idx + 3];
15514 			if(alpha == 255) {
15515 				where[idx + 0] = rawData[idx + 0]; // r
15516 				where[idx + 1] = rawData[idx + 1]; // g
15517 				where[idx + 2] = rawData[idx + 2]; // b
15518 				where[idx + 3] = rawData[idx + 3]; // a
15519 			} else {
15520 				where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r
15521 				where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g
15522 				where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b
15523 				where[idx + 3] = rawData[idx + 3]; // a
15524 
15525 			}
15526 		}
15527 	}
15528 
15529 	void setFromRgbaBytes(in ubyte[] where) {
15530 		// FIXME: this is probably wrong
15531 		assert(where.length == this.width * this.height * 4);
15532 
15533 		// if rawData had a length....
15534 		//assert(rawData.length == where.length);
15535 		for(long idx = 0; idx < where.length; idx += 4) {
15536 			auto alpha = rawData[idx + 3];
15537 			if(alpha == 255) {
15538 				rawData[idx + 0] = where[idx + 0]; // r
15539 				rawData[idx + 1] = where[idx + 1]; // g
15540 				rawData[idx + 2] = where[idx + 2]; // b
15541 				rawData[idx + 3] = where[idx + 3]; // a
15542 			} else {
15543 				rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r
15544 				rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g
15545 				rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b
15546 				rawData[idx + 3] = where[idx + 3]; // a
15547 
15548 			}
15549 		}
15550 	}
15551 
15552 
15553         void createImage(int width, int height, bool forcexshm=false) {
15554             auto colorSpace = CGColorSpaceCreateDeviceRGB();
15555             context = CGBitmapContextCreate(null, width, height, 8, 4*width,
15556                                             colorSpace,
15557                                             kCGImageAlphaPremultipliedLast
15558                                                    |kCGBitmapByteOrder32Big);
15559             CGColorSpaceRelease(colorSpace);
15560             rawData = CGBitmapContextGetData(context);
15561         }
15562         void dispose() {
15563             CGContextRelease(context);
15564         }
15565 
15566         void setPixel(int x, int y, Color c) {
15567             auto offset = (y * width + x) * 4;
15568             if (c.a == 255) {
15569                 rawData[offset + 0] = c.r;
15570                 rawData[offset + 1] = c.g;
15571                 rawData[offset + 2] = c.b;
15572                 rawData[offset + 3] = c.a;
15573             } else {
15574                 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255);
15575                 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255);
15576                 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255);
15577                 rawData[offset + 3] = c.a;
15578             }
15579         }
15580     }
15581 
15582     mixin template NativeScreenPainterImplementation() {
15583         CGContextRef context;
15584         ubyte[4] _outlineComponents;
15585 	id view;
15586 
15587         void create(NativeWindowHandle window) {
15588             context = window.drawingContext;
15589 	    view = window.view;
15590         }
15591 
15592         void dispose() {
15593             	setNeedsDisplay(view, true);
15594         }
15595 
15596 	// NotYetImplementedException
15597 	Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); }
15598 	void rasterOp(RasterOp op) {}
15599 	Pen _activePen;
15600 	Color _fillColor;
15601 	Rectangle _clipRectangle;
15602 	void setClipRectangle(int, int, int, int) {}
15603 	void setFont(OperatingSystemFont) {}
15604 	int fontHeight() { return 14; }
15605 
15606 	// end
15607 
15608         void pen(Pen pen) {
15609 	    _activePen = pen;
15610 	    auto color = pen.color; // FIXME
15611             double alphaComponent = color.a/255.0f;
15612             CGContextSetRGBStrokeColor(context,
15613                                        color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent);
15614 
15615             if (color.a != 255) {
15616                 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255);
15617                 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255);
15618                 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255);
15619                 _outlineComponents[3] = color.a;
15620             } else {
15621                 _outlineComponents[0] = color.r;
15622                 _outlineComponents[1] = color.g;
15623                 _outlineComponents[2] = color.b;
15624                 _outlineComponents[3] = color.a;
15625             }
15626         }
15627 
15628         @property void fillColor(Color color) {
15629             CGContextSetRGBFillColor(context,
15630                                      color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
15631         }
15632 
15633         void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) {
15634 		// NotYetImplementedException for upper left/width/height
15635             auto cgImage = CGBitmapContextCreateImage(image.context);
15636             auto size = CGSize(CGBitmapContextGetWidth(image.context),
15637                                CGBitmapContextGetHeight(image.context));
15638             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
15639             CGImageRelease(cgImage);
15640         }
15641 
15642 	version(OSXCocoa) {} else // NotYetImplementedException
15643         void drawPixmap(Sprite image, int x, int y) {
15644 		// FIXME: is this efficient?
15645             auto cgImage = CGBitmapContextCreateImage(image.context);
15646             auto size = CGSize(CGBitmapContextGetWidth(image.context),
15647                                CGBitmapContextGetHeight(image.context));
15648             CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage);
15649             CGImageRelease(cgImage);
15650         }
15651 
15652 
15653         void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) {
15654 		// FIXME: alignment
15655             if (_outlineComponents[3] != 0) {
15656                 CGContextSaveGState(context);
15657                 auto invAlpha = 1.0f/_outlineComponents[3];
15658                 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha,
15659                                                   _outlineComponents[1]*invAlpha,
15660                                                   _outlineComponents[2]*invAlpha,
15661                                                   _outlineComponents[3]/255.0f);
15662                 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length);
15663 // auto cfstr = cast(id)createCFString(text);
15664 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"),
15665 // NSPoint(x, y), null);
15666 // CFRelease(cfstr);
15667                 CGContextRestoreGState(context);
15668             }
15669         }
15670 
15671         void drawPixel(int x, int y) {
15672             auto rawData = CGBitmapContextGetData(context);
15673             auto width = CGBitmapContextGetWidth(context);
15674             auto height = CGBitmapContextGetHeight(context);
15675             auto offset = ((height - y - 1) * width + x) * 4;
15676             rawData[offset .. offset+4] = _outlineComponents;
15677         }
15678 
15679         void drawLine(int x1, int y1, int x2, int y2) {
15680             CGPoint[2] linePoints;
15681             linePoints[0] = CGPoint(x1, y1);
15682             linePoints[1] = CGPoint(x2, y2);
15683             CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length);
15684         }
15685 
15686         void drawRectangle(int x, int y, int width, int height) {
15687             CGContextBeginPath(context);
15688             auto rect = CGRect(CGPoint(x, y), CGSize(width, height));
15689             CGContextAddRect(context, rect);
15690             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
15691         }
15692 
15693         void drawEllipse(int x1, int y1, int x2, int y2) {
15694             CGContextBeginPath(context);
15695             auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1));
15696             CGContextAddEllipseInRect(context, rect);
15697             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
15698         }
15699 
15700         void drawArc(int x1, int y1, int width, int height, int start, int finish) {
15701             // @@@BUG@@@ Does not support elliptic arc (width != height).
15702             CGContextBeginPath(context);
15703             CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width,
15704                             start*PI/(180*64), finish*PI/(180*64), 0);
15705             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
15706         }
15707 
15708         void drawPolygon(Point[] intPoints) {
15709             CGContextBeginPath(context);
15710             auto points = array(map!(CGPoint.fromTuple)(intPoints));
15711             CGContextAddLines(context, points.ptr, points.length);
15712             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
15713         }
15714     }
15715 
15716     mixin template NativeSimpleWindowImplementation() {
15717         void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) {
15718             synchronized {
15719                 if (NSApp == null) initializeApp();
15720             }
15721 
15722             auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height));
15723 
15724             // create the window.
15725             window = initWithContentRect(alloc("NSWindow"),
15726                                          contentRect,
15727                                          NSTitledWindowMask
15728                                             |NSClosableWindowMask
15729                                             |NSMiniaturizableWindowMask
15730                                             |NSResizableWindowMask,
15731                                          NSBackingStoreBuffered,
15732                                          true);
15733 
15734             // set the title & move the window to center.
15735             auto windowTitle = createCFString(title);
15736             setTitle(window, windowTitle);
15737             CFRelease(windowTitle);
15738             center(window);
15739 
15740             // create area to draw on.
15741             auto colorSpace = CGColorSpaceCreateDeviceRGB();
15742             drawingContext = CGBitmapContextCreate(null, width, height,
15743                                                    8, 4*width, colorSpace,
15744                                                    kCGImageAlphaPremultipliedLast
15745                                                       |kCGBitmapByteOrder32Big);
15746             CGColorSpaceRelease(colorSpace);
15747             CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1);
15748             auto matrix = CGContextGetTextMatrix(drawingContext);
15749             matrix.c = -matrix.c;
15750             matrix.d = -matrix.d;
15751             CGContextSetTextMatrix(drawingContext, matrix);
15752 
15753             // create the subview that things will be drawn on.
15754             view = initWithFrame(alloc("SDGraphicsView"), contentRect);
15755             setContentView(window, view);
15756             object_setIvar(view, simpleWindowIvar, cast(id)this);
15757             release(view);
15758 
15759             setBackgroundColor(window, whiteNSColor);
15760             makeKeyAndOrderFront(window, null);
15761         }
15762         void dispose() {
15763             closeWindow();
15764             release(window);
15765         }
15766         void closeWindow() {
15767             invalidate(timer);
15768             .close(window);
15769         }
15770 
15771         ScreenPainter getPainter() {
15772 		return ScreenPainter(this, this);
15773 	}
15774 
15775         id window;
15776         id timer;
15777         id view;
15778         CGContextRef drawingContext;
15779     }
15780 
15781     extern(C) {
15782     private:
15783         BOOL returnTrue3(id self, SEL _cmd, id app) {
15784             return true;
15785         }
15786         BOOL returnTrue2(id self, SEL _cmd) {
15787             return true;
15788         }
15789 
15790         void pulse(id self, SEL _cmd) {
15791             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
15792             simpleWindow.handlePulse();
15793             setNeedsDisplay(self, true);
15794         }
15795         void drawRect(id self, SEL _cmd, NSRect rect) {
15796             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
15797             auto curCtx = graphicsPort(currentNSGraphicsContext);
15798             auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext);
15799             auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext),
15800                                CGBitmapContextGetHeight(simpleWindow.drawingContext));
15801             CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage);
15802             CGImageRelease(cgImage);
15803         }
15804         void keyDown(id self, SEL _cmd, id event) {
15805             auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar);
15806 
15807             // the event may have multiple characters, and we send them all at
15808             // once.
15809             if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) {
15810                 auto chars = characters(event);
15811                 auto range = CFRange(0, CFStringGetLength(chars));
15812                 auto buffer = new char[range.length*3];
15813                 long actualLength;
15814                 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false,
15815                                  buffer.ptr, cast(int) buffer.length, &actualLength);
15816                 foreach (dchar dc; buffer[0..actualLength]) {
15817                     if (simpleWindow.handleCharEvent)
15818                         simpleWindow.handleCharEvent(dc);
15819 		    // NotYetImplementedException
15820                     //if (simpleWindow.handleKeyEvent)
15821                         //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp?
15822                 }
15823             }
15824 
15825             // the event's 'keyCode' is hardware-dependent. I don't think people
15826             // will like it. Let's leave it to the native handler.
15827 
15828             // perform the default action.
15829 
15830 	    // so the default action is to make a bomp sound and i dont want that
15831 	    // sooooooooo yeah not gonna do that.
15832 
15833             //auto superData = objc_super(self, superclass(self));
15834             //alias extern(C) void function(objc_super*, SEL, id) T;
15835             //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event);
15836         }
15837     }
15838 
15839     // initialize the app so that it can be interacted with the user.
15840     // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html
15841     private void initializeApp() {
15842         // push an autorelease pool to avoid leaking.
15843         init(alloc("NSAutoreleasePool"));
15844 
15845         // create a new NSApp instance
15846         sharedNSApplication;
15847         setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular);
15848 
15849         // create the "Quit" menu.
15850         auto menuBar = init(alloc("NSMenu"));
15851         auto appMenuItem = init(alloc("NSMenuItem"));
15852         addItem(menuBar, appMenuItem);
15853         setMainMenu(NSApp, menuBar);
15854         release(appMenuItem);
15855         release(menuBar);
15856 
15857         auto appMenu = init(alloc("NSMenu"));
15858         auto quitTitle = createCFString("Quit");
15859         auto q = createCFString("q");
15860         auto quitItem = initWithTitle(alloc("NSMenuItem"),
15861                                       quitTitle, sel_registerName("terminate:"), q);
15862         addItem(appMenu, quitItem);
15863         setSubmenu(appMenuItem, appMenu);
15864         release(quitItem);
15865         release(appMenu);
15866         CFRelease(q);
15867         CFRelease(quitTitle);
15868 
15869         // assign a delegate for the application, allow it to quit when the last
15870         // window is closed.
15871         auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"),
15872                                                     "SDWindowCloseDelegate", 0);
15873         class_addMethod(delegateClass,
15874                         sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"),
15875                         &returnTrue3, "c@:@");
15876         objc_registerClassPair(delegateClass);
15877 
15878         auto appDelegate = init(alloc("SDWindowCloseDelegate"));
15879         setDelegate(NSApp, appDelegate);
15880         activateIgnoringOtherApps(NSApp, true);
15881 
15882         // create a new view that draws the graphics and respond to keyDown
15883         // events.
15884         auto viewClass = objc_allocateClassPair(objc_getClass("NSView"),
15885                                                 "SDGraphicsView", (void*).sizeof);
15886         class_addIvar(viewClass, "simpledisplay_simpleWindow",
15887                       (void*).sizeof, (void*).alignof, "^v");
15888         class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"),
15889                         &pulse, "v@:");
15890         class_addMethod(viewClass, sel_registerName("drawRect:"),
15891                         &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}");
15892         class_addMethod(viewClass, sel_registerName("isFlipped"),
15893                         &returnTrue2, "c@:");
15894         class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"),
15895                         &returnTrue2, "c@:");
15896         class_addMethod(viewClass, sel_registerName("keyDown:"),
15897                         &keyDown, "v@:@");
15898         objc_registerClassPair(viewClass);
15899         simpleWindowIvar = class_getInstanceVariable(viewClass,
15900                                                      "simpledisplay_simpleWindow");
15901     }
15902 }
15903 
15904 version(without_opengl) {} else
15905 extern(System) nothrow @nogc {
15906 	//enum uint GL_VERSION = 0x1F02;
15907 	//const(char)* glGetString (/*GLenum*/uint);
15908 	version(X11) {
15909 	static if (!SdpyIsUsingIVGLBinds) {
15910 
15911 		enum GLX_X_RENDERABLE = 0x8012;
15912 		enum GLX_DRAWABLE_TYPE = 0x8010;
15913 		enum GLX_RENDER_TYPE = 0x8011;
15914 		enum GLX_X_VISUAL_TYPE = 0x22;
15915 		enum GLX_TRUE_COLOR = 0x8002;
15916 		enum GLX_WINDOW_BIT = 0x00000001;
15917 		enum GLX_RGBA_BIT = 0x00000001;
15918 		enum GLX_COLOR_INDEX_BIT = 0x00000002;
15919 		enum GLX_SAMPLE_BUFFERS = 0x186a0;
15920 		enum GLX_SAMPLES = 0x186a1;
15921 		enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
15922 		enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
15923 	}
15924 
15925 		// GLX_EXT_swap_control
15926 		alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval);
15927 		private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null;
15928 
15929 		//k8: ugly code to prevent warnings when sdpy is compiled into .a
15930 		extern(System) {
15931 			alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list);
15932 		}
15933 		private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK!
15934 
15935 		// this made public so we don't have to get it again and again
15936 		public bool glXCreateContextAttribsARB_present () {
15937 			if (glXCreateContextAttribsARBFn is cast(void*)1) {
15938 				// get it
15939 				glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB");
15940 				//{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); }
15941 			}
15942 			return (glXCreateContextAttribsARBFn !is null);
15943 		}
15944 
15945 		// this made public so we don't have to get it again and again
15946 		public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) {
15947 			if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present");
15948 			return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list);
15949 		}
15950 
15951 		// extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers
15952 		extern(C) private __gshared int function(int) glXSwapIntervalMESA;
15953 
15954 		void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) {
15955 			if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return;
15956 			if (_glx_swapInterval_fn is null) {
15957 				_glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT");
15958 				if (_glx_swapInterval_fn is null) {
15959 					_glx_swapInterval_fn = cast(glXSwapIntervalEXT)1;
15960 					return;
15961 				}
15962 				version(sdddd) { import std.stdio; debug writeln("glXSwapIntervalEXT found!"); }
15963 			}
15964 
15965 			if(glXSwapIntervalMESA is null) {
15966 				// it seems to require both to actually take effect on many computers
15967 				// idk why
15968 				glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA");
15969 				if(glXSwapIntervalMESA is null)
15970 					glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1;
15971 			}
15972 
15973 			if(cast(void*) glXSwapIntervalMESA > cast(void*) 1)
15974 				glXSwapIntervalMESA(wait ? 1 : 0);
15975 
15976 			_glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0));
15977 		}
15978 	} else version(Windows) {
15979 	static if (!SdpyIsUsingIVGLBinds) {
15980 	enum GL_TRUE = 1;
15981 	enum GL_FALSE = 0;
15982 	alias int GLint;
15983 
15984 	public void* glbindGetProcAddress (const(char)* name) {
15985 		void* res = wglGetProcAddress(name);
15986 		if (res is null) {
15987 			/+
15988 			//{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); }
15989 			import core.sys.windows.windef, core.sys.windows.winbase;
15990 			__gshared HINSTANCE dll = null;
15991 			if (dll is null) {
15992 				dll = LoadLibraryA("opengl32.dll");
15993 				if (dll is null) return null; // <32, but idc
15994 			}
15995 			res = GetProcAddress(dll, name);
15996 			+/
15997 			res = GetProcAddress(gl.libHandle, name);
15998 		}
15999 		//{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); }
16000 		return res;
16001 	}
16002 	}
16003 
16004  
16005  	private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT;
16006         void wglSetVSync(bool wait) {
16007 		if(wglSwapIntervalEXT is null) {
16008 			wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT");
16009 			if(wglSwapIntervalEXT is null)
16010 				wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1;
16011 		}
16012 		if(cast(void*) wglSwapIntervalEXT is cast(void*) 1)
16013 			return;
16014 
16015 		wglSwapIntervalEXT(wait ? 1 : 0);
16016 	}
16017 
16018 		enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
16019 		enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
16020 		enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
16021 		enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
16022 		enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
16023 
16024 		enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
16025 		enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
16026 
16027 		enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
16028 		enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
16029 
16030 		alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList);
16031 		__gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null;
16032 
16033 		void wglInitOtherFunctions () {
16034 			if (wglCreateContextAttribsARB is null) {
16035 				wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB");
16036 			}
16037 		}
16038 	}
16039 
16040 	static if (!SdpyIsUsingIVGLBinds) {
16041 
16042 	interface GL {
16043 		extern(System) @nogc nothrow:
16044 
16045 		void glGetIntegerv(int, void*);
16046 		void glMatrixMode(int);
16047 		void glPushMatrix();
16048 		void glLoadIdentity();
16049 		void glOrtho(double, double, double, double, double, double);
16050 		void glFrustum(double, double, double, double, double, double);
16051 
16052 		void glPopMatrix();
16053 		void glEnable(int);
16054 		void glDisable(int);
16055 		void glClear(int);
16056 		void glBegin(int);
16057 		void glVertex2f(float, float);
16058 		void glVertex3f(float, float, float);
16059 		void glEnd();
16060 		void glColor3b(byte, byte, byte);
16061 		void glColor3ub(ubyte, ubyte, ubyte);
16062 		void glColor4b(byte, byte, byte, byte);
16063 		void glColor4ub(ubyte, ubyte, ubyte, ubyte);
16064 		void glColor3i(int, int, int);
16065 		void glColor3ui(uint, uint, uint);
16066 		void glColor4i(int, int, int, int);
16067 		void glColor4ui(uint, uint, uint, uint);
16068 		void glColor3f(float, float, float);
16069 		void glColor4f(float, float, float, float);
16070 		void glTranslatef(float, float, float);
16071 		void glScalef(float, float, float);
16072 		version(X11) {
16073 			void glSecondaryColor3b(byte, byte, byte);
16074 			void glSecondaryColor3ub(ubyte, ubyte, ubyte);
16075 			void glSecondaryColor3i(int, int, int);
16076 			void glSecondaryColor3ui(uint, uint, uint);
16077 			void glSecondaryColor3f(float, float, float);
16078 		}
16079 
16080 		void glDrawElements(int, int, int, void*);
16081 
16082 		void glRotatef(float, float, float, float);
16083 
16084 		uint glGetError();
16085 
16086 		void glDeleteTextures(int, uint*);
16087 
16088 
16089 		void glRasterPos2i(int, int);
16090 		void glDrawPixels(int, int, uint, uint, void*);
16091 		void glClearColor(float, float, float, float);
16092 
16093 
16094 		void glPixelStorei(uint, int);
16095 
16096 		void glGenTextures(uint, uint*);
16097 		void glBindTexture(int, int);
16098 		void glTexParameteri(uint, uint, int);
16099 		void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
16100 		void glTexImage2D(int, int, int, int, int, int, int, int, in void*);
16101 		void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset,
16102 			/*GLsizei*/int width, /*GLsizei*/int height,
16103 			uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
16104 		void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param);
16105 
16106 		void glLineWidth(int);
16107 
16108 
16109 		void glTexCoord2f(float, float);
16110 		void glVertex2i(int, int);
16111 		void glBlendFunc (int, int);
16112 		void glDepthFunc (int);
16113 		void glViewport(int, int, int, int);
16114 
16115 		void glClearDepth(double);
16116 
16117 		void glReadBuffer(uint);
16118 		void glReadPixels(int, int, int, int, int, int, void*);
16119 
16120 		void glFlush();
16121 		void glFinish();
16122 
16123 		version(Windows) {
16124 			BOOL wglCopyContext(HGLRC, HGLRC, UINT);
16125 			HGLRC wglCreateContext(HDC);
16126 			HGLRC wglCreateLayerContext(HDC, int);
16127 			BOOL wglDeleteContext(HGLRC);
16128 			BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR);
16129 			HGLRC wglGetCurrentContext();
16130 			HDC wglGetCurrentDC();
16131 			int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*);
16132 			PROC wglGetProcAddress(LPCSTR);
16133 			BOOL wglMakeCurrent(HDC, HGLRC);
16134 			BOOL wglRealizeLayerPalette(HDC, int, BOOL);
16135 			int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*);
16136 			BOOL wglShareLists(HGLRC, HGLRC);
16137 			BOOL wglSwapLayerBuffers(HDC, UINT);
16138 			BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
16139 			BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
16140 			BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
16141 			BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT);
16142 		}
16143 
16144 	}
16145 
16146 	interface GL3 {
16147 		extern(System) @nogc nothrow:
16148 
16149 		void glGenVertexArrays(GLsizei, GLuint*);
16150 		void glBindVertexArray(GLuint);
16151 		void glDeleteVertexArrays(GLsizei, const(GLuint)*);
16152 		void glGenerateMipmap(GLenum);
16153 		void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*);
16154 		void glStencilMask(GLuint);
16155 		void glStencilFunc(GLenum, GLint, GLuint);
16156 		void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
16157 		void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*);
16158 		GLuint glCreateProgram();
16159 		GLuint glCreateShader(GLenum);
16160 		void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*);
16161 		void glCompileShader(GLuint);
16162 		void glGetShaderiv(GLuint, GLenum, GLint*);
16163 		void glAttachShader(GLuint, GLuint);
16164 		void glBindAttribLocation(GLuint, GLuint, const(GLchar)*);
16165 		void glLinkProgram(GLuint);
16166 		void glGetProgramiv(GLuint, GLenum, GLint*);
16167 		void glDeleteProgram(GLuint);
16168 		void glDeleteShader(GLuint);
16169 		GLint glGetUniformLocation(GLuint, const(GLchar)*);
16170 		void glGenBuffers(GLsizei, GLuint*);
16171 		void glUniform4fv(GLint, GLsizei, const(GLfloat)*);
16172 		void glUniform4f(GLint, float, float, float, float);
16173 		void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean);
16174 		void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum);
16175 		void glDrawArrays(GLenum, GLint, GLsizei);
16176 		void glStencilOp(GLenum, GLenum, GLenum);
16177 		void glUseProgram(GLuint);
16178 		void glCullFace(GLenum);
16179 		void glFrontFace(GLenum);
16180 		void glActiveTexture(GLenum);
16181 		void glBindBuffer(GLenum, GLuint);
16182 		void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum);
16183 		void glEnableVertexAttribArray(GLuint);
16184 		void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*);
16185 		void glUniform1i(GLint, GLint);
16186 		void glUniform2fv(GLint, GLsizei, const(GLfloat)*);
16187 		void glDisableVertexAttribArray(GLuint);
16188 		void glDeleteBuffers(GLsizei, const(GLuint)*);
16189 		void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum);
16190 		void glLogicOp (GLenum opcode);
16191 		void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
16192 		void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers);
16193 		void glGenFramebuffers (GLsizei n, GLuint* framebuffers);
16194 		GLenum glCheckFramebufferStatus (GLenum target);
16195 		void glBindFramebuffer (GLenum target, GLuint framebuffer);
16196 	}
16197 
16198 	interface GL4 {
16199 		extern(System) @nogc nothrow:
16200 
16201 		void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset,
16202 			/*GLsizei*/int width, /*GLsizei*/int height,
16203 			uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels);
16204 	}
16205 
16206 	interface GLU {
16207 		extern(System) @nogc nothrow:
16208 
16209 		void gluLookAt(double, double, double, double, double, double, double, double, double);
16210 		void gluPerspective(double, double, double, double);
16211 
16212 		char* gluErrorString(uint);
16213 	}
16214 
16215 
16216 	enum GL_RED = 0x1903;
16217 	enum GL_ALPHA = 0x1906;
16218 
16219 	enum uint GL_FRONT = 0x0404;
16220 
16221 	enum uint GL_BLEND = 0x0be2;
16222 	enum uint GL_LEQUAL = 0x0203;
16223 
16224 
16225 	enum uint GL_RGB = 0x1907;
16226 	enum uint GL_BGRA = 0x80e1;
16227 	enum uint GL_RGBA = 0x1908;
16228 	enum uint GL_TEXTURE_2D =   0x0DE1;
16229 	enum uint GL_TEXTURE_MIN_FILTER = 0x2801;
16230 	enum uint GL_NEAREST = 0x2600;
16231 	enum uint GL_LINEAR = 0x2601;
16232 	enum uint GL_TEXTURE_MAG_FILTER = 0x2800;
16233 	enum uint GL_TEXTURE_WRAP_S = 0x2802;
16234 	enum uint GL_TEXTURE_WRAP_T = 0x2803;
16235 	enum uint GL_REPEAT = 0x2901;
16236 	enum uint GL_CLAMP = 0x2900;
16237 	enum uint GL_CLAMP_TO_EDGE = 0x812F;
16238 	enum uint GL_CLAMP_TO_BORDER = 0x812D;
16239 	enum uint GL_DECAL = 0x2101;
16240 	enum uint GL_MODULATE = 0x2100;
16241 	enum uint GL_TEXTURE_ENV = 0x2300;
16242 	enum uint GL_TEXTURE_ENV_MODE = 0x2200;
16243 	enum uint GL_REPLACE = 0x1E01;
16244 	enum uint GL_LIGHTING = 0x0B50;
16245 	enum uint GL_DITHER = 0x0BD0;
16246 
16247 	enum uint GL_NO_ERROR = 0;
16248 
16249 
16250 
16251 	enum int GL_VIEWPORT = 0x0BA2;
16252 	enum int GL_MODELVIEW = 0x1700;
16253 	enum int GL_TEXTURE = 0x1702;
16254 	enum int GL_PROJECTION = 0x1701;
16255 	enum int GL_DEPTH_TEST = 0x0B71;
16256 
16257 	enum int GL_COLOR_BUFFER_BIT = 0x00004000;
16258 	enum int GL_ACCUM_BUFFER_BIT = 0x00000200;
16259 	enum int GL_DEPTH_BUFFER_BIT = 0x00000100;
16260 	enum uint GL_STENCIL_BUFFER_BIT = 0x00000400;
16261 
16262 	enum int GL_POINTS = 0x0000;
16263 	enum int GL_LINES =  0x0001;
16264 	enum int GL_LINE_LOOP = 0x0002;
16265 	enum int GL_LINE_STRIP = 0x0003;
16266 	enum int GL_TRIANGLES = 0x0004;
16267 	enum int GL_TRIANGLE_STRIP = 5;
16268 	enum int GL_TRIANGLE_FAN = 6;
16269 	enum int GL_QUADS = 7;
16270 	enum int GL_QUAD_STRIP = 8;
16271 	enum int GL_POLYGON = 9;
16272 
16273 	alias GLvoid = void;
16274 	alias GLboolean = ubyte;
16275 	alias GLuint = uint;
16276 	alias GLenum = uint;
16277 	alias GLchar = char;
16278 	alias GLsizei = int;
16279 	alias GLfloat = float;
16280 	alias GLintptr = size_t;
16281 	alias GLsizeiptr = ptrdiff_t;
16282 
16283 
16284 	enum uint GL_INVALID_ENUM = 0x0500;
16285 
16286 	enum uint GL_ZERO = 0;
16287 	enum uint GL_ONE = 1;
16288 
16289 	enum uint GL_BYTE = 0x1400;
16290 	enum uint GL_UNSIGNED_BYTE = 0x1401;
16291 	enum uint GL_SHORT = 0x1402;
16292 	enum uint GL_UNSIGNED_SHORT = 0x1403;
16293 	enum uint GL_INT = 0x1404;
16294 	enum uint GL_UNSIGNED_INT = 0x1405;
16295 	enum uint GL_FLOAT = 0x1406;
16296 	enum uint GL_2_BYTES = 0x1407;
16297 	enum uint GL_3_BYTES = 0x1408;
16298 	enum uint GL_4_BYTES = 0x1409;
16299 	enum uint GL_DOUBLE = 0x140A;
16300 
16301 	enum uint GL_STREAM_DRAW = 0x88E0;
16302 
16303 	enum uint GL_CCW = 0x0901;
16304 
16305 	enum uint GL_STENCIL_TEST = 0x0B90;
16306 	enum uint GL_SCISSOR_TEST = 0x0C11;
16307 
16308 	enum uint GL_EQUAL = 0x0202;
16309 	enum uint GL_NOTEQUAL = 0x0205;
16310 
16311 	enum uint GL_ALWAYS = 0x0207;
16312 	enum uint GL_KEEP = 0x1E00;
16313 
16314 	enum uint GL_INCR = 0x1E02;
16315 
16316 	enum uint GL_INCR_WRAP = 0x8507;
16317 	enum uint GL_DECR_WRAP = 0x8508;
16318 
16319 	enum uint GL_CULL_FACE = 0x0B44;
16320 	enum uint GL_BACK = 0x0405;
16321 
16322 	enum uint GL_FRAGMENT_SHADER = 0x8B30;
16323 	enum uint GL_VERTEX_SHADER = 0x8B31;
16324 
16325 	enum uint GL_COMPILE_STATUS = 0x8B81;
16326 	enum uint GL_LINK_STATUS = 0x8B82;
16327 
16328 	enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893;
16329 
16330 	enum uint GL_STATIC_DRAW = 0x88E4;
16331 
16332 	enum uint GL_UNPACK_ALIGNMENT = 0x0CF5;
16333 	enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2;
16334 	enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4;
16335 	enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3;
16336 
16337 	enum uint GL_GENERATE_MIPMAP = 0x8191;
16338 	enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703;
16339 
16340 	enum uint GL_TEXTURE0 = 0x84C0U;
16341 	enum uint GL_TEXTURE1 = 0x84C1U;
16342 
16343 	enum uint GL_ARRAY_BUFFER = 0x8892;
16344 
16345 	enum uint GL_SRC_COLOR = 0x0300;
16346 	enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
16347 	enum uint GL_SRC_ALPHA = 0x0302;
16348 	enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
16349 	enum uint GL_DST_ALPHA = 0x0304;
16350 	enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
16351 	enum uint GL_DST_COLOR = 0x0306;
16352 	enum uint GL_ONE_MINUS_DST_COLOR = 0x0307;
16353 	enum uint GL_SRC_ALPHA_SATURATE = 0x0308;
16354 
16355 	enum uint GL_INVERT = 0x150AU;
16356 
16357 	enum uint GL_DEPTH_STENCIL = 0x84F9U;
16358 	enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU;
16359 
16360 	enum uint GL_FRAMEBUFFER = 0x8D40U;
16361 	enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U;
16362 	enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU;
16363 
16364 	enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U;
16365 	enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U;
16366 	enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U;
16367 	enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U;
16368 	enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU;
16369 
16370 	enum uint GL_COLOR_LOGIC_OP = 0x0BF2U;
16371 	enum uint GL_CLEAR = 0x1500U;
16372 	enum uint GL_COPY = 0x1503U;
16373 	enum uint GL_XOR = 0x1506U;
16374 
16375 	enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U;
16376 
16377 	enum uint GL_TEXTURE_LOD_BIAS = 0x8501;
16378 
16379 	}
16380 }
16381 
16382 version(without_opengl) {} else {
16383 static if(!SdpyIsUsingIVGLBinds) {
16384 	version(Windows) {
16385 		mixin DynamicLoad!(GL, "opengl32", 1, true) gl;
16386 		mixin DynamicLoad!(GLU, "glu32", 1, true) glu;
16387 	} else {
16388 		mixin DynamicLoad!(GL, "GL", 1, true) gl;
16389 		mixin DynamicLoad!(GLU, "GLU", 3, true) glu;
16390 	}
16391 	mixin DynamicLoadSupplementalOpenGL!(GL3) gl3;
16392 
16393 
16394 	shared static this() {
16395 		gl.loadDynamicLibrary();
16396 		glu.loadDynamicLibrary();
16397 	}
16398 }
16399 }
16400 
16401 /++
16402 	Convenience method for converting D arrays to opengl buffer data
16403 
16404 	I would LOVE to overload it with the original glBufferData, but D won't
16405 	let me since glBufferData is a function pointer :(
16406 
16407 	Added: August 25, 2020 (version 8.5)
16408 +/
16409 version(without_opengl) {} else
16410 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) {
16411 	glBufferData(target, data.length, data.ptr, usage);
16412 }
16413 
16414 /+
16415 /++
16416 	A matrix for simple uses that easily integrates with [OpenGlShader].
16417 
16418 	Might not be useful to you since it only as some simple functions and
16419 	probably isn't that fast.
16420 
16421 	Note it uses an inline static array for its storage, so copying it
16422 	may be expensive.
16423 +/
16424 struct BasicMatrix(int columns, int rows, T = float) {
16425 	import core.stdc.math;
16426 
16427 	T[columns * rows] data = 0.0;
16428 
16429 	/++
16430 		Basic operations that operate *in place*.
16431 	+/
16432 	void translate() {
16433 
16434 	}
16435 
16436 	/// ditto
16437 	void scale() {
16438 
16439 	}
16440 
16441 	/// ditto
16442 	void rotate() {
16443 
16444 	}
16445 
16446 	/++
16447 
16448 	+/
16449 	static if(columns == rows)
16450 	static BasicMatrix identity() {
16451 		BasicMatrix m;
16452 		foreach(i; 0 .. columns)
16453 			data[0 + i + i * columns] = 1.0;
16454 		return m;
16455 	}
16456 
16457 	static BasicMatrix ortho() {
16458 		return BasicMatrix.init;
16459 	}
16460 }
16461 +/
16462 
16463 /++
16464 	Convenience class for using opengl shaders.
16465 
16466 	Ensure that you've loaded opengl 3+ and set your active
16467 	context before trying to use this.
16468 
16469 	Added: August 25, 2020 (version 8.5)
16470 +/
16471 version(without_opengl) {} else
16472 final class OpenGlShader {
16473 	private int shaderProgram_;
16474 	private @property void shaderProgram(int a) {
16475 		shaderProgram_ = a;
16476 	}
16477 	/// Get the program ID for use in OpenGL functions.
16478 	public @property int shaderProgram() {
16479 		return shaderProgram_;
16480 	}
16481 
16482 	/++
16483 
16484 	+/
16485 	static struct Source {
16486 		uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc.
16487 		string code; ///
16488 	}
16489 
16490 	/++
16491 		Helper method to just compile some shader code and check for errors
16492 		while you do glCreateShader, etc. on the outside yourself.
16493 
16494 		This just does `glShaderSource` and `glCompileShader` for the given code.
16495 
16496 		If you the OpenGlShader class constructor, you never need to call this yourself.
16497 	+/
16498 	static void compile(int sid, Source code) {
16499 		const(char)*[1] buffer;
16500 		int[1] lengthBuffer;
16501 
16502 		buffer[0] = code.code.ptr;
16503 		lengthBuffer[0] = cast(int) code.code.length;
16504 
16505 		glShaderSource(sid, 1, buffer.ptr, lengthBuffer.ptr);
16506 		glCompileShader(sid);
16507 
16508 		int success;
16509 		glGetShaderiv(sid, GL_COMPILE_STATUS, &success);
16510 		if(!success) {
16511 			char[512] info;
16512 			int len;
16513 			glGetShaderInfoLog(sid, info.length, &len, info.ptr);
16514 
16515 			throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]);
16516 		}
16517 	}
16518 
16519 	/++
16520 		Calls `glLinkProgram` and throws if error a occurs.
16521 
16522 		If you the OpenGlShader class constructor, you never need to call this yourself.
16523 	+/
16524 	static void link(int shaderProgram) {
16525 		glLinkProgram(shaderProgram);
16526 		int success;
16527 		glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
16528 		if(!success) {
16529 			char[512] info;
16530 			int len;
16531 			glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr);
16532 
16533 			throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]);
16534 		}
16535 	}
16536 
16537 	/++
16538 		Constructs the shader object by calling `glCreateProgram`, then
16539 		compiling each given [Source], and finally, linking them together.
16540 
16541 		Throws: on compile or link failure.
16542 	+/
16543 	this(Source[] codes...) {
16544 		shaderProgram = glCreateProgram();
16545 
16546 		int[16] shadersBufferStack;
16547 
16548 		int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 
16549 			shadersBufferStack[0 .. codes.length] :
16550 			new int[](codes.length);
16551 
16552 		foreach(idx, code; codes) {
16553 			shadersBuffer[idx] = glCreateShader(code.type);
16554 
16555 			compile(shadersBuffer[idx], code);
16556 
16557 			glAttachShader(shaderProgram, shadersBuffer[idx]);
16558 		}
16559 
16560 		link(shaderProgram);
16561 
16562 		foreach(s; shadersBuffer)
16563 			glDeleteShader(s);
16564 	}
16565 
16566 	/// Calls `glUseProgram(this.shaderProgram)`
16567 	void use() {
16568 		glUseProgram(this.shaderProgram);
16569 	}
16570 
16571 	/// Deletes the program.
16572 	void delete_() {
16573 		glDeleteProgram(shaderProgram);
16574 		shaderProgram = 0;
16575 	}
16576 
16577 	/++
16578 		[OpenGlShader.uniforms].name gives you one of these.
16579 
16580 		You can get the id out of it or just assign
16581 	+/
16582 	static struct Uniform {
16583 		/// the id passed to glUniform*
16584 		int id;
16585 
16586 		/// Assigns the 4 floats. You will probably have to call this via the .opAssign name
16587 		void opAssign(float x, float y, float z, float w) {
16588 			glUniform4f(id, x, y, z, w);
16589 		}
16590 	}
16591 
16592 	static struct UniformsHelper {
16593 		OpenGlShader _shader;
16594 
16595 		@property Uniform opDispatch(string name)() {
16596 			auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr);
16597 			if(i == -1)
16598 				throw new Exception("Could not find uniform " ~ name);
16599 			return Uniform(i);
16600 		}
16601 	}
16602 
16603 	/++
16604 		Gives access to the uniforms through dot access.
16605 		`OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo");
16606 	+/
16607 	@property UniformsHelper uniforms() { return UniformsHelper(this); }
16608 }
16609 
16610 version(linux) {
16611 	version(with_eventloop) {} else {
16612 		private int epollFd = -1;
16613 		void prepareEventLoop() {
16614 			if(epollFd != -1)
16615 				return; // already initialized, no need to do it again
16616 			import ep = core.sys.linux.epoll;
16617 
16618 			epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC);
16619 			if(epollFd == -1)
16620 				throw new Exception("epoll create failure");
16621 		}
16622 	}
16623 } else version(Posix) {
16624 	void prepareEventLoop() {}
16625 }
16626 
16627 version(X11) {
16628 	import core.stdc.locale : LC_ALL; // rdmd fix
16629 	__gshared bool sdx_isUTF8Locale;
16630 
16631 	// This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
16632 	// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
16633 	// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
16634 	// anal magic is here. I (Ketmar) hope you like it.
16635 	// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
16636 	// always return correct unicode symbols. The detection is here 'cause user can change locale
16637 	// later.
16638 
16639 	// NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded
16640 	shared static this () {
16641 		if(!librariesSuccessfullyLoaded)
16642 			return;
16643 
16644 		import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
16645 
16646 		// this doesn't hurt; it may add some locking, but the speed is still
16647 		// allows doing 60 FPS videogames; also, ignore the result, as most
16648 		// users will probably won't do mulththreaded X11 anyway (and I (ketmar)
16649 		// never seen this failing).
16650 		if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); }
16651 
16652 		setlocale(LC_ALL, "");
16653 		// check if out locale is UTF-8
16654 		auto lct = setlocale(LC_CTYPE, null);
16655 		if (lct is null) {
16656 			sdx_isUTF8Locale = false;
16657 		} else {
16658 			for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
16659 				if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
16660 						(lct[idx+1] == 't' || lct[idx+1] == 'T') &&
16661 						(lct[idx+2] == 'f' || lct[idx+2] == 'F'))
16662 				{
16663 					sdx_isUTF8Locale = true;
16664 					break;
16665 				}
16666 			}
16667 		}
16668 		//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
16669 	}
16670 }
16671 
16672 class ExperimentalTextComponent2 {
16673 	/+
16674 		Stage 1: get it working monospace
16675 		Stage 2: use proportional font
16676 		Stage 3: allow changes in inline style
16677 		Stage 4: allow new fonts and sizes in the middle
16678 		Stage 5: optimize gap buffer
16679 		Stage 6: optimize layout
16680 		Stage 7: word wrap
16681 		Stage 8: justification
16682 		Stage 9: editing, selection, etc.
16683 	+/
16684 
16685 	/++
16686 		It asks for a window so it can translate abstract font sizes to actual on-screen values depending on the window's current dpi, scaling settings, etc.
16687 	+/
16688 	this(SimpleWindow window) {
16689 		this.window = window;
16690 	}
16691 
16692 	private SimpleWindow window;
16693 
16694 
16695 	/++
16696 		When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces
16697 		representing the internal parts. The first pass is focused on the x parameter, then the
16698 		renderer is responsible for going back to the parts in the current line and calling
16699 		adjustDownForAscent to change the y params.
16700 	+/
16701 	static interface ComponentRenderHelper {
16702 		void adjustDownForAscent(int amount); // at the end of the line it needs to do these
16703 
16704 		int ascent() const;
16705 		int descent() const;
16706 
16707 		int advance() const;
16708 
16709 		bool endsWithExplititLineBreak() const;
16710 	}
16711 
16712 	static interface RenderResult {
16713 		/++
16714 			This is responsible for using what space is left (your object is responsible for keeping its own state after getting it updated from [repositionForNextLine]) and not going over if at all possible. If you can word wrap, you should when space is out. Otherwise, you can keep going if it means overflow hidden or scroll.
16715 		+/
16716 		void popFront();
16717 		@property bool empty() const;
16718 		@property ComponentRenderHelper front() const;
16719 
16720 		void repositionForNextLine(Point baseline, int availableWidth);
16721 	}
16722 
16723 	static interface ComponentInFlow {
16724 		void draw(ScreenPainter painter);
16725 		//RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different"
16726 
16727 		bool startsWithExplicitLineBreak() const;
16728 	}
16729 
16730 	static class TextFlowComponent : ComponentInFlow {
16731 		bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true
16732 
16733 		Color foreground;
16734 		Color background;
16735 
16736 		OperatingSystemFont font; // should NEVER be null
16737 
16738 		ubyte attributes; // underline, strike through, display on new block
16739 
16740 		version(Windows)
16741 			const(wchar)[] content;
16742 		else
16743 			const(char)[] content; // this should NEVER have a newline, except at the end
16744 
16745 		RenderedComponent[] rendered; // entirely controlled by [rerender]
16746 
16747 		// could prolly put some spacing around it too like margin / padding
16748 
16749 		this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c)
16750 			in { assert(font !is null);
16751 			     assert(!font.isNull); }
16752 			do
16753 		{
16754 			this.foreground = f;
16755 			this.background = b;
16756 			this.font = font;
16757 
16758 			this.attributes = attr;
16759 			version(Windows) {
16760 				auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines;
16761 				auto sz = sizeOfConvertedWstring(c, conversionFlags);
16762 				auto buffer = new wchar[](sz);
16763 				this.content = makeWindowsString(c, buffer, conversionFlags);
16764 			} else {
16765 				this.content = c.dup;
16766 			}
16767 		}
16768 
16769 		void draw(ScreenPainter painter) {
16770 			painter.setFont(this.font);
16771 			painter.outlineColor = this.foreground;
16772 			painter.fillColor = Color.transparent;
16773 			foreach(rendered; this.rendered) {
16774 				// the component works in term of baseline,
16775 				// but the painter works in term of upper left bounding box
16776 				// so need to translate that
16777 
16778 				if(this.background.a) {
16779 					painter.fillColor = this.background;
16780 					painter.outlineColor = this.background;
16781 
16782 					painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height));
16783 
16784 					painter.outlineColor = this.foreground;
16785 					painter.fillColor = Color.transparent;
16786 				}
16787 
16788 				painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice);
16789 
16790 				// FIXME: strike through, underline, highlight selection, etc.
16791 			}
16792 		}
16793 	}
16794 
16795 	// I could split the parts into words on render
16796 	// for easier word-wrap, each one being an unbreakable "inline-block"
16797 	private TextFlowComponent[] parts;
16798 	private int needsRerenderFrom;
16799 
16800 	void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) {
16801 		// FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop.
16802 		parts ~= new TextFlowComponent(f, b, font, attr, c);
16803 	}
16804 
16805 	static struct RenderedComponent {
16806 		int startX;
16807 		int startY;
16808 		short width;
16809 		// height is always from the containing part's font. This saves some space and means recalculations need not continue past the current line, unless a new part is added with a different font!
16810 		// for individual chars in here you've gotta process on demand
16811 		version(Windows)
16812 			const(wchar)[] slice;
16813 		else
16814 			const(char)[] slice;
16815 	}
16816 
16817 
16818 	void rerender(Rectangle boundingBox) {
16819 		Point baseline = boundingBox.upperLeft;
16820 
16821 		this.boundingBox.left = boundingBox.left;
16822 		this.boundingBox.top = boundingBox.top;
16823 
16824 		auto remainingParts = parts;
16825 
16826 		int largestX;
16827 
16828 
16829 		foreach(part; parts)
16830 			part.font.prepareContext(window);
16831 		scope(exit)
16832 		foreach(part; parts)
16833 			part.font.releaseContext();
16834 
16835 		calculateNextLine:
16836 
16837 		int nextLineHeight = 0;
16838 		int nextBiggestDescent = 0;
16839 
16840 		foreach(part; remainingParts) {
16841 			auto height = part.font.ascent;
16842 			if(height > nextLineHeight)
16843 				nextLineHeight = height;
16844 			if(part.font.descent > nextBiggestDescent)
16845 				nextBiggestDescent = part.font.descent;
16846 			if(part.content.length && part.content[$-1] == '\n')
16847 				break;
16848 		}
16849 
16850 		baseline.y += nextLineHeight;
16851 		auto lineStart = baseline;
16852 
16853 		while(remainingParts.length) {
16854 			remainingParts[0].rendered = null;
16855 
16856 			bool eol;
16857 			if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n')
16858 				eol = true;
16859 
16860 			// FIXME: word wrap
16861 			auto font = remainingParts[0].font;
16862 			auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)];
16863 			auto width = font.stringWidth(slice, window);
16864 			remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice);
16865 
16866 			remainingParts = remainingParts[1 .. $];
16867 			baseline.x += width;
16868 
16869 			if(eol) {
16870 				baseline.y += nextBiggestDescent;
16871 				if(baseline.x > largestX)
16872 					largestX = baseline.x;
16873 				baseline.x = lineStart.x;
16874 				goto calculateNextLine;
16875 			}
16876 		}
16877 
16878 		if(baseline.x > largestX)
16879 			largestX = baseline.x;
16880 
16881 		this.boundingBox.right = largestX;
16882 		this.boundingBox.bottom = baseline.y;
16883 	}
16884 
16885 	// you must call rerender first!
16886 	void draw(ScreenPainter painter) {
16887 		foreach(part; parts) {
16888 			part.draw(painter);
16889 		}
16890 	}
16891 
16892 	struct IdentifyResult {
16893 		TextFlowComponent part;
16894 		int charIndexInPart;
16895 		int totalCharIndex = -1; // if this is -1, it just means the end
16896 
16897 		Rectangle boundingBox;
16898 	}
16899 
16900 	IdentifyResult identify(Point pt, bool exact = false) {
16901 		if(parts.length == 0)
16902 			return IdentifyResult(null, 0);
16903 
16904 		if(pt.y < boundingBox.top) {
16905 			if(exact)
16906 				return IdentifyResult(null, 1);
16907 			return IdentifyResult(parts[0], 0);
16908 		}
16909 		if(pt.y > boundingBox.bottom) {
16910 			if(exact)
16911 				return IdentifyResult(null, 2);
16912 			return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length);
16913 		}
16914 
16915 		int tci = 0;
16916 
16917 		// I should probably like binary search this or something...
16918 		foreach(ref part; parts) {
16919 			foreach(rendered; part.rendered) {
16920 				auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent);
16921 				if(rect.contains(pt)) {
16922 					auto x = pt.x - rendered.startX;
16923 					auto estimatedIdx = x / part.font.averageWidth;
16924 
16925 					if(estimatedIdx < 0)
16926 						estimatedIdx = 0;
16927 
16928 					if(estimatedIdx > rendered.slice.length)
16929 						estimatedIdx = cast(int) rendered.slice.length;
16930 
16931 					int idx;
16932 					int x1, x2;
16933 					if(part.font.isMonospace) {
16934 						auto w = part.font.averageWidth;
16935 						if(!exact && x > (estimatedIdx + 1) * w)
16936 							return IdentifyResult(null, 4);
16937 						idx = estimatedIdx;
16938 						x1 = idx * w;
16939 						x2 = (idx + 1) * w;
16940 					} else {
16941 						idx = estimatedIdx;
16942 
16943 						part.font.prepareContext(window);
16944 						scope(exit) part.font.releaseContext();
16945 
16946 						// int iterations;
16947 
16948 						while(true) {
16949 							// iterations++;
16950 							x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0;
16951 							x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies.
16952 
16953 							x1 += rendered.startX;
16954 							x2 += rendered.startX;
16955 
16956 							if(pt.x < x1) {
16957 								if(idx == 0) {
16958 									if(exact)
16959 										return IdentifyResult(null, 6);
16960 									else
16961 										break;
16962 								}
16963 								idx--;
16964 							} else if(pt.x > x2) {
16965 								idx++;
16966 								if(idx > rendered.slice.length) {
16967 									if(exact)
16968 										return IdentifyResult(null, 5);
16969 									else
16970 										break;
16971 								}
16972 							} else if(pt.x >= x1 && pt.x <= x2) {
16973 								if(idx)
16974 									idx--; // point it at the original index
16975 								break; // we fit
16976 							}
16977 						}
16978 
16979 						// import std.stdio; writeln(iterations)
16980 					}
16981 
16982 
16983 					return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8?
16984 				}
16985 			}
16986 			tci += cast(int) part.content.length; // FIXME: utf-8?
16987 		}
16988 		return IdentifyResult(null, 3);
16989 	}
16990 
16991 	Rectangle boundingBox; // only set after [rerender]
16992 
16993 	// text will be positioned around the exclusion zone
16994 	static struct ExclusionZone {
16995 
16996 	}
16997 
16998 	ExclusionZone[] exclusionZones;
16999 }
17000 
17001 
17002 // Don't use this yet. When I'm happy with it, I will move it to the
17003 // regular module namespace.
17004 mixin template ExperimentalTextComponent() {
17005 
17006 	alias Rectangle = arsd.color.Rectangle;
17007 
17008 	struct ForegroundColor {
17009 		Color color;
17010 		alias color this;
17011 
17012 		this(Color c) {
17013 			color = c;
17014 		}
17015 
17016 		this(int r, int g, int b, int a = 255) {
17017 			color = Color(r, g, b, a);
17018 		}
17019 
17020 		static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) {
17021 			return ForegroundColor(mixin("Color." ~ s));
17022 		}
17023 	}
17024 
17025 	struct BackgroundColor {
17026 		Color color;
17027 		alias color this;
17028 
17029 		this(Color c) {
17030 			color = c;
17031 		}
17032 
17033 		this(int r, int g, int b, int a = 255) {
17034 			color = Color(r, g, b, a);
17035 		}
17036 
17037 		static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) {
17038 			return BackgroundColor(mixin("Color." ~ s));
17039 		}
17040 	}
17041 
17042 	static class InlineElement {
17043 		string text;
17044 
17045 		BlockElement containingBlock;
17046 
17047 		Color color = Color.black;
17048 		Color backgroundColor = Color.transparent;
17049 		ushort styles;
17050 
17051 		string font;
17052 		int fontSize;
17053 
17054 		int lineHeight;
17055 
17056 		void* identifier;
17057 
17058 		Rectangle boundingBox;
17059 		int[] letterXs; // FIXME: maybe i should do bounding boxes for every character
17060 
17061 		bool isMergeCompatible(InlineElement other) {
17062 			return
17063 				containingBlock is other.containingBlock &&
17064 				color == other.color &&
17065 				backgroundColor == other.backgroundColor &&
17066 				styles == other.styles &&
17067 				font == other.font &&
17068 				fontSize == other.fontSize &&
17069 				lineHeight == other.lineHeight &&
17070 				true;
17071 		}
17072 
17073 		int xOfIndex(size_t index) {
17074 			if(index < letterXs.length)
17075 				return letterXs[index];
17076 			else
17077 				return boundingBox.right;
17078 		}
17079 
17080 		InlineElement clone() {
17081 			auto ie = new InlineElement();
17082 			ie.tupleof = this.tupleof;
17083 			return ie;
17084 		}
17085 
17086 		InlineElement getPreviousInlineElement() {
17087 			InlineElement prev = null;
17088 			foreach(ie; this.containingBlock.parts) {
17089 				if(ie is this)
17090 					break;
17091 				prev = ie;
17092 			}
17093 			if(prev is null) {
17094 				BlockElement pb;
17095 				BlockElement cb = this.containingBlock;
17096 				moar:
17097 				foreach(ie; this.containingBlock.containingLayout.blocks) {
17098 					if(ie is cb)
17099 						break;
17100 					pb = ie;
17101 				}
17102 				if(pb is null)
17103 					return null;
17104 				if(pb.parts.length == 0) {
17105 					cb = pb;
17106 					goto moar;
17107 				}
17108 
17109 				prev = pb.parts[$-1];
17110 
17111 			}
17112 			return prev;
17113 		}
17114 
17115 		InlineElement getNextInlineElement() {
17116 			InlineElement next = null;
17117 			foreach(idx, ie; this.containingBlock.parts) {
17118 				if(ie is this) {
17119 					if(idx + 1 < this.containingBlock.parts.length)
17120 						next = this.containingBlock.parts[idx + 1];
17121 					break;
17122 				}
17123 			}
17124 			if(next is null) {
17125 				BlockElement n;
17126 				foreach(idx, ie; this.containingBlock.containingLayout.blocks) {
17127 					if(ie is this.containingBlock) {
17128 						if(idx + 1 < this.containingBlock.containingLayout.blocks.length)
17129 							n = this.containingBlock.containingLayout.blocks[idx + 1];
17130 						break;
17131 					}
17132 				}
17133 				if(n is null)
17134 					return null;
17135 
17136 				if(n.parts.length)
17137 					next = n.parts[0];
17138 				else {} // FIXME
17139 
17140 			}
17141 			return next;
17142 		}
17143 
17144 	}
17145 
17146 	// Block elements are used entirely for positioning inline elements,
17147 	// which are the things that are actually drawn.
17148 	class BlockElement {
17149 		InlineElement[] parts;
17150 		uint alignment;
17151 
17152 		int whiteSpace; // pre, pre-wrap, wrap
17153 
17154 		TextLayout containingLayout;
17155 
17156 		// inputs
17157 		Point where;
17158 		Size minimumSize;
17159 		Size maximumSize;
17160 		Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box.
17161 		void* identifier;
17162 
17163 		Rectangle margin;
17164 		Rectangle padding;
17165 
17166 		// outputs
17167 		Rectangle[] boundingBoxes;
17168 	}
17169 
17170 	struct TextIdentifyResult {
17171 		InlineElement element;
17172 		int offset;
17173 
17174 		private TextIdentifyResult fixupNewline() {
17175 			if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
17176 				offset--;
17177 			} else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') {
17178 				offset--;
17179 			}
17180 			return this;
17181 		}
17182 	}
17183 
17184 	class TextLayout {
17185 		BlockElement[] blocks;
17186 		Rectangle boundingBox_;
17187 		Rectangle boundingBox() { return boundingBox_; }
17188 		void boundingBox(Rectangle r) {
17189 			if(r != boundingBox_) {
17190 				boundingBox_ = r;
17191 				layoutInvalidated = true;
17192 			}
17193 		}
17194 
17195 		Rectangle contentBoundingBox() {
17196 			Rectangle r;
17197 			foreach(block; blocks)
17198 			foreach(ie; block.parts) {
17199 				if(ie.boundingBox.right > r.right)
17200 					r.right = ie.boundingBox.right;
17201 				if(ie.boundingBox.bottom > r.bottom)
17202 					r.bottom = ie.boundingBox.bottom;
17203 			}
17204 			return r;
17205 		}
17206 
17207 		BlockElement[] getBlocks() {
17208 			return blocks;
17209 		}
17210 
17211 		InlineElement[] getTexts() {
17212 			InlineElement[] elements;
17213 			foreach(block; blocks)
17214 				elements ~= block.parts;
17215 			return elements;
17216 		}
17217 
17218 		string getPlainText() {
17219 			string text;
17220 			foreach(block; blocks)
17221 				foreach(part; block.parts)
17222 					text ~= part.text;
17223 			return text;
17224 		}
17225 
17226 		string getHtml() {
17227 			return null; // FIXME
17228 		}
17229 
17230 		this(Rectangle boundingBox) {
17231 			this.boundingBox = boundingBox;
17232 		}
17233 
17234 		BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) {
17235 			auto be = new BlockElement();
17236 			be.containingLayout = this;
17237 			if(after is null)
17238 				blocks ~= be;
17239 			else {
17240 				foreach(idx, b; blocks) {
17241 					if(b is after.containingBlock) {
17242 						blocks = blocks[0 .. idx + 1] ~  be ~ blocks[idx + 1 .. $];
17243 						break;
17244 					}
17245 				}
17246 			}
17247 			return be;
17248 		}
17249 
17250 		void clear() {
17251 			blocks = null;
17252 			selectionStart = selectionEnd = caret = Caret.init;
17253 		}
17254 
17255 		void addText(Args...)(Args args) {
17256 			if(blocks.length == 0)
17257 				addBlock();
17258 
17259 			InlineElement ie = new InlineElement();
17260 			foreach(idx, arg; args) {
17261 				static if(is(typeof(arg) == ForegroundColor))
17262 					ie.color = arg;
17263 				else static if(is(typeof(arg) == TextFormat)) {
17264 					if(arg & 0x8000) // ~TextFormat.something turns it off
17265 						ie.styles &= arg;
17266 					else
17267 						ie.styles |= arg;
17268 				} else static if(is(typeof(arg) == string)) {
17269 					static if(idx == 0 && args.length > 1)
17270 						static assert(0, "Put styles before the string.");
17271 					size_t lastLineIndex;
17272 					foreach(cidx, char a; arg) {
17273 						if(a == '\n') {
17274 							ie.text = arg[lastLineIndex .. cidx + 1];
17275 							lastLineIndex = cidx + 1;
17276 							ie.containingBlock = blocks[$-1];
17277 							blocks[$-1].parts ~= ie.clone;
17278 							ie.text = null;
17279 						} else {
17280 
17281 						}
17282 					}
17283 
17284 					ie.text = arg[lastLineIndex .. $];
17285 					ie.containingBlock = blocks[$-1];
17286 					blocks[$-1].parts ~= ie.clone;
17287 					caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
17288 				}
17289 			}
17290 
17291 			invalidateLayout();
17292 		}
17293 
17294 		void tryMerge(InlineElement into, InlineElement what) {
17295 			if(!into.isMergeCompatible(what)) {
17296 				return; // cannot merge, different configs
17297 			}
17298 
17299 			// cool, can merge, bring text together...
17300 			into.text ~= what.text;
17301 
17302 			// and remove what
17303 			for(size_t a = 0; a < what.containingBlock.parts.length; a++) {
17304 				if(what.containingBlock.parts[a] is what) {
17305 					for(size_t i = a; i < what.containingBlock.parts.length - 1; i++)
17306 						what.containingBlock.parts[i] = what.containingBlock.parts[i + 1];
17307 					what.containingBlock.parts = what.containingBlock.parts[0 .. $-1];
17308 
17309 				}
17310 			}
17311 
17312 			// FIXME: ensure no other carets have a reference to it
17313 		}
17314 
17315 		/// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click.
17316 		TextIdentifyResult identify(int x, int y, bool exact = false) {
17317 			TextIdentifyResult inexactMatch;
17318 			foreach(block; blocks) {
17319 				foreach(part; block.parts) {
17320 					if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
17321 
17322 						// FIXME binary search
17323 						int tidx;
17324 						int lastX;
17325 						foreach_reverse(idxo, lx; part.letterXs) {
17326 							int idx = cast(int) idxo;
17327 							if(lx <= x) {
17328 								if(lastX && lastX - x < x - lx)
17329 									tidx = idx + 1;
17330 								else
17331 									tidx = idx;
17332 								break;
17333 							}
17334 							lastX = lx;
17335 						}
17336 
17337 						return TextIdentifyResult(part, tidx).fixupNewline;
17338 					} else if(!exact) {
17339 						// we're not in the box, but are we on the same line?
17340 						if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
17341 							inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
17342 					}
17343 				}
17344 			}
17345 
17346 			if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
17347 				return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
17348 
17349 			return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
17350 		}
17351 
17352 		void moveCaretToPixelCoordinates(int x, int y) {
17353 			auto result = identify(x, y);
17354 			caret.inlineElement = result.element;
17355 			caret.offset = result.offset;
17356 		}
17357 
17358 		void selectToPixelCoordinates(int x, int y) {
17359 			auto result = identify(x, y);
17360 
17361 			if(y < caretLastDrawnY1) {
17362 				// on a previous line, carat is selectionEnd
17363 				selectionEnd = caret;
17364 
17365 				selectionStart = Caret(this, result.element, result.offset);
17366 			} else if(y > caretLastDrawnY2) {
17367 				// on a later line
17368 				selectionStart = caret;
17369 
17370 				selectionEnd = Caret(this, result.element, result.offset);
17371 			} else {
17372 				// on the same line...
17373 				if(x <= caretLastDrawnX) {
17374 					selectionEnd = caret;
17375 					selectionStart = Caret(this, result.element, result.offset);
17376 				} else {
17377 					selectionStart = caret;
17378 					selectionEnd = Caret(this, result.element, result.offset);
17379 				}
17380 
17381 			}
17382 		}
17383 
17384 
17385 		/// Call this if the inputs change. It will reflow everything
17386 		void redoLayout(ScreenPainter painter) {
17387 			//painter.setClipRectangle(boundingBox);
17388 			auto pos = Point(boundingBox.left, boundingBox.top);
17389 
17390 			int lastHeight;
17391 			void nl() {
17392 				pos.x = boundingBox.left;
17393 				pos.y += lastHeight;
17394 			}
17395 			foreach(block; blocks) {
17396 				nl();
17397 				foreach(part; block.parts) {
17398 					part.letterXs = null;
17399 
17400 					auto size = painter.textSize(part.text);
17401 					version(Windows)
17402 						if(part.text.length && part.text[$-1] == '\n')
17403 							size.height /= 2; // windows counts the new line at the end, but we don't want that
17404 
17405 					part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height);
17406 
17407 					foreach(idx, char c; part.text) {
17408 							// FIXME: unicode
17409 						part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x;
17410 					}
17411 
17412 					pos.x += size.width;
17413 					if(pos.x >= boundingBox.right) {
17414 						pos.y += size.height;
17415 						pos.x = boundingBox.left;
17416 						lastHeight = 0;
17417 					} else {
17418 						lastHeight = size.height;
17419 					}
17420 
17421 					if(part.text.length && part.text[$-1] == '\n')
17422 						nl();
17423 				}
17424 			}
17425 
17426 			layoutInvalidated = false;
17427 		}
17428 
17429 		bool layoutInvalidated = true;
17430 		void invalidateLayout() {
17431 			layoutInvalidated = true;
17432 		}
17433 
17434 // FIXME: caret can remain sometimes when inserting
17435 // FIXME: inserting at the beginning once you already have something can eff it up.
17436 		void drawInto(ScreenPainter painter, bool focused = false) {
17437 			if(layoutInvalidated)
17438 				redoLayout(painter);
17439 			foreach(block; blocks) {
17440 				foreach(part; block.parts) {
17441 					painter.outlineColor = part.color;
17442 					painter.fillColor = part.backgroundColor;
17443 
17444 					auto pos = part.boundingBox.upperLeft;
17445 					auto size = part.boundingBox.size;
17446 
17447 					painter.drawText(pos, part.text);
17448 					if(part.styles & TextFormat.underline)
17449 						painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4));
17450 					if(part.styles & TextFormat.strikethrough)
17451 						painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2));
17452 				}
17453 			}
17454 
17455 			// on every redraw, I will force the caret to be
17456 			// redrawn too, in order to eliminate perceived lag
17457 			// when moving around with the mouse.
17458 			eraseCaret(painter);
17459 
17460 			if(focused) {
17461 				highlightSelection(painter);
17462 				drawCaret(painter);
17463 			}
17464 		}
17465 
17466 		void highlightSelection(ScreenPainter painter) {
17467 			if(selectionStart is selectionEnd)
17468 				return; // no selection
17469 
17470 			if(selectionStart.inlineElement is null) return;
17471 			if(selectionEnd.inlineElement is null) return;
17472 
17473 			assert(selectionStart.inlineElement !is null);
17474 			assert(selectionEnd.inlineElement !is null);
17475 
17476 			painter.rasterOp = RasterOp.xor;
17477 			painter.outlineColor = Color.transparent;
17478 			painter.fillColor = Color(255, 255, 127);
17479 
17480 			auto at = selectionStart.inlineElement;
17481 			auto atOffset = selectionStart.offset;
17482 			bool done;
17483 			while(at) {
17484 				auto box = at.boundingBox;
17485 				if(atOffset < at.letterXs.length)
17486 					box.left = at.letterXs[atOffset];
17487 
17488 				if(at is selectionEnd.inlineElement) {
17489 					if(selectionEnd.offset < at.letterXs.length)
17490 						box.right = at.letterXs[selectionEnd.offset];
17491 					done = true;
17492 				}
17493 
17494 				painter.drawRectangle(box.upperLeft, box.width, box.height);
17495 
17496 				if(done)
17497 					break;
17498 
17499 				at = at.getNextInlineElement();
17500 				atOffset = 0;
17501 			}
17502 		}
17503 
17504 		int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
17505 		bool caretShowingOnScreen = false;
17506 		void drawCaret(ScreenPainter painter) {
17507 			//painter.setClipRectangle(boundingBox);
17508 			int x, y1, y2;
17509 			if(caret.inlineElement is null) {
17510 				x = boundingBox.left;
17511 				y1 = boundingBox.top + 2;
17512 				y2 = boundingBox.top + painter.fontHeight;
17513 			} else {
17514 				x = caret.inlineElement.xOfIndex(caret.offset);
17515 				y1 = caret.inlineElement.boundingBox.top + 2;
17516 				y2 = caret.inlineElement.boundingBox.bottom - 2;
17517 			}
17518 
17519 			if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
17520 				eraseCaret(painter);
17521 
17522 			painter.pen = Pen(Color.white, 1);
17523 			painter.rasterOp = RasterOp.xor;
17524 			painter.drawLine(
17525 				Point(x, y1),
17526 				Point(x, y2)
17527 			);
17528 			painter.rasterOp = RasterOp.normal;
17529 			caretShowingOnScreen = !caretShowingOnScreen;
17530 
17531 			if(caretShowingOnScreen) {
17532 				caretLastDrawnX = x;
17533 				caretLastDrawnY1 = y1;
17534 				caretLastDrawnY2 = y2;
17535 			}
17536 		}
17537 
17538 		Rectangle caretBoundingBox() {
17539 			int x, y1, y2;
17540 			if(caret.inlineElement is null) {
17541 				x = boundingBox.left;
17542 				y1 = boundingBox.top + 2;
17543 				y2 = boundingBox.top + 16;
17544 			} else {
17545 				x = caret.inlineElement.xOfIndex(caret.offset);
17546 				y1 = caret.inlineElement.boundingBox.top + 2;
17547 				y2 = caret.inlineElement.boundingBox.bottom - 2;
17548 			}
17549 
17550 			return Rectangle(x, y1, x + 1, y2);
17551 		}
17552 
17553 		void eraseCaret(ScreenPainter painter) {
17554 			//painter.setClipRectangle(boundingBox);
17555 			if(!caretShowingOnScreen) return;
17556 			painter.pen = Pen(Color.white, 1);
17557 			painter.rasterOp = RasterOp.xor;
17558 			painter.drawLine(
17559 				Point(caretLastDrawnX, caretLastDrawnY1),
17560 				Point(caretLastDrawnX, caretLastDrawnY2)
17561 			);
17562 
17563 			caretShowingOnScreen = false;
17564 			painter.rasterOp = RasterOp.normal;
17565 		}
17566 
17567 		/// Caret movement api
17568 		/// These should give the user a logical result based on what they see on screen...
17569 		/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
17570 		void moveUp() {
17571 			if(caret.inlineElement is null) return;
17572 			auto x = caret.inlineElement.xOfIndex(caret.offset);
17573 			auto y = caret.inlineElement.boundingBox.top + 2;
17574 
17575 			y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
17576 			if(y < 0)
17577 				return;
17578 
17579 			auto i = identify(x, y);
17580 
17581 			if(i.element) {
17582 				caret.inlineElement = i.element;
17583 				caret.offset = i.offset;
17584 			}
17585 		}
17586 		void moveDown() {
17587 			if(caret.inlineElement is null) return;
17588 			auto x = caret.inlineElement.xOfIndex(caret.offset);
17589 			auto y = caret.inlineElement.boundingBox.bottom - 2;
17590 
17591 			y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
17592 
17593 			auto i = identify(x, y);
17594 			if(i.element) {
17595 				caret.inlineElement = i.element;
17596 				caret.offset = i.offset;
17597 			}
17598 		}
17599 		void moveLeft() {
17600 			if(caret.inlineElement is null) return;
17601 			if(caret.offset)
17602 				caret.offset--;
17603 			else {
17604 				auto p = caret.inlineElement.getPreviousInlineElement();
17605 				if(p) {
17606 					caret.inlineElement = p;
17607 					if(p.text.length && p.text[$-1] == '\n')
17608 						caret.offset = cast(int) p.text.length - 1;
17609 					else
17610 						caret.offset = cast(int) p.text.length;
17611 				}
17612 			}
17613 		}
17614 		void moveRight() {
17615 			if(caret.inlineElement is null) return;
17616 			if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
17617 				caret.offset++;
17618 			} else {
17619 				auto p = caret.inlineElement.getNextInlineElement();
17620 				if(p) {
17621 					caret.inlineElement = p;
17622 					caret.offset = 0;
17623 				}
17624 			}
17625 		}
17626 		void moveHome() {
17627 			if(caret.inlineElement is null) return;
17628 			auto x = 0;
17629 			auto y = caret.inlineElement.boundingBox.top + 2;
17630 
17631 			auto i = identify(x, y);
17632 
17633 			if(i.element) {
17634 				caret.inlineElement = i.element;
17635 				caret.offset = i.offset;
17636 			}
17637 		}
17638 		void moveEnd() {
17639 			if(caret.inlineElement is null) return;
17640 			auto x = int.max;
17641 			auto y = caret.inlineElement.boundingBox.top + 2;
17642 
17643 			auto i = identify(x, y);
17644 
17645 			if(i.element) {
17646 				caret.inlineElement = i.element;
17647 				caret.offset = i.offset;
17648 			}
17649 
17650 		}
17651 		void movePageUp(ref Caret caret) {}
17652 		void movePageDown(ref Caret caret) {}
17653 
17654 		void moveDocumentStart(ref Caret caret) {
17655 			if(blocks.length && blocks[0].parts.length)
17656 				caret = Caret(this, blocks[0].parts[0], 0);
17657 			else
17658 				caret = Caret.init;
17659 		}
17660 
17661 		void moveDocumentEnd(ref Caret caret) {
17662 			if(blocks.length) {
17663 				auto parts = blocks[$-1].parts;
17664 				if(parts.length) {
17665 					caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
17666 				} else {
17667 					caret = Caret.init;
17668 				}
17669 			} else
17670 				caret = Caret.init;
17671 		}
17672 
17673 		void deleteSelection() {
17674 			if(selectionStart is selectionEnd)
17675 				return;
17676 
17677 			if(selectionStart.inlineElement is null) return;
17678 			if(selectionEnd.inlineElement is null) return;
17679 
17680 			assert(selectionStart.inlineElement !is null);
17681 			assert(selectionEnd.inlineElement !is null);
17682 
17683 			auto at = selectionStart.inlineElement;
17684 
17685 			if(selectionEnd.inlineElement is at) {
17686 				// same element, need to chop out
17687 				at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
17688 				at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
17689 				selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
17690 			} else {
17691 				// different elements, we can do it with slicing
17692 				at.text = at.text[0 .. selectionStart.offset];
17693 				if(selectionStart.offset < at.letterXs.length)
17694 					at.letterXs = at.letterXs[0 .. selectionStart.offset];
17695 
17696 				at = at.getNextInlineElement();
17697 
17698 				while(at) {
17699 					if(at is selectionEnd.inlineElement) {
17700 						at.text = at.text[selectionEnd.offset .. $];
17701 						if(selectionEnd.offset < at.letterXs.length)
17702 							at.letterXs = at.letterXs[selectionEnd.offset .. $];
17703 						selectionEnd.offset = 0;
17704 						break;
17705 					} else {
17706 						auto cfd = at;
17707 						cfd.text = null; // delete the whole thing
17708 
17709 						at = at.getNextInlineElement();
17710 
17711 						if(cfd.text.length == 0) {
17712 							// and remove cfd
17713 							for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
17714 								if(cfd.containingBlock.parts[a] is cfd) {
17715 									for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
17716 										cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
17717 									cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
17718 
17719 								}
17720 							}
17721 						}
17722 					}
17723 				}
17724 			}
17725 
17726 			caret = selectionEnd;
17727 			selectNone();
17728 
17729 			invalidateLayout();
17730 
17731 		}
17732 
17733 		/// Plain text editing api. These work at the current caret inside the selected inline element.
17734 		void insert(in char[] text) {
17735 			foreach(dchar ch; text)
17736 				insert(ch);
17737 		}
17738 		/// ditto
17739 		void insert(dchar ch) {
17740 
17741 			bool selectionDeleted = false;
17742 			if(selectionStart !is selectionEnd) {
17743 				deleteSelection();
17744 				selectionDeleted = true;
17745 			}
17746 
17747 			if(ch == 127) {
17748 				delete_();
17749 				return;
17750 			}
17751 			if(ch == 8) {
17752 				if(!selectionDeleted)
17753 					backspace();
17754 				return;
17755 			}
17756 
17757 			invalidateLayout();
17758 
17759 			if(ch == 13) ch = 10;
17760 			auto e = caret.inlineElement;
17761 			if(e is null) {
17762 				addText("" ~ cast(char) ch) ; // FIXME
17763 				return;
17764 			}
17765 
17766 			if(caret.offset == e.text.length) {
17767 				e.text ~= cast(char) ch; // FIXME
17768 				caret.offset++;
17769 				if(ch == 10) {
17770 					auto c = caret.inlineElement.clone;
17771 					c.text = null;
17772 					c.letterXs = null;
17773 					insertPartAfter(c,e);
17774 					caret = Caret(this, c, 0);
17775 				}
17776 			} else {
17777 				// FIXME cast char sucks
17778 				if(ch == 10) {
17779 					auto c = caret.inlineElement.clone;
17780 					c.text = e.text[caret.offset .. $];
17781 					if(caret.offset < c.letterXs.length)
17782 						c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox
17783 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
17784 					if(caret.offset <= e.letterXs.length) {
17785 						e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box
17786 					}
17787 					insertPartAfter(c,e);
17788 					caret = Caret(this, c, 0);
17789 				} else {
17790 					e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
17791 					caret.offset++;
17792 				}
17793 			}
17794 		}
17795 
17796 		void insertPartAfter(InlineElement what, InlineElement where) {
17797 			foreach(idx, p; where.containingBlock.parts) {
17798 				if(p is where) {
17799 					if(idx + 1 == where.containingBlock.parts.length)
17800 						where.containingBlock.parts ~= what;
17801 					else
17802 						where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $];
17803 					return;
17804 				}
17805 			}
17806 		}
17807 
17808 		void cleanupStructures() {
17809 			for(size_t i = 0; i < blocks.length; i++) {
17810 				auto block = blocks[i];
17811 				for(size_t a = 0; a < block.parts.length; a++) {
17812 					auto part = block.parts[a];
17813 					if(part.text.length == 0) {
17814 						for(size_t b = a; b < block.parts.length - 1; b++)
17815 							block.parts[b] = block.parts[b+1];
17816 						block.parts = block.parts[0 .. $-1];
17817 					}
17818 				}
17819 				if(block.parts.length == 0) {
17820 					for(size_t a = i; a < blocks.length - 1; a++)
17821 						blocks[a] = blocks[a+1];
17822 					blocks = blocks[0 .. $-1];
17823 				}
17824 			}
17825 		}
17826 
17827 		void backspace() {
17828 			try_again:
17829 			auto e = caret.inlineElement;
17830 			if(e is null)
17831 				return;
17832 			if(caret.offset == 0) {
17833 				auto prev = e.getPreviousInlineElement();
17834 				if(prev is null)
17835 					return;
17836 				auto newOffset = cast(int) prev.text.length;
17837 				tryMerge(prev, e);
17838 				caret.inlineElement = prev;
17839 				caret.offset = prev is null ? 0 : newOffset;
17840 
17841 				goto try_again;
17842 			} else if(caret.offset == e.text.length) {
17843 				e.text = e.text[0 .. $-1];
17844 				caret.offset--;
17845 			} else {
17846 				e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
17847 				caret.offset--;
17848 			}
17849 			//cleanupStructures();
17850 
17851 			invalidateLayout();
17852 		}
17853 		void delete_() {
17854 			if(selectionStart !is selectionEnd)
17855 				deleteSelection();
17856 			else {
17857 				auto before = caret;
17858 				moveRight();
17859 				if(caret != before) {
17860 					backspace();
17861 				}
17862 			}
17863 
17864 			invalidateLayout();
17865 		}
17866 		void overstrike() {}
17867 
17868 		/// Selection API. See also: caret movement.
17869 		void selectAll() {
17870 			moveDocumentStart(selectionStart);
17871 			moveDocumentEnd(selectionEnd);
17872 		}
17873 		bool selectNone() {
17874 			if(selectionStart != selectionEnd) {
17875 				selectionStart = selectionEnd = Caret.init;
17876 				return true;
17877 			}
17878 			return false;
17879 		}
17880 
17881 		/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
17882 		/// They will modify the current selection if there is one and will splice one in if needed.
17883 		void changeAttributes() {}
17884 
17885 
17886 		/// Text search api. They manipulate the selection and/or caret.
17887 		void findText(string text) {}
17888 		void findIndex(size_t textIndex) {}
17889 
17890 		// sample event handlers
17891 
17892 		void handleEvent(KeyEvent event) {
17893 			//if(event.type == KeyEvent.Type.KeyPressed) {
17894 
17895 			//}
17896 		}
17897 
17898 		void handleEvent(dchar ch) {
17899 
17900 		}
17901 
17902 		void handleEvent(MouseEvent event) {
17903 
17904 		}
17905 
17906 		bool contentEditable; // can it be edited?
17907 		bool contentCaretable; // is there a caret/cursor that moves around in there?
17908 		bool contentSelectable; // selectable?
17909 
17910 		Caret caret;
17911 		Caret selectionStart;
17912 		Caret selectionEnd;
17913 
17914 		bool insertMode;
17915 	}
17916 
17917 	struct Caret {
17918 		TextLayout layout;
17919 		InlineElement inlineElement;
17920 		int offset;
17921 	}
17922 
17923 	enum TextFormat : ushort {
17924 		// decorations
17925 		underline = 1,
17926 		strikethrough = 2,
17927 
17928 		// font selectors
17929 
17930 		bold = 0x4000 | 1, // weight 700
17931 		light = 0x4000 | 2, // weight 300
17932 		veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold
17933 		// bold | light is really invalid but should give weight 500
17934 		// veryBoldOrLight without one of the others should just give the default for the font; it should be ignored.
17935 
17936 		italic = 0x4000 | 8,
17937 		smallcaps = 0x4000 | 16,
17938 	}
17939 
17940 	void* findFont(string family, int weight, TextFormat formats) {
17941 		return null;
17942 	}
17943 
17944 }
17945 
17946 /++
17947 	$(PITFALL this is not yet stable and may break in future versions without notice.)
17948 
17949 	History:
17950 		Added February 19, 2021
17951 +/
17952 /// Group: drag_and_drop
17953 interface DropHandler {
17954 	/++
17955 		Called when the drag enters the handler's area.
17956 	+/
17957 	DragAndDropAction dragEnter(DropPackage*);
17958 	/++
17959 		Called when the drag leaves the handler's area or is
17960 		cancelled. You should free your resources when this is called.
17961 	+/
17962 	void dragLeave();
17963 	/++
17964 		Called continually as the drag moves over the handler's area.
17965 
17966 		Returns: feedback to the dragger
17967 	+/
17968 	DropParameters dragOver(Point pt);
17969 	/++
17970 		The user dropped the data and you should process it now. You can
17971 		access the data through the given [DropPackage].
17972 	+/
17973 	void drop(scope DropPackage*);
17974 	/++
17975 		Called when the drop is complete. You should free whatever temporary
17976 		resources you were using. It is often reasonable to simply forward
17977 		this call to [dragLeave].
17978 	+/
17979 	void finish();
17980 
17981 	/++
17982 		Parameters returned by [DropHandler.drop].
17983 	+/
17984 	static struct DropParameters {
17985 		/++
17986 			Acceptable action over this area.
17987 		+/
17988 		DragAndDropAction action;
17989 		/++
17990 			Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again.
17991 
17992 			If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources.
17993 		+/
17994 		Rectangle consistentWithin;
17995 	}
17996 }
17997 
17998 /++
17999 	History:
18000 		Added February 19, 2021
18001 +/
18002 /// Group: drag_and_drop
18003 enum DragAndDropAction {
18004 	none = 0,
18005 	copy,
18006 	move,
18007 	link,
18008 	ask,
18009 	custom
18010 }
18011 
18012 /++
18013 	An opaque structure representing dropped data. It contains
18014 	private, platform-specific data that your `drop` function
18015 	should simply forward.
18016 
18017 	$(PITFALL this is not yet stable and may break in future versions without notice.)
18018 
18019 	History:
18020 		Added February 19, 2021
18021 +/
18022 /// Group: drag_and_drop
18023 struct DropPackage {
18024 	/++
18025 		Lists the available formats as magic numbers. You should compare these
18026 		against looked-up formats (see [DraggableData.getFormatId]) you know you support and can
18027 		understand the passed data.
18028 	+/
18029 	DraggableData.FormatId[] availableFormats() {
18030 		version(X11) {
18031 			return xFormats;
18032 		} else version(Windows) {
18033 			if(pDataObj is null)
18034 				return null;
18035 
18036 			typeof(return) ret;
18037 
18038 			IEnumFORMATETC ef;
18039 			if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) {
18040 				FORMATETC fmt;
18041 				ULONG fetched;
18042 				while(ef.Next(1, &fmt, &fetched) == S_OK) {
18043 					if(fetched == 0)
18044 						break;
18045 
18046 					if(fmt.lindex != -1)
18047 						continue;
18048 					if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT)
18049 						continue;
18050 					if(!(fmt.tymed & TYMED.TYMED_HGLOBAL))
18051 						continue;
18052 
18053 					ret ~= fmt.cfFormat;
18054 				}
18055 			}
18056 
18057 			return ret;
18058 		}
18059 	}
18060 
18061 	/++
18062 		Gets data from the drop and optionally accepts it.
18063 
18064 		Returns:
18065 			void because the data is fed asynchronously through the `dg` parameter.
18066 
18067 		Params:
18068 			acceptedAction = the action to report back to the ender. If it is [DragAndDropAction.none], you are just inspecting the data, but not accepting the drop.
18069 
18070 			This is useful to tell the sender that you accepted a move, for example, so they can update their data source as well. For other cases, accepting a drop also indicates that any memory associated with the transfer can be freed.
18071 
18072 			Calling `getData` again after accepting a drop is not permitted.
18073 
18074 			format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format.
18075 
18076 			dg = delegate to receive the data asynchronously. Please note this delegate may be called immediately, never be called, or be called somewhere in between during event loop processing depending on the platform, requested format, and other conditions beyond your control.
18077 
18078 		Throws:
18079 			if `format` was not compatible with the [availableFormats] or if the drop has already been accepted.
18080 
18081 		History:
18082 			Included in first release of [DropPackage].
18083 	+/
18084 	void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) {
18085 		version(X11) {
18086 
18087 			auto display = XDisplayConnection.get();
18088 			auto selectionAtom = GetAtom!"XdndSelection"(display);
18089 			auto best = format;
18090 
18091 			static class X11GetSelectionHandler_Drop : X11GetSelectionHandler {
18092 
18093 				XDisplay* display;
18094 				Atom selectionAtom;
18095 				DraggableData.FormatId best;
18096 				DraggableData.FormatId format;
18097 				void delegate(scope ubyte[] data) dg;
18098 				DragAndDropAction acceptedAction;
18099 				Window sourceWindow;
18100 				SimpleWindow win;
18101 				this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) {
18102 					this.display = display;
18103 					this.win = win;
18104 					this.sourceWindow = sourceWindow;
18105 					this.format = format;
18106 					this.selectionAtom = selectionAtom;
18107 					this.best = best;
18108 					this.dg = dg;
18109 					this.acceptedAction = acceptedAction;
18110 				}
18111 
18112 
18113 				mixin X11GetSelectionHandler_Basics;
18114 
18115 				void handleData(Atom target, in ubyte[] data) {
18116 					//if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get))
18117 
18118 					dg(cast(ubyte[]) data);
18119 
18120 					if(acceptedAction != DragAndDropAction.none) {
18121 						auto display = XDisplayConnection.get;
18122 
18123 						XClientMessageEvent xclient;
18124 
18125 						xclient.type = EventType.ClientMessage;
18126 						xclient.window = sourceWindow;
18127 						xclient.message_type = GetAtom!"XdndFinished"(display);
18128 						xclient.format = 32;
18129 						xclient.data.l[0] = win.impl.window;
18130 						xclient.data.l[1] = 1; // drop successful
18131 						xclient.data.l[2] = dndActionAtom(display, acceptedAction);
18132 
18133 						XSendEvent(
18134 							display,
18135 							sourceWindow,
18136 							false,
18137 							EventMask.NoEventMask,
18138 							cast(XEvent*) &xclient
18139 						);
18140 
18141 						XFlush(display);
18142 					}
18143 				}
18144 
18145 				Atom findBestFormat(Atom[] answer) {
18146 					Atom best = None;
18147 					foreach(option; answer) {
18148 						if(option == format) {
18149 							best = option;
18150 							break;
18151 						}
18152 						/*
18153 						if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) {
18154 							best = option;
18155 							break;
18156 						} else if(option == XA_STRING) {
18157 							best = option;
18158 						} else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) {
18159 							best = option;
18160 						}
18161 						*/
18162 					}
18163 					return best;
18164 				}
18165 			}
18166 
18167 			win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction);
18168 
18169 			XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp);
18170 
18171 		} else version(Windows) {
18172 
18173 			// clean up like DragLeave
18174 			// pass effect back up
18175 
18176 			FORMATETC t;
18177 			assert(format >= 0 && format <= ushort.max);
18178 			t.cfFormat = cast(ushort) format;
18179 			t.lindex = -1;
18180 			t.dwAspect = DVASPECT.DVASPECT_CONTENT;
18181 			t.tymed = TYMED.TYMED_HGLOBAL;
18182 
18183 			STGMEDIUM m;
18184 
18185 			if(pDataObj.GetData(&t, &m) != S_OK) {
18186 				// fail
18187 			} else {
18188 				// succeed, take the data and clean up
18189 
18190 				// FIXME: ensure it is legit HGLOBAL
18191 				auto handle = m.hGlobal;
18192 
18193 				if(handle) {
18194 					auto sz = GlobalSize(handle);
18195 					if(auto ptr = cast(ubyte*) GlobalLock(handle)) {
18196 						scope(exit) GlobalUnlock(handle);
18197 						scope(exit) GlobalFree(handle);
18198 
18199 						auto data = ptr[0 .. sz];
18200 
18201 						dg(data);
18202 					}
18203 				}
18204 			}
18205 		}
18206 	}
18207 
18208 	private:
18209 
18210 	version(X11) {
18211 		SimpleWindow win;
18212 		Window sourceWindow;
18213 		Time dataTimestamp;
18214 
18215 		Atom[] xFormats;
18216 	}
18217 	version(Windows) {
18218 		IDataObject pDataObj;
18219 	}
18220 }
18221 
18222 /++
18223 	A generic helper base class for making a drop handler with a preference list of custom types.
18224 	This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own
18225 	droppers too.
18226 
18227 	It assumes the whole window it used, but you can subclass to change that.
18228 
18229 	$(PITFALL this is not yet stable and may break in future versions without notice.)
18230 
18231 	History:
18232 		Added February 19, 2021
18233 +/
18234 /// Group: drag_and_drop
18235 class GenericDropHandlerBase : DropHandler {
18236 	// no fancy state here so no need to do anything here
18237 	void finish() { }
18238 	void dragLeave() { }
18239 
18240 	private DragAndDropAction acceptedAction;
18241 	private DraggableData.FormatId acceptedFormat;
18242 	private void delegate(scope ubyte[]) acceptedHandler;
18243 
18244 	struct FormatHandler {
18245 		DraggableData.FormatId format;
18246 		void delegate(scope ubyte[]) handler;
18247 	}
18248 
18249 	protected abstract FormatHandler[] formatHandlers();
18250 
18251 	DragAndDropAction dragEnter(DropPackage* pkg) {
18252 		debug(sdpy_dnd) { import std.stdio; foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); }
18253 		foreach(fmt; formatHandlers())
18254 		foreach(f; pkg.availableFormats())
18255 			if(f == fmt.format) {
18256 				acceptedFormat = f;
18257 				acceptedHandler = fmt.handler;
18258 				return acceptedAction = DragAndDropAction.copy;
18259 			}
18260 		return acceptedAction = DragAndDropAction.none;
18261 	}
18262 	DropParameters dragOver(Point pt) {
18263 		return DropParameters(acceptedAction);
18264 	}
18265 
18266 	void drop(scope DropPackage* dropPackage) {
18267 		if(!acceptedFormat || acceptedHandler is null) {
18268 			debug(sdpy_dnd) { import std.stdio; writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); }
18269 			return; // prolly shouldn't happen anyway...
18270 		}
18271 
18272 		dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler);
18273 	}
18274 }
18275 
18276 /++
18277 	A simple handler for making your window accept drops of plain text.
18278 
18279 	$(PITFALL this is not yet stable and may break in future versions without notice.)
18280 
18281 	History:
18282 		Added February 22, 2021
18283 +/
18284 /// Group: drag_and_drop
18285 class TextDropHandler : GenericDropHandlerBase {
18286 	private void delegate(in char[] text) dg;
18287 
18288 	/++
18289 
18290 	+/
18291 	this(void delegate(in char[] text) dg) {
18292 		this.dg = dg;
18293 	}
18294 
18295 	protected override FormatHandler[] formatHandlers() {
18296 		version(X11)
18297 			return [
18298 				FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator),
18299 				FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator),
18300 			];
18301 		else version(Windows)
18302 			return [
18303 				FormatHandler(CF_UNICODETEXT, &translator),
18304 			];
18305 	}
18306 
18307 	private void translator(scope ubyte[] data) {
18308 		version(X11)
18309 			dg(cast(char[]) data);
18310 		else version(Windows)
18311 			dg(makeUtf8StringFromWindowsString(cast(wchar[]) data));
18312 	}
18313 }
18314 
18315 /++
18316 	A simple handler for making your window accept drops of files, issued to you as file names.
18317 
18318 	$(PITFALL this is not yet stable and may break in future versions without notice.)
18319 
18320 	History:
18321 		Added February 22, 2021
18322 +/
18323 /// Group: drag_and_drop
18324 
18325 class FilesDropHandler : GenericDropHandlerBase {
18326 	private void delegate(in char[][]) dg;
18327 
18328 	/++
18329 
18330 	+/
18331 	this(void delegate(in char[][] fileNames) dg) {
18332 		this.dg = dg;
18333 	}
18334 
18335 	protected override FormatHandler[] formatHandlers() {
18336 		version(X11)
18337 			return [
18338 				FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator),
18339 			];
18340 		else version(Windows)
18341 			return [
18342 				FormatHandler(CF_HDROP, &translator),
18343 			];
18344 	}
18345 
18346 	private void translator(scope ubyte[] data) {
18347 		version(X11) {
18348 			char[] listString = cast(char[]) data;
18349 			char[][16] buffer;
18350 			int count;
18351 			char[][] result = buffer[];
18352 
18353 			void commit(char[] s) {
18354 				if(count == result.length)
18355 					result.length += 16;
18356 				if(s.length > 7 && s[0 ..7] == "file://")
18357 					s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding
18358 				result[count++] = s;
18359 			}
18360 
18361 			size_t last;
18362 			foreach(idx, char c; listString) {
18363 				if(c == '\n') {
18364 					commit(listString[last .. idx - 1]); // a \r
18365 					last = idx + 1; // a \n
18366 				}
18367 			}
18368 
18369 			if(last < listString.length) {
18370 				commit(listString[last .. $]);
18371 			}
18372 
18373 			// FIXME: they are uris now, should I translate it to local file names?
18374 			// of course the host name is supposed to be there cuz of X rokking...
18375 
18376 			dg(result[0 .. count]);
18377 		} else version(Windows) {
18378 
18379 			static struct DROPFILES {
18380 				DWORD pFiles;
18381 				POINT pt;
18382 				BOOL  fNC;
18383 				BOOL  fWide;
18384 			}
18385 
18386 
18387 			const(char)[][16] buffer;
18388 			int count;
18389 			const(char)[][] result = buffer[];
18390 			size_t last;
18391 
18392 			void commitA(in char[] stuff) {
18393 				if(count == result.length)
18394 					result.length += 16;
18395 				result[count++] = stuff;
18396 			}
18397 
18398 			void commitW(in wchar[] stuff) {
18399 				commitA(makeUtf8StringFromWindowsString(stuff));
18400 			}
18401 
18402 			void magic(T)(T chars) {
18403 				size_t idx;
18404 				while(chars[idx]) {
18405 					last = idx;
18406 					while(chars[idx]) {
18407 						idx++;
18408 					}
18409 					static if(is(T == char*))
18410 						commitA(chars[last .. idx]);
18411 					else
18412 						commitW(chars[last .. idx]);
18413 					idx++;
18414 				}
18415 			}
18416 
18417 			auto df = cast(DROPFILES*) data.ptr;
18418 			if(df.fWide) {
18419 				wchar* chars = cast(wchar*) (data.ptr + df.pFiles);
18420 				magic(chars);
18421 			} else {
18422 				char* chars = cast(char*) (data.ptr + df.pFiles);
18423 				magic(chars);
18424 			}
18425 			dg(result[0 .. count]);
18426 		}
18427 	}
18428 }
18429 
18430 /++
18431 	Interface to describe data being dragged. See also [draggable] helper function.
18432 
18433 	$(PITFALL this is not yet stable and may break in future versions without notice.)
18434 
18435 	History:
18436 		Added February 19, 2021
18437 +/
18438 interface DraggableData {
18439 	version(X11)
18440 		alias FormatId = Atom;
18441 	else
18442 		alias FormatId = uint;
18443 	/++
18444 		Gets the platform-specific FormatId associated with the given named format.
18445 
18446 		This may be a MIME type, but may also be other various strings defined by the
18447 		programs you want to interoperate with.
18448 
18449 		FIXME: sdpy needs to offer data adapter things that look for compatible formats
18450 		and convert it to some particular type for you.
18451 	+/
18452 	static FormatId getFormatId(string name)() {
18453 		version(X11)
18454 			return GetAtom!name(XDisplayConnection.get);
18455 		else version(Windows) {
18456 			static UINT cache;
18457 			if(!cache)
18458 				cache = RegisterClipboardFormatA(name);
18459 			return cache;
18460 		} else
18461 			throw new NotYetImplementedException();
18462 	}
18463 
18464 	/++
18465 		Looks up a string to represent the name for the given format, if there is one.
18466 
18467 		You should avoid using this function because it is slow. It is provided more for
18468 		debugging than for primary use.
18469 	+/
18470 	static string getFormatName(FormatId format) {
18471 		version(X11) {
18472 			if(format == 0)
18473 				return "None";
18474 			else
18475 				return getAtomName(format, XDisplayConnection.get);
18476 		} else version(Windows) {
18477 			switch(format) {
18478 				case CF_UNICODETEXT: return "CF_UNICODETEXT";
18479 				case CF_DIBV5: return "CF_DIBV5";
18480 				case CF_RIFF: return "CF_RIFF";
18481 				case CF_WAVE: return "CF_WAVE";
18482 				case CF_HDROP: return "CF_HDROP";
18483 				default:
18484 					char[1024] name;
18485 					auto count = GetClipboardFormatNameA(format, name.ptr, name.length);
18486 					return name[0 .. count].idup;
18487 			}
18488 		}
18489 	}
18490 
18491 	FormatId[] availableFormats();
18492 	// Return the slice of data you filled, empty slice if done.
18493 	// this is to support the incremental thing
18494 	ubyte[] getData(FormatId format, return scope ubyte[] data);
18495 
18496 	size_t dataLength(FormatId format);
18497 }
18498 
18499 /++
18500 	$(PITFALL this is not yet stable and may break in future versions without notice.)
18501 
18502 	History:
18503 		Added February 19, 2021
18504 +/
18505 DraggableData draggable(string s) {
18506 	version(X11)
18507 	return new class X11SetSelectionHandler_Text, DraggableData {
18508 		this() {
18509 			super(s);
18510 		}
18511 
18512 		override FormatId[] availableFormats() {
18513 			return X11SetSelectionHandler_Text.availableFormats();
18514 		}
18515 
18516 		override ubyte[] getData(FormatId format, return scope ubyte[] data) {
18517 			return X11SetSelectionHandler_Text.getData(format, data);
18518 		}
18519 
18520 		size_t dataLength(FormatId format) {
18521 			return s.length;
18522 		}
18523 	};
18524 	version(Windows)
18525 	return new class DraggableData {
18526 		FormatId[] availableFormats() {
18527 			return [CF_UNICODETEXT];
18528 		}
18529 
18530 		ubyte[] getData(FormatId format, return scope ubyte[] data) {
18531 			return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate);
18532 		}
18533 
18534 		size_t dataLength(FormatId format) {
18535 			return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof;
18536 		}
18537 	};
18538 }
18539 
18540 /++
18541 	$(PITFALL this is not yet stable and may break in future versions without notice.)
18542 
18543 	History:
18544 		Added February 19, 2021
18545 +/
18546 /// Group: drag_and_drop
18547 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy)
18548 in {
18549 	assert(window !is null);
18550 	assert(handler !is null);
18551 }
18552 do
18553 {
18554 	version(X11) {
18555 		auto sh = cast(X11SetSelectionHandler) handler;
18556 		if(sh is null) {
18557 			// gotta make my own adapter.
18558 			sh = new class X11SetSelectionHandler {
18559 				mixin X11SetSelectionHandler_Basics;
18560 
18561 				Atom[] availableFormats() { return handler.availableFormats(); }
18562 				ubyte[] getData(Atom format, return scope ubyte[] data) {
18563 					return handler.getData(format, data);
18564 				}
18565 
18566 				// since the drop selection is only ever used once it isn't important
18567 				// to reset it.
18568 				void done() {}
18569 			};
18570 		}
18571 		return doDragDropX11(window, sh, action);
18572 	} else version(Windows) {
18573 		return doDragDropWindows(window, handler, action);
18574 	} else throw new NotYetImplementedException();
18575 }
18576 
18577 version(Windows)
18578 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) {
18579 	IDataObject obj = new class IDataObject {
18580 		ULONG refCount;
18581 		ULONG AddRef() {
18582 			return ++refCount;
18583 		}
18584 		ULONG Release() {
18585 			return --refCount;
18586 		}
18587 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
18588 			if (IID_IUnknown == *riid) {
18589 				*ppv = cast(void*) cast(IUnknown) this;
18590 			}
18591 			else if (IID_IDataObject == *riid) {
18592 				*ppv = cast(void*) cast(IDataObject) this;
18593 			}
18594 			else {
18595 				*ppv = null;
18596 				return E_NOINTERFACE;
18597 			}
18598 
18599 			AddRef();
18600 			return NOERROR;
18601 		}
18602 
18603 		HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
18604 			// import std.stdio; writeln("Advise");
18605 			return E_NOTIMPL;
18606 		}
18607 		HRESULT DUnadvise(DWORD dwConnection) {
18608 			return E_NOTIMPL;
18609 		}
18610 		HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
18611 			// import std.stdio; writeln("EnumDAdvise");
18612 			return OLE_E_ADVISENOTSUPPORTED;
18613 		}
18614 		// tell what formats it supports
18615 
18616 		FORMATETC[] types;
18617 		this() {
18618 			FORMATETC t;
18619 			foreach(ty; handler.availableFormats()) {
18620 				assert(ty <= ushort.max && ty >= 0);
18621 				t.cfFormat = cast(ushort) ty;
18622 				t.lindex = -1;
18623 				t.dwAspect = DVASPECT.DVASPECT_CONTENT;
18624 				t.tymed = TYMED.TYMED_HGLOBAL;
18625 			}
18626 			types ~= t;
18627 		}
18628 		HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) {
18629 			if(dwDirection == DATADIR.DATADIR_GET) {
18630 				*ppenumFormatEtc = new class IEnumFORMATETC {
18631 					ULONG refCount;
18632 					ULONG AddRef() {
18633 						return ++refCount;
18634 					}
18635 					ULONG Release() {
18636 						return --refCount;
18637 					}
18638 					HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
18639 						if (IID_IUnknown == *riid) {
18640 							*ppv = cast(void*) cast(IUnknown) this;
18641 						}
18642 						else if (IID_IEnumFORMATETC == *riid) {
18643 							*ppv = cast(void*) cast(IEnumFORMATETC) this;
18644 						}
18645 						else {
18646 							*ppv = null;
18647 							return E_NOINTERFACE;
18648 						}
18649 
18650 						AddRef();
18651 						return NOERROR;
18652 					}
18653 
18654 
18655 					int pos;
18656 					this() {
18657 						pos = 0;
18658 					}
18659 
18660 					HRESULT Clone(IEnumFORMATETC* ppenum) {
18661 						// import std.stdio; writeln("clone");
18662 						return E_NOTIMPL; // FIXME
18663 					}
18664 
18665 					// Caller is responsible for freeing memory
18666 					HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
18667 						// fetched may be null if celt is one
18668 						if(celt != 1)
18669 							return E_NOTIMPL; // FIXME
18670 
18671 						if(celt + pos > types.length)
18672 							return S_FALSE;
18673 
18674 						*rgelt = types[pos++];
18675 
18676 						if(pceltFetched !is null)
18677 							*pceltFetched = 1;
18678 
18679 						// import std.stdio; writeln("ok celt ", celt);
18680 						return S_OK;
18681 					}
18682 
18683 					HRESULT Reset() {
18684 						pos = 0;
18685 						return S_OK;
18686 					}
18687 
18688 					HRESULT Skip(ULONG celt) {
18689 						if(celt + pos <= types.length) {
18690 							pos += celt;
18691 							return S_OK;
18692 						}
18693 						return S_FALSE;
18694 					}
18695 				};
18696 
18697 				return S_OK;
18698 			} else
18699 				return E_NOTIMPL;
18700 		}
18701 		// given a format, return the format you'd prefer to use cuz it is identical
18702 		HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) {
18703 			// FIXME: prolly could be better but meh
18704 			// import std.stdio; writeln("gcf: ", *pformatectIn);
18705 			*pformatetcOut = *pformatectIn;
18706 			return S_OK;
18707 		}
18708 		HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
18709 			foreach(ty; types) {
18710 				if(ty == *pformatetcIn) {
18711 					auto format = ty.cfFormat;
18712 					// import std.stdio; writeln("A: ", *pformatetcIn, "\nB: ", ty);
18713 					STGMEDIUM medium;
18714 					medium.tymed = TYMED.TYMED_HGLOBAL;
18715 
18716 					auto sz = handler.dataLength(format);
18717 					auto handle = GlobalAlloc(GMEM_MOVEABLE, sz);
18718 					if(handle is null) throw new Exception("GlobalAlloc");
18719 					if(auto data = cast(wchar*) GlobalLock(handle)) {
18720 						auto slice = data[0 .. sz];
18721 						scope(exit)
18722 							GlobalUnlock(handle);
18723 
18724 						handler.getData(format, cast(ubyte[]) slice[]);
18725 					}
18726 
18727 
18728 					medium.hGlobal = handle; // FIXME
18729 					*pmedium = medium;
18730 					return S_OK;
18731 				}
18732 			}
18733 			return DV_E_FORMATETC;
18734 		}
18735 		HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) {
18736 			// import std.stdio; writeln("GDH: ", *pformatetcIn);
18737 			return E_NOTIMPL; // FIXME
18738 		}
18739 		HRESULT QueryGetData(FORMATETC* pformatetc) {
18740 			auto search = *pformatetc;
18741 			search.tymed &= TYMED.TYMED_HGLOBAL;
18742 			foreach(ty; types)
18743 				if(ty == search) {
18744 					// import std.stdio; writeln("QueryGetData ", search, " ", types[0]);
18745 					return S_OK;
18746 				}
18747 			if(pformatetc.cfFormat==CF_UNICODETEXT) {
18748 				//import std.stdio; writeln("QueryGetData FALSE ", search, " ", types[0]);
18749 			}
18750 			return S_FALSE;
18751 		}
18752 		HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) {
18753 			// import std.stdio; writeln("SetData: ");
18754 			return E_NOTIMPL;
18755 		}
18756 	};
18757 
18758 
18759 	IDropSource src = new class IDropSource {
18760 		ULONG refCount;
18761 		ULONG AddRef() {
18762 			return ++refCount;
18763 		}
18764 		ULONG Release() {
18765 			return --refCount;
18766 		}
18767 		HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
18768 			if (IID_IUnknown == *riid) {
18769 				*ppv = cast(void*) cast(IUnknown) this;
18770 			}
18771 			else if (IID_IDropSource == *riid) {
18772 				*ppv = cast(void*) cast(IDropSource) this;
18773 			}
18774 			else {
18775 				*ppv = null;
18776 				return E_NOINTERFACE;
18777 			}
18778 
18779 			AddRef();
18780 			return NOERROR;
18781 		}
18782 
18783 		int QueryContinueDrag(int fEscapePressed, uint grfKeyState) {
18784 			if(fEscapePressed)
18785 				return DRAGDROP_S_CANCEL;
18786 			if(!(grfKeyState & MK_LBUTTON))
18787 				return DRAGDROP_S_DROP;
18788 			return S_OK;
18789 		}
18790 
18791 		int GiveFeedback(uint dwEffect) {
18792 			return DRAGDROP_S_USEDEFAULTCURSORS;
18793 		}
18794 	};
18795 
18796 	DWORD effect;
18797 
18798 	if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect.");
18799 
18800 	DROPEFFECT de = win32DragAndDropAction(action);
18801 
18802 	// I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time
18803 	// but still prolly a FIXME
18804 
18805 	auto ret = DoDragDrop(obj, src, de, &effect);
18806 	/+
18807 	import std.stdio;
18808 	if(ret == DRAGDROP_S_DROP)
18809 		writeln("drop ", effect);
18810 	else if(ret == DRAGDROP_S_CANCEL)
18811 		writeln("cancel");
18812 	else if(ret == S_OK)
18813 		writeln("ok");
18814 	else writeln(ret);
18815 	+/
18816 
18817 	return ret;
18818 }
18819 
18820 version(Windows)
18821 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) {
18822 	DROPEFFECT de;
18823 
18824 	with(DragAndDropAction)
18825 	with(DROPEFFECT)
18826 	final switch(action) {
18827 		case none: de = DROPEFFECT_NONE; break;
18828 		case copy: de = DROPEFFECT_COPY; break;
18829 		case move: de = DROPEFFECT_MOVE; break;
18830 		case link: de = DROPEFFECT_LINK; break;
18831 		case ask: throw new Exception("ask not implemented yet");
18832 		case custom: throw new Exception("custom not implemented yet");
18833 	}
18834 
18835 	return de;
18836 }
18837 
18838 
18839 /++
18840 	History:
18841 		Added February 19, 2021
18842 +/
18843 /// Group: drag_and_drop
18844 void enableDragAndDrop(SimpleWindow window, DropHandler handler) {
18845 	version(X11) {
18846 		auto display = XDisplayConnection.get;
18847 
18848 		Atom atom = 5; // right???
18849 
18850 		XChangeProperty(
18851 			display,
18852 			window.impl.window,
18853 			GetAtom!"XdndAware"(display),
18854 			XA_ATOM,
18855 			32 /* bits */,
18856 			PropModeReplace,
18857 			&atom,
18858 			1);
18859 
18860 		window.dropHandler = handler;
18861 	} else version(Windows) {
18862 
18863 		initDnd();
18864 
18865 		auto dropTarget = new class (handler) IDropTarget {
18866 			DropHandler handler;
18867 			this(DropHandler handler) {
18868 				this.handler = handler;
18869 			}
18870 			ULONG refCount;
18871 			ULONG AddRef() {
18872 				return ++refCount;
18873 			}
18874 			ULONG Release() {
18875 				return --refCount;
18876 			}
18877 			HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
18878 				if (IID_IUnknown == *riid) {
18879 					*ppv = cast(void*) cast(IUnknown) this;
18880 				}
18881 				else if (IID_IDropTarget == *riid) {
18882 					*ppv = cast(void*) cast(IDropTarget) this;
18883 				}
18884 				else {
18885 					*ppv = null;
18886 					return E_NOINTERFACE;
18887 				}
18888 
18889 				AddRef();
18890 				return NOERROR;
18891 			}
18892 
18893 
18894 			// ///////////////////
18895 
18896 			HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
18897 				DropPackage dropPackage = DropPackage(pDataObj);
18898 				*pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage));
18899 				return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter
18900 			}
18901 
18902 			HRESULT DragLeave() {
18903 				handler.dragLeave();
18904 				// release the IDataObject if needed
18905 				return S_OK;
18906 			}
18907 
18908 			HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
18909 				auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates
18910 
18911 				*pdwEffect = win32DragAndDropAction(res.action);
18912 				// same as DragEnter basically
18913 				return S_OK;
18914 			}
18915 
18916 			HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
18917 				DropPackage pkg = DropPackage(pDataObj);
18918 				handler.drop(&pkg);
18919 
18920 				return S_OK;
18921 			}
18922 		};
18923 		// Windows can hold on to the handler and try to call it
18924 		// during which time the GC can't see it. so important to
18925 		// manually manage this. At some point i'll FIXME and make
18926 		// all my com instances manually managed since they supposed
18927 		// to respect the refcount.
18928 		import core.memory;
18929 		GC.addRoot(cast(void*) dropTarget);
18930 
18931 		if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK)
18932 			throw new Exception("register");
18933 
18934 		window.dropHandler = handler;
18935 	} else throw new NotYetImplementedException();
18936 }
18937 
18938 
18939 
18940 static if(UsingSimpledisplayX11) {
18941 
18942 enum _NET_WM_STATE_ADD = 1;
18943 enum _NET_WM_STATE_REMOVE = 0;
18944 enum _NET_WM_STATE_TOGGLE = 2;
18945 
18946 /// X-specific. Use [SimpleWindow.requestAttention] instead for most casesl
18947 void demandAttention(SimpleWindow window, bool needs = true) {
18948 	demandAttention(window.impl.window, needs);
18949 }
18950 
18951 /// ditto
18952 void demandAttention(Window window, bool needs = true) {
18953 	setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs);
18954 }
18955 
18956 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) {
18957 	auto display = XDisplayConnection.get();
18958 	if(atom == None)
18959 		return; // non-failure error
18960 	//auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display);
18961 
18962 	XClientMessageEvent xclient;
18963 
18964 	xclient.type = EventType.ClientMessage;
18965 	xclient.window = window;
18966 	xclient.message_type = GetAtom!"_NET_WM_STATE"(display);
18967 	xclient.format = 32;
18968 	xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
18969 	xclient.data.l[1] = atom;
18970 	xclient.data.l[2] = atom2;
18971 	xclient.data.l[3] = 1;
18972 	// [3] == source. 0 == unknown, 1 == app, 2 == else
18973 
18974 	XSendEvent(
18975 		display,
18976 		RootWindow(display, DefaultScreen(display)),
18977 		false,
18978 		EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask,
18979 		cast(XEvent*) &xclient
18980 	);
18981 
18982 	/+
18983 	XChangeProperty(
18984 		display,
18985 		window.impl.window,
18986 		GetAtom!"_NET_WM_STATE"(display),
18987 		XA_ATOM,
18988 		32 /* bits */,
18989 		PropModeAppend,
18990 		&atom,
18991 		1);
18992 	+/
18993 }
18994 
18995 private Atom dndActionAtom(Display* display, DragAndDropAction action) {
18996 	Atom actionAtom;
18997 	with(DragAndDropAction)
18998 	final switch(action) {
18999 		case none: actionAtom = None; break;
19000 		case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break;
19001 		case move: actionAtom = GetAtom!"XdndActionMove"(display); break;
19002 		case link: actionAtom = GetAtom!"XdndActionLink"(display); break;
19003 		case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break;
19004 		case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break;
19005 	}
19006 
19007 	return actionAtom;
19008 }
19009 
19010 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) {
19011 	// FIXME: I need to show user feedback somehow.
19012 	auto display = XDisplayConnection.get;
19013 
19014 	auto actionAtom = dndActionAtom(display, action);
19015 	assert(actionAtom, "Don't use action none to accept a drop");
19016 
19017 	setX11Selection!"XdndSelection"(window, handler, null);
19018 
19019 	auto oldKeyHandler = window.handleKeyEvent;
19020 	scope(exit) window.handleKeyEvent = oldKeyHandler;
19021 
19022 	auto oldCharHandler = window.handleCharEvent;
19023 	scope(exit) window.handleCharEvent = oldCharHandler;
19024 
19025 	auto oldMouseHandler = window.handleMouseEvent;
19026 	scope(exit) window.handleMouseEvent = oldMouseHandler;
19027 
19028 	Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child
19029 
19030 	import core.sys.posix.sys.time;
19031 	timeval tv;
19032 	gettimeofday(&tv, null);
19033 
19034 	Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000;
19035 
19036 	Time lastMouseTimestamp;
19037 
19038 	bool dnding = true;
19039 	Window lastIn = None;
19040 
19041 	void leave() {
19042 		if(lastIn == None)
19043 			return;
19044 
19045 		XEvent ev;
19046 		ev.xclient.type = EventType.ClientMessage;
19047 		ev.xclient.window = lastIn;
19048 		ev.xclient.message_type = GetAtom!("XdndLeave", true)(display);
19049 		ev.xclient.format = 32;
19050 		ev.xclient.data.l[0] = window.impl.window;
19051 
19052 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
19053 		XFlush(display);
19054 
19055 		lastIn = None;
19056 	}
19057 
19058 	void enter(Window w) {
19059 		assert(lastIn == None);
19060 
19061 		lastIn = w;
19062 
19063 		XEvent ev;
19064 		ev.xclient.type = EventType.ClientMessage;
19065 		ev.xclient.window = lastIn;
19066 		ev.xclient.message_type = GetAtom!("XdndEnter", true)(display);
19067 		ev.xclient.format = 32;
19068 		ev.xclient.data.l[0] = window.impl.window;
19069 		ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types
19070 
19071 		auto types = handler.availableFormats();
19072 		assert(types.length > 0);
19073 
19074 		ev.xclient.data.l[2] = types[0];
19075 		if(types.length > 1)
19076 			ev.xclient.data.l[3] = types[1];
19077 		if(types.length > 2)
19078 			ev.xclient.data.l[4] = types[2];
19079 
19080 		// FIXME: other types?!?!? and make sure we skip TARGETS
19081 
19082 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
19083 		XFlush(display);
19084 	}
19085 
19086 	void position(int rootX, int rootY) {
19087 		assert(lastIn != None);
19088 
19089 		XEvent ev;
19090 		ev.xclient.type = EventType.ClientMessage;
19091 		ev.xclient.window = lastIn;
19092 		ev.xclient.message_type = GetAtom!("XdndPosition", true)(display);
19093 		ev.xclient.format = 32;
19094 		ev.xclient.data.l[0] = window.impl.window;
19095 		ev.xclient.data.l[1] = 0; // reserved
19096 		ev.xclient.data.l[2] = (rootX << 16) | rootY;
19097 		ev.xclient.data.l[3] = dataTimestamp;
19098 		ev.xclient.data.l[4] = actionAtom;
19099 
19100 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
19101 		XFlush(display);
19102 
19103 	}
19104 
19105 	void drop() {
19106 		XEvent ev;
19107 		ev.xclient.type = EventType.ClientMessage;
19108 		ev.xclient.window = lastIn;
19109 		ev.xclient.message_type = GetAtom!("XdndDrop", true)(display);
19110 		ev.xclient.format = 32;
19111 		ev.xclient.data.l[0] = window.impl.window;
19112 		ev.xclient.data.l[1] = 0; // reserved
19113 		ev.xclient.data.l[2] = dataTimestamp;
19114 
19115 		XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev);
19116 		XFlush(display);
19117 
19118 		lastIn = None;
19119 		dnding = false;
19120 	}
19121 
19122 	// fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler
19123 	// but idk if i should...
19124 
19125 	window.setEventHandlers(
19126 		delegate(KeyEvent ev) {
19127 			if(ev.pressed == true && ev.key == Key.Escape) {
19128 				// cancel
19129 				dnding = false;
19130 			}
19131 		},
19132 		delegate(MouseEvent ev) {
19133 			if(ev.timestamp < lastMouseTimestamp)
19134 				return;
19135 
19136 			lastMouseTimestamp = ev.timestamp;
19137 
19138 			if(ev.type == MouseEventType.motion) {
19139 				auto display = XDisplayConnection.get;
19140 				auto root = RootWindow(display, DefaultScreen(display));
19141 
19142 				Window topWindow;
19143 				int rootX, rootY;
19144 
19145 				XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow);
19146 
19147 				if(topWindow == None)
19148 					return;
19149 
19150 				top:
19151 				if(auto result = topWindow in eligibility) {
19152 					auto dropWindow = *result;
19153 					if(dropWindow == None) {
19154 						leave();
19155 						return;
19156 					}
19157 
19158 					if(dropWindow != lastIn) {
19159 						leave();
19160 						enter(dropWindow);
19161 						position(rootX, rootY);
19162 					} else {
19163 						position(rootX, rootY);
19164 					}
19165 				} else {
19166 					// determine eligibility
19167 					auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM);
19168 					if(data.length == 1) {
19169 						// in case there is no WM or it isn't reparenting
19170 						eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh
19171 					} else {
19172 
19173 						Window tryScanChildren(Window search, int maxRecurse) {
19174 							// could be reparenting window manager, so gotta check the next few children too
19175 							Window child;
19176 							int x;
19177 							int y;
19178 							XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child);
19179 
19180 							if(child == None)
19181 								return None;
19182 							auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM);
19183 							if(data.length == 1) {
19184 								return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh
19185 							} else {
19186 								if(maxRecurse)
19187 									return tryScanChildren(child, maxRecurse - 1);
19188 								else
19189 									return None;
19190 							}
19191 
19192 						}
19193 
19194 						// if a WM puts more than 3 layers on it, like wtf is it doing, screw that.
19195 						auto topResult = tryScanChildren(topWindow, 3);
19196 						// it is easy to have a false negative due to the mouse going over a WM
19197 						// child window like the close button if separate from the frame... so I
19198 						// can't really cache negatives, :(
19199 						if(topResult != None) {
19200 							eligibility[topWindow] = topResult;
19201 							goto top; // reload to do the positioning iff eligibility changed lest we endless loop
19202 						}
19203 					}
19204 
19205 				}
19206 
19207 			} else if(ev.type == MouseEventType.buttonReleased) {
19208 				drop();
19209 				dnding = false;
19210 			}
19211 		}
19212 	);
19213 
19214 	window.grabInput();
19215 	scope(exit)
19216 		window.releaseInputGrab();
19217 
19218 
19219 	EventLoop.get.run(() => dnding);
19220 
19221 	return 0;
19222 }
19223 
19224 /// X-specific
19225 TrueColorImage getWindowNetWmIcon(Window window) {
19226 	try {
19227 		auto display = XDisplayConnection.get;
19228 
19229 		auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL);
19230 
19231 		if (data.length > arch_ulong.sizeof * 2) {
19232 			auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]);
19233 			// these are an array of rgba images that we have to convert into pixmaps ourself
19234 
19235 			int width = cast(int) meta[0];
19236 			int height = cast(int) meta[1];
19237 
19238 			auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]);
19239 
19240 			static if(arch_ulong.sizeof == 4) {
19241 				bytes = bytes[0 .. width * height * 4];
19242 				alias imageData = bytes;
19243 			} else static if(arch_ulong.sizeof == 8) {
19244 				bytes = bytes[0 .. width * height * 8];
19245 				auto imageData = new ubyte[](4 * width * height);
19246 			} else static assert(0);
19247 
19248 
19249 
19250 			// this returns ARGB. Remember it is little-endian so
19251 			//                                         we have BGRA
19252 			// our thing uses RGBA, which in little endian, is ABGR
19253 			for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) {
19254 				auto r = bytes[idx + 2];
19255 				auto g = bytes[idx + 1];
19256 				auto b = bytes[idx + 0];
19257 				auto a = bytes[idx + 3];
19258 
19259 				imageData[idx2 + 0] = r;
19260 				imageData[idx2 + 1] = g;
19261 				imageData[idx2 + 2] = b;
19262 				imageData[idx2 + 3] = a;
19263 			}
19264 
19265 			return new TrueColorImage(width, height, imageData);
19266 		}
19267 
19268 		return null;
19269 	} catch(Exception e) {
19270 		return null;
19271 	}
19272 }
19273 
19274 } /* UsingSimpledisplayX11 */
19275 
19276 
19277 void loadBinNameToWindowClassName () {
19278 	import core.stdc.stdlib : realloc;
19279 	version(linux) {
19280 		// args[0] MAY be empty, so we'll just use this
19281 		import core.sys.posix.unistd : readlink;
19282 		char[1024] ebuf = void; // 1KB should be enough for everyone!
19283 		auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length);
19284 		if (len < 1) return;
19285 	} else /*version(Windows)*/ {
19286 		import core.runtime : Runtime;
19287 		if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return;
19288 		auto ebuf = Runtime.args[0];
19289 		auto len = ebuf.length;
19290 	}
19291 	auto pos = len;
19292 	while (pos > 0 && ebuf[pos-1] != '/') --pos;
19293 	sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1);
19294 	if (sdpyWindowClassStr is null) return; // oops
19295 	sdpyWindowClassStr[0..len-pos+1] = 0; // just in case
19296 	sdpyWindowClassStr[0..len-pos] = ebuf[pos..len];
19297 }
19298 
19299 /++
19300 	An interface representing a font.
19301 
19302 	This is still MAJOR work in progress.
19303 +/
19304 interface DrawableFont {
19305 	void drawString(ScreenPainter painter, Point upperLeft, in char[] text);
19306 }
19307 
19308 /++
19309 	Loads a true type font using [arsd.ttf]. That module must be compiled
19310 	in if you choose to use this function.
19311 
19312 	Be warned: this can be slow and memory hungry, especially on remote connections
19313 	to the X server.
19314 
19315 	This is still MAJOR work in progress.
19316 +/
19317 DrawableFont arsdTtfFont()(in ubyte[] data, int size) {
19318 	import arsd.ttf;
19319 	static class ArsdTtfFont : DrawableFont {
19320 		TtfFont font;
19321 		int size;
19322 		this(in ubyte[] data, int size) {
19323 			font = TtfFont(data);
19324 			this.size = size;
19325 		}
19326 
19327 		Sprite[string] cache;
19328 
19329 		void drawString(ScreenPainter painter, Point upperLeft, in char[] text) {
19330 			Sprite sprite = (text in cache) ? *(text in cache) : null;
19331 
19332 			auto fg = painter.impl._outlineColor;
19333 			auto bg = painter.impl._fillColor;
19334 
19335 			if(sprite is null) {
19336 				int width, height;
19337 				auto data = font.renderString(text, size, width, height);
19338 				auto image = new TrueColorImage(width, height);
19339 				int pos = 0;
19340 				foreach(y; 0 .. height)
19341 				foreach(x; 0 .. width) {
19342 					fg.a = data[0];
19343 					bg.a = 255;
19344 					auto color = alphaBlend(fg, bg);
19345 					image.imageData.bytes[pos++] = color.r;
19346 					image.imageData.bytes[pos++] = color.g;
19347 					image.imageData.bytes[pos++] = color.b;
19348 					image.imageData.bytes[pos++] = data[0];
19349 					data = data[1 .. $];
19350 				}
19351 				assert(data.length == 0);
19352 
19353 				sprite = new Sprite(painter.window, Image.fromMemoryImage(image));
19354 				cache[text.idup] = sprite;
19355 			}
19356 
19357 			sprite.drawAt(painter, upperLeft);
19358 		}
19359 	}
19360 
19361 	return new ArsdTtfFont(data, size);
19362 }
19363 
19364 class NotYetImplementedException : Exception {
19365 	this(string file = __FILE__, size_t line = __LINE__) {
19366 		super("Not yet implemented", file, line);
19367 	}
19368 }
19369 
19370 ///
19371 __gshared bool librariesSuccessfullyLoaded = true;
19372 ///
19373 __gshared bool openGlLibrariesSuccessfullyLoaded = true;
19374 
19375 private mixin template DynamicLoadSupplementalOpenGL(Iface) {
19376 	mixin(staticForeachReplacement!Iface);
19377 
19378 	void loadDynamicLibrary() @nogc {
19379 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
19380 	}
19381 
19382         void loadDynamicLibraryForReal() {
19383                 foreach(name; __traits(derivedMembers, Iface)) {
19384                         mixin("alias tmp = " ~ name ~ ";");
19385                         tmp = cast(typeof(tmp)) glbindGetProcAddress(name);
19386                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL");
19387                 }
19388         }
19389 }
19390 
19391 private const(char)[] staticForeachReplacement(Iface)() pure {
19392 /*
19393 	// just this for gdc 9....
19394 	// when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease
19395 
19396         static foreach(name; __traits(derivedMembers, Iface))
19397                 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";");
19398 */
19399 
19400 	char[] code = new char[](__traits(derivedMembers, Iface).length * 64);
19401 	size_t pos;
19402 
19403 	void append(in char[] what) {
19404 		if(pos + what.length > code.length)
19405 			code.length = (code.length * 3) / 2;
19406 		code[pos .. pos + what.length] = what[];
19407 		pos += what.length;
19408 	}
19409 
19410         foreach(name; __traits(derivedMembers, Iface)) {
19411                 append(`__gshared typeof(&__traits(getMember, Iface, "`);
19412 		append(name);
19413 		append(`")) `);
19414 		append(name);
19415 		append(";");
19416 	}
19417 
19418 	return code[0 .. pos];
19419 }
19420 
19421 private mixin template DynamicLoad(Iface, string library, int majorVersion, bool openGLRelated = false, bool optional = false) {
19422 	mixin(staticForeachReplacement!Iface);
19423 
19424         private void* libHandle;
19425 	private bool attempted;
19426 
19427         void loadDynamicLibrary() @nogc {
19428 		(cast(void function() @nogc) &loadDynamicLibraryForReal)();
19429 	}
19430 
19431 	bool loadAttempted() {
19432 		return attempted;
19433 	}
19434 	bool loadSuccessful() {
19435 		return libHandle !is null;
19436 	}
19437 
19438         void loadDynamicLibraryForReal() {
19439 		attempted = true;
19440                 version(Posix) {
19441                         import core.sys.posix.dlfcn;
19442 			version(OSX) {
19443 				version(X11)
19444                         		libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW);
19445 				else
19446                         		libHandle = dlopen(library ~ ".dylib", RTLD_NOW);
19447 			} else {
19448                         	libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW);
19449 				if(libHandle is null)
19450                         		libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW);
19451 			}
19452 
19453 			static void* loadsym(void* l, const char* name) {
19454 				import core.stdc.stdlib;
19455 				if(l is null)
19456 					return &abort;
19457 				return dlsym(l, name);
19458 			}
19459                 } else version(Windows) {
19460                         import core.sys.windows.windows;
19461                         libHandle = LoadLibrary(library ~ ".dll");
19462 			static void* loadsym(void* l, const char* name) {
19463 				import core.stdc.stdlib;
19464 				if(l is null)
19465 					return &abort;
19466 				return GetProcAddress(l, name);
19467 			}
19468                 }
19469                 if(libHandle is null && !optional) {
19470 			if(openGLRelated)
19471 				openGlLibrariesSuccessfullyLoaded = false;
19472 			else
19473 				librariesSuccessfullyLoaded = false;
19474                         //throw new Exception("load failure of library " ~ library);
19475 		}
19476                 foreach(name; __traits(derivedMembers, Iface)) {
19477                         mixin("alias tmp = " ~ name ~ ";");
19478                         tmp = cast(typeof(tmp)) loadsym(libHandle, name);
19479                         if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library);
19480                 }
19481         }
19482 
19483         void unloadDynamicLibrary() {
19484                 version(Posix) {
19485                         import core.sys.posix.dlfcn;
19486                         dlclose(libHandle);
19487                 } else version(Windows) {
19488                         import core.sys.windows.windows;
19489                         FreeLibrary(libHandle);
19490                 }
19491                 foreach(name; __traits(derivedMembers, Iface))
19492                         mixin(name ~ " = null;");
19493         }
19494 }
19495 
19496 void guiAbortProcess(string msg) {
19497 	import core.stdc.stdlib;
19498 	version(Windows) {
19499 		WCharzBuffer t = WCharzBuffer(msg);
19500 		MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0);
19501 	} else {
19502 		import std.stdio;
19503 		stderr.writeln(msg);
19504 		stderr.flush();
19505 	}
19506 
19507 	abort();
19508 }
19509 
19510 private alias scriptable = arsd_jsvar_compatible;
Suggestion Box / Bug Report